JS是单线程,但是浏览器不是,只是执行JS代码的引擎是个单线程的所以JS的代码没办法开启多个线程。在执行的时候,有且只有一个主线程来处理所有的任务。

浏览器是多线程,异步任务是浏览器开启对应的线程来执行的,最后放入JS引擎中进行执行。

浏览器有定时器线程、事件触发线程、异步http请求线程、GUI线程等。

堆栈(先进后出),队列(先进先出)

执行栈 = 调用栈 = 执行上下文栈

宏任务/微任务

宏任务 Macrotasks

  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI rendering

微任务 Microtasks

  • process.nextTick
  • promises
  • Object.observe
  • MutationObserver

Event Loop

  1. 整体的代码(main script)块看成宏任务,开始第一次执行宏任务。
  2. 当遇到异步代码时进入到 event table,并注册其回调函数,接受到响应后将回调函数加入 任务队列,此时的同步代码也依次在执行。
  3. 异步任务分成宏任务和微任务,将分别进入宏任务队列、微任务队列。
  4. 同步代码执行完后,将执行微任务队列的任务,根据优先级和入队顺序,依次执行,直到微任务队列清空。
  5. 接着再去宏任务队列拿一个宏任务执行(只拿一个),执行完后。
  6. 继续看微任务队列是否有任务待执行,清空完所有微任务队列的任务后,再继续执行宏任务。
  7. 重复(4)(5)就构成了一个循环(event loop),直到所有宏任务队列和微任务队列被清空。

main script 的概念,就是一开始执行的代码,被定义为了宏任务,然后根据 main script 中产生的微任务队列和宏任务队列,先清空微任务的所有队列,再去清空宏任务的队列的一个。

(图片来源于网络)

以下示例代码摘自:传送门

代码一:

  1. setTimeout(()=>{
  2. console.log(1)
  3. },0)
  4. Promise.resolve().then(()=>{
  5. console.log(2)
  6. })
  7. console.log(3)
  8. // 打印结果:3 2 1

解析:

  1. 将定时任务加入宏任务队列
  2. 将Promise加入微任务队列
  3. 执行同步代码,打印 3
  4. 优先执行微任务,打印 2
  5. 再执行宏任务,打印 1

代码二:

  1. setTimeout(()=>{
  2. console.log(1)
  3. },0)
  4. let a = new Promise((resolve)=>{
  5. console.log(2)
  6. resolve()
  7. }).then(()=>{
  8. console.log(3)
  9. }).then(()=>{
  10. console.log(4)
  11. })
  12. console.log(5)

解析:

最后打印结果:2, 5, 3, 4, 1

  1. setTimeout 加入宏任务队列。
  2. Promiseexecutor 是同步的,所以打印 2,执行 resolve(),将 then 加入微任务队列。
  3. 再打印 5,主代码的同步代码执行完毕。
  4. 接着优先执行微任务,打印 3,而 Promisethen 会始终返回一个 resolve,所以这里默认执行 resolve(),再将第二个 then 放到微任务。
  5. 因为微任务是一次把微任务队列中所有清空,所以继续执行第二个 then 的回调,打印 4。
  6. 此此微任务被清空,执行宏任务的 setTimeout,打印 1。

代码三:

  1. new Promise((resolve,reject)=>{
  2. console.log("promise1")
  3. resolve()
  4. }).then(()=>{
  5. console.log("then11")
  6. new Promise((resolve,reject)=>{
  7. console.log("promise2")
  8. resolve()
  9. }).then(()=>{
  10. console.log("then21")
  11. }).then(()=>{
  12. console.log("then23")
  13. })
  14. }).then(()=>{
  15. console.log("then12")
  16. })

最后打印结果:promise1, then11, promise2, then21, then12, then23

解析:

  1. 首先执行外层 Promiseexecutor,输出 promise1 并将其第一个 then 加入微任务。
  2. 执行微任务 then,输出 then11,同时执行内层 Promise,输出 promise2,同时将内层第一个 then 加入微任务。
  3. 注意,在此时外层的 Promise 的第一个 then 里的同步代码已执行结束,剩下的是内层最后一个 then,它是异步代码,所以此时这里 return resolve() (默认的返回),将外层最后一个 then 加入微任务。
  4. 此时,微任务里有俩个任务,第一个是内层第一个 then 和外层最后一个 then
  5. 执行第一个微任务,输出 then21,再执行第二个微任务,输出 then 12
  6. 最后输出 then23
  7. 在这步最容易产的和问题是 then21then23then12 谁先执行的问题,搞清楚这里就OK。

代码四:

代码三的变异版本一

  1. new Promise((resolve,reject)=>{
  2. console.log("promise1")
  3. resolve()
  4. }).then(()=>{
  5. console.log("then11")
  6. return new Promise((resolve,reject)=>{
  7. console.log("promise2")
  8. resolve()
  9. }).then(()=>{
  10. console.log("then21")
  11. }).then(()=>{
  12. console.log("then23")
  13. })
  14. }).then(()=>{
  15. console.log("then12")
  16. })

输出:promise1, then11, promise2, then21, then23, then12

  1. 这里的就是 then12then23 之后了。
  2. 因为 Promise2 进行主动返回了,且返回的是 Promise2 的最后一个 then 的结果,就需要将 Promise2 的所有 then 执行完后才返回。

代码五:

代码三的变异版本二

  1. new Promise((resolve,reject)=>{ // Promise1
  2. console.log("promise1")
  3. resolve()
  4. }).then(()=>{
  5. console.log("then11")
  6. new Promise((resolve,reject)=>{ // Promise2
  7. console.log("promise2")
  8. resolve()
  9. }).then(()=>{
  10. console.log("then21")
  11. }).then(()=>{
  12. console.log("then23")
  13. })
  14. }).then(()=>{
  15. console.log("then12")
  16. })
  17. new Promise((resolve,reject)=>{ // Promise3
  18. console.log("promise3")
  19. resolve()
  20. }).then(()=>{
  21. console.log("then31")
  22. })

执行结果: promise1, promise3, then11, promise2, then31, then21, then12, then23

这里主要考的是微任务队列的先后问题

执行流程:

  1. 执行 Promise1Promise3executor,输出 "promise1", "promise3",并将对应 then,加入队列
    • MicroTaskQueue: [Promise1·then1Promise3·then1]
  2. 执行 Promise1·then1,输出 "then11""promise2"
    • MicroTaskQueue: [Promise3·then1Promise2·then1, Promise1·then2]
  3. 执行 Promise3·then1,输出 "then31"
    • MicroTaskQueue: [Promise2·then1, Promise1·then2]
  4. 执行 Promise2·then1,输出 "then21"
    • MicroTaskQueue: [Promise1·then2, Promise2·then2]
  5. 执行 Promise1·then2,输出 "then12"
    • MicroTaskQueue: [Promise2·then2]
  6. 执行 Promise2·then2,输出 "then23"
    • MicroTaskQueue: []
  7. 执行结束。

*执行代码六

  1. async function async1() {
  2. console.log("async1 start");
  3. await async2();
  4. console.log("async1 end");
  5. }
  6. async function async2() {
  7. console.log( 'async2');
  8. }
  9. console.log("script start");
  10. setTimeout(function () {
  11. console.log("settimeout");
  12. },0);
  13. async1();
  14. new Promise(function (resolve) {
  15. console.log("promise1");
  16. resolve();
  17. }).then(function () {
  18. console.log("promise2");
  19. });
  20. console.log('script end');

执行结果: script start, async1 start, async2, promise1, script end, async1 end, promise2, settimeout

执行流程:

  1. 主代码执行将 setTimeout 放入宏队列。
  2. 执行 async1(),输出 async1 start,遇到 await 调用 async2(),输出 async2,会返回一个 Promise,在微任务队列加入一个回调,继续执行同步代码。
  3. 执行 Promise 和后面的代码,在微任务队列再放一个 Promise 回调。
  4. 执行第一个回调,输入 async1 end,第二个回调 promise2

这里的最麻烦的是 asyncpromise 的执行顺序,实际上 asyncawait 就是 PromiseGenerator的语法糖,内部可以按 Promise 的东西来理解就行。

以上的 async1 函数等同于以下代码:

  1. async function async1(){
  2. console.log('async1 start')
  3. return new Promise((resolve)=>{
  4. console.log('async2')
  5. resolve()
  6. }).then(()=>{
  7. console.log('async1 end')
  8. })
  9. }

*执行代码七

此代码是代码六的变异版

  1. async function async1() {
  2. console.log("async1 start");
  3. await async2();
  4. console.log("async1 end");
  5. }
  6. async function async2() {
  7. await 'await hello'
  8. console.log( 'async2');
  9. }
  10. console.log("script start");
  11. setTimeout(function () {
  12. console.log(", settimeout");
  13. },0);
  14. async1();
  15. new Promise(function (resolve) {
  16. console.log("promise1");
  17. resolve();
  18. }).then(function () {
  19. console.log("promise2");
  20. }).then(function () {
  21. console.log("promise3");
  22. });
  23. console.log('script end');

解析:原理基本同 代码六 差不多,唯一就是在 async2() 中加入了 awaitasync1() 函数等同如下

  1. async function async1() {
  2. console.log("async1 start");
  3. return new Promise((resolve)=>{
  4. return Promise.resolve().then(()=>{
  5. console.log( 'async2');
  6. resolve()
  7. })
  8. }).then(()=>{
  9. console.log("async1 end");
  10. })
  11. }