JavaScript
虽然是一门单线程语言,但并不意味着所有代码都会阻塞,实现单线程非阻塞的方法就是事件循环
JavaScript
中,所有的任务都可以分为:
ajax
请求、setTimeout
定时任务等先从一段简单的代码开始,考虑下面代码的打印顺序:
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
出现这种情况,是因为异步任务还细分为微任务与宏任务
微任务:
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
宏任务:
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
宏任务与微任务的执行流程如下:
来看一个更加复杂的例子:
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
异步任务中的代码
流程图如下:
见以下代码:
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
分支