event loop

https://blog.csdn.net/itKingOne/article/details/86502910 > https://www.jianshu.com/p/129ae19e47b7 为什么 setTimeout 会比 Promise 后执行,明明代码写在 Promise 之前,这其中就涉及倒到了 Event Loop 的相关知识。

进程与线程

JS 是单线程执行的。
进程描述了 CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。线程是进程中的更小单位,描述了执行一段指令所需的时间。
好处:节省内存,节约上下文切换时间,没有锁的问题的好处。

执行栈

可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。即为最先执行的函数最后弹出,最后执行的函数最先弹出。
其实这也很好理解,当函数调用存在包裹时,先执行的父函数需要等待子函数的执行完成之后才能释放内存。

事件循环

事件循环被称作循环的原因在于,它一直在查找新的事件并且执行。一次循环的执行称之为 tick, 在这个循环里执行的代码称作 task。
任务(Tasks)中同步执行的代码可能会在循环中生成新的任务。

任务队列

让事情变得复杂的情况是,事件循环可能有几种任务任务队列。唯一的两个限制是同一个任务源中的事件必须属于同一个队列,并且必须在每个队列中按插入顺序处理任务。

浏览器中的 Event Loop

其实当遇到异步的代码时,会被挂起并在需要执行的时候加入到 Task(有多种 Task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为。
不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task。
示例:

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('set-timeout')
}, 0)
new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })
console.log('script end')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

执行结果为:

script start
async2 end
Promise
script end
async1 end
promise1
promise2
undefined
set-timeout
1
2
3
4
5
6
7
8
9

解析:首先先来解释下上述代码的 async 和 await 的执行顺序。当我们调用 async1 函数时,会马上输出 async2 end,并且函数返回一个 Promise,接下来在遇到 await 的时候会就让出线程开始执行 async1 外的代码,所以我们完全可以把 await 看成是让出线程的标志。
然后当同步代码全部执行完毕以后,就会去执行所有的异步代码,那么又会回到 await 的位置执行返回的 Promise 的 resolve 函数,这又会把 resolve 丢到微任务队列中,接下来去执行 then 中的回调,当两个 then 中的回调全部执行完毕以后,又会回到 await 的位置处理返回值,这时候你可以看成是 Promise.resolve(返回值).then(),然后 await 后的代码全部被包裹进了 then 的回调中,所以 console.log('async1 end') 会优先执行于 setTimeout。

繁琐复杂而且不对。。
我们从上到下依次来看,首先执行了 script 的宏任务。 宏任务 1:script start -> async2 end (await 加入到微任务) -> Promise (then 加入到微任务) -> script end
微任务 1:async1 end -> promise1 -> promise2 完成微任务 1 以后执行宏任务 2:set-timeout

所以 Event Loop 执行顺序如下:

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

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

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

这里很多人会有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 script ,浏览器会先执行一个宏任务,接下来有异步代码的话才会先执行微任务。

Node 中的 Event Loop

上次更新时间: 10/16/2019, 1:31:15 AM