MS-JS异步&EventLoop

并发与并行

并发是宏观概念,有任务A和任务B,在一段时间内通过任务间的切换完成这两种任务,这种情况称为并发

并行是微观概念,假设CPU中存在两个核心,同时完成任务A/B,这种同时完成多个任务的情况可以称之为并行

回调函数

ajax(url,()=>{
    // 回调函数
})

回调函数用于出现回调地狱,如多个请求存在依赖性,即会出现

ajax(url,()=>{
    ajax(url2,()=>{
        ajax(url2,()=>{
    
        })
    })
})
  1. 回调函数的嵌套存在耦合性,牵一发而动全身
  2. 回调函数过多,不容易处理错误

Promise

特征

Promise有三种状态等待中(pending)已完成(resolved)拒绝了(rejected)

状态一旦从等待中改变就不能再改变了

在构造 Promise 的时候,构造函数内部的代码是立即执行的

链式调用

Promise实现了链式调用,即每次调用then返回的也是一个Promise。如果在then中使用了returnreturn的内容也会被Promise.resolved包装

优缺点

Promise解决了回调函数的回调地狱问题

Promise也存在无法取消的问题

async和await

如果函数加上async就会返回一个Promise

async funtion as(){
    return '1'
}
console.log(as()) // Promise{<resolved>:'1'>}

async 就是将函数返回值使用 Promise.resolve() 包裹了下,和 then 中处理返回值一样,并且 await 只能配套 async 使用
async中遇到await就会先返回,等到异步操作完成,在执行函数体后面的语句

async 函数对 Generator 函数的改进,体现在以下四点

  1. 内置执行器。Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器
  2. 更好的语义。asyncawait,比起星号和yield,语义更清楚了
  3. 更广的适用性。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值
  4. 返回值是 Promise。async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

asyncawait是异步的终极解决方案
相比Promise优势在于处理调用链,逻辑更为清楚,没有那么多的then
缺点是,await会将代码当做同步代码处理,如果多个异步请求没有依赖关系会导致性能降低

定时器

setTimeout

setTimeout的延迟时间不能保证准确执行,会受到同步代码的执行时间影响

setInterval

setInterval不仅存在执行时间不准确的问题,还有累积执行的问题
如果在定时器执行期间有大量消耗时间的操作,定时器累积的回调函数会在同一时间执行

requestAnimationFrame

window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
requestAnimationFrame 自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题

进程与线程

进程与线程?JS 单线程?

进程描述了 CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。
线程是进程中的更小单位,描述了执行一段指令所需的时间。

在浏览器中来说,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。

在 JS 运行的时候可能会阻止 UI 渲染,这说明了两个线程是互斥的。
这其中的原因是因为 JS 可以修改 DOM,如果在 JS 执行的时候 UI 线程还在工作,就可能导致不能安全的渲染 UI。
这其实也是一个单线程的好处,得益于 JS 是单线程运行的,可以达到节省内存,节约上下文切换时间,没有锁的问题的好处

EventLoop

执行 JS 代码的时候其实就是往执行栈中放入函数

当遇到异步的代码时,会被挂起并在需要执行的时候加入到 Task(有多种 Task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行

不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)

微任务包括 process.nextTick ,promise ,MutationObserver。

宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering。

 console.log('script start')

 async function async1() {
     await async2()
     console.log('async1 end')
 }
 async function async2() {
     console.log('async2 end')
 }
 async1()

 setTimeout(function () {
     console.log('setTimeout')
 }, 0)

 new Promise(resolve => {
         console.log('Promise')
         resolve()
     })
     .then(function () {
         console.log('promise1')
     })
     .then(function () {
         console.log('promise2')
     })

 console.log('script end')
 /* 
 script start
 async2 end await让出线程不影响函数执行 返回promise
 Promise
 script end 
 promise1
 promise2
 async1 end
 setTimeout
  */

  1. 首先执行同步代码script,这属于宏任务
  2. 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
  3. 执行所有微任务
  4. 当执行完所有微任务后,如有必要会渲染页面
  5. 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数

猜你喜欢

转载自blog.csdn.net/Basarawade/article/details/87923360