目录

---------

JavaScript 虽然是一门单线程语言,但并不意味着所有代码都会阻塞,实现单线程非阻塞的方法就是事件循环

JavaScript 中,所有的任务都可以分为:

先从一段简单的代码开始,考虑下面代码的打印顺序:

Javascript
console.log(1)

setTimeout(() => {
  console.log(2)
}, 0)

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve()
}).then(() => {
  console.log('then')
})

console.log(3)

如果我们只区分同步和异步,那么它的打印顺序应该是 1 -> new Promise -> 3 -> 2 -> then,而事实上,正确的结果应该是 1 -> new Promise -> 3 -> then -> 2

出现这种情况,是因为异步任务还细分为微任务与宏任务

微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

常见的微任务有:

宏任务:

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务有:

宏任务与微任务的执行流程如下:

来看一个更加复杂的例子:

Javascript
console.log(1)

// 定时器属于新的宏任务,放到后面执行
setTimeout(() => {
  console.log(2)
  setTimeout(() => {
    console.log('timeout5')
  }, 0)
}, 0)

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve()
})
  // .then 属于微任务,放到微任务队列
  .then(() => {
    console.log('then')
    new Promise((resolve) => {
      resolve('then1 then1')
    }).then((data) => {
      console.log(data)
    })
  })
  .then(() => {
    console.log('then2')
  })

new Promise((resolve, reject) => {
  console.log('new Promise2')
  resolve()
})
  .then(() => {
    console.log('promise2.then')
  })
  .then(() => {
    console.log('promise2.then2')
    new Promise(() => {
      console.log('then then1')
    }).then(() => {
      console.log('then then2') // 上述 Promise 并未 resolve,所以不会打印
    })
  })

setTimeout(() => {
  console.log('timeout 3')
  setTimeout(() => {
    console.log('timeout4')
  }, 0)
}, 0)

console.log(3)

正确的打印顺序是 1 -> new Promise -> new Promise2 -> 3 -> then -> promise2.then -> then then1 -> then2 -> promise2.then2 -> then then1 -> 2 -> timeout3 -> timeout5 -> timeout4

整段代码都属于一个宏任务,所以开始也会收集微任务,例如先收集 .then.then 在微任务队列中依次被消耗,等到 .then 消耗完在执行 setTimout 异步任务中的代码

流程图如下:

img img

补充

见以下代码:

Javascript
new Promise((resolve) => {
  console.log(111)
  resolve()
  fdaf.fda()
})
  .then(() => {
    console.log(222)
  })
  .catch(() => {
    console.log(333)
  })

其正确打印顺序是 111 -> 222,之所以这样,是因为 Promise 只会改变一次状态,即要么从 pending 变为 fulfilled,要么从 pending 变为 rejected,上述代码在错误发生之前就已经 resolve 发生状态改变了,因此即使后续遇到错误,也不会走向 .catch 分支