JS是单线程,但是浏览器不是,只是执行JS代码的引擎是个单线程的所以JS的代码没办法开启多个线程。在执行的时候,有且只有一个主线程来处理所有的任务。
浏览器是多线程,异步任务是浏览器开启对应的线程来执行的,最后放入JS引擎中进行执行。
浏览器有定时器线程、事件触发线程、异步http请求线程、GUI线程等。
堆栈(先进后出),队列(先进先出)
执行栈 = 调用栈 = 执行上下文栈
宏任务/微任务
宏任务 Macrotasks
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
微任务 Microtasks
- process.nextTick
- promises
- Object.observe
- MutationObserver
Event Loop
- 整体的代码(main script)块看成宏任务,开始第一次执行宏任务。
- 当遇到异步代码时进入到
event table
,并注册其回调函数,接受到响应后将回调函数加入任务队列
,此时的同步代码也依次在执行。 - 异步任务分成宏任务和微任务,将分别进入宏任务队列、微任务队列。
- 同步代码执行完后,将执行微任务队列的任务,根据优先级和入队顺序,依次执行,直到微任务队列清空。
- 接着再去宏任务队列拿一个宏任务执行(只拿一个),执行完后。
- 继续看微任务队列是否有任务待执行,清空完所有微任务队列的任务后,再继续执行宏任务。
- 重复(4)(5)就构成了一个循环(event loop),直到所有宏任务队列和微任务队列被清空。
main script
的概念,就是一开始执行的代码,被定义为了宏任务,然后根据main script
中产生的微任务队列和宏任务队列,先清空微任务的所有队列,再去清空宏任务的队列的一个。
(图片来源于网络)
以下示例代码摘自:传送门
代码一:
setTimeout(()=>{
console.log(1)
},0)
Promise.resolve().then(()=>{
console.log(2)
})
console.log(3)
// 打印结果:3 2 1
解析:
- 将定时任务加入宏任务队列
- 将Promise加入微任务队列
- 执行同步代码,打印 3
- 优先执行微任务,打印 2
- 再执行宏任务,打印 1
代码二:
setTimeout(()=>{
console.log(1)
},0)
let a = new Promise((resolve)=>{
console.log(2)
resolve()
}).then(()=>{
console.log(3)
}).then(()=>{
console.log(4)
})
console.log(5)
解析:
最后打印结果:2, 5, 3, 4, 1
- 将
setTimeout
加入宏任务队列。 - 因
Promise
的executor
是同步的,所以打印 2,执行resolve()
,将then
加入微任务队列。 - 再打印 5,主代码的同步代码执行完毕。
- 接着优先执行微任务,打印 3,而
Promise
的then
会始终返回一个resolve
,所以这里默认执行resolve()
,再将第二个then
放到微任务。 - 因为微任务是一次把微任务队列中所有清空,所以继续执行第二个
then
的回调,打印 4。 - 此此微任务被清空,执行宏任务的
setTimeout
,打印 1。
代码三:
new Promise((resolve,reject)=>{
console.log("promise1")
resolve()
}).then(()=>{
console.log("then11")
new Promise((resolve,reject)=>{
console.log("promise2")
resolve()
}).then(()=>{
console.log("then21")
}).then(()=>{
console.log("then23")
})
}).then(()=>{
console.log("then12")
})
最后打印结果:promise1, then11, promise2, then21, then12, then23
解析:
- 首先执行外层
Promise
的executor
,输出promise1
并将其第一个then
加入微任务。 - 执行微任务
then
,输出then11
,同时执行内层Promise
,输出promise2
,同时将内层第一个then
加入微任务。 - 注意,在此时外层的
Promise
的第一个then
里的同步代码已执行结束,剩下的是内层最后一个then
,它是异步代码,所以此时这里return resolve()
(默认的返回),将外层最后一个then
加入微任务。 - 此时,微任务里有俩个任务,第一个是内层第一个
then
和外层最后一个then
。 - 执行第一个微任务,输出
then21
,再执行第二个微任务,输出then 12
。 - 最后输出
then23
- 在这步最容易产的和问题是
then21
、then23
、then12
谁先执行的问题,搞清楚这里就OK。
代码四:
代码三的变异版本一
new Promise((resolve,reject)=>{
console.log("promise1")
resolve()
}).then(()=>{
console.log("then11")
return new Promise((resolve,reject)=>{
console.log("promise2")
resolve()
}).then(()=>{
console.log("then21")
}).then(()=>{
console.log("then23")
})
}).then(()=>{
console.log("then12")
})
输出:promise1, then11, promise2, then21, then23, then12
- 这里的就是
then12
在then23
之后了。 - 因为
Promise2
进行主动返回了,且返回的是Promise2
的最后一个then
的结果,就需要将Promise2
的所有then
执行完后才返回。
代码五:
代码三的变异版本二
new Promise((resolve,reject)=>{ // Promise1
console.log("promise1")
resolve()
}).then(()=>{
console.log("then11")
new Promise((resolve,reject)=>{ // Promise2
console.log("promise2")
resolve()
}).then(()=>{
console.log("then21")
}).then(()=>{
console.log("then23")
})
}).then(()=>{
console.log("then12")
})
new Promise((resolve,reject)=>{ // Promise3
console.log("promise3")
resolve()
}).then(()=>{
console.log("then31")
})
执行结果: promise1, promise3, then11, promise2, then31, then21, then12, then23
这里主要考的是微任务队列的先后问题
执行流程:
- 执行
Promise1
和Promise3
的executor
,输出"promise1"
,"promise3"
,并将对应then
,加入队列- MicroTaskQueue: [
Promise1·then1
,Promise3·then1
]
- MicroTaskQueue: [
- 执行
Promise1·then1
,输出"then11"
、"promise2"
- MicroTaskQueue: [
Promise3·then1
,Promise2·then1
,Promise1·then2
]
- MicroTaskQueue: [
- 执行
Promise3·then1
,输出"then31"
- MicroTaskQueue: [
Promise2·then1
,Promise1·then2
]
- MicroTaskQueue: [
- 执行
Promise2·then1
,输出"then21"
- MicroTaskQueue: [
Promise1·then2
,Promise2·then2
]
- MicroTaskQueue: [
- 执行
Promise1·then2
,输出"then12"
- MicroTaskQueue: [
Promise2·then2
]
- MicroTaskQueue: [
- 执行
Promise2·then2
,输出"then23"
- MicroTaskQueue: []
- 执行结束。
*执行代码六
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log( 'async2');
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
},0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log('script end');
执行结果: script start, async1 start, async2, promise1, script end, async1 end, promise2, settimeout
执行流程:
- 主代码执行将
setTimeout
放入宏队列。 - 执行
async1()
,输出async1 start
,遇到await
调用async2()
,输出async2
,会返回一个Promise
,在微任务队列加入一个回调,继续执行同步代码。 - 执行
Promise
和后面的代码,在微任务队列再放一个Promise
回调。 - 执行第一个回调,输入
async1 end
,第二个回调promise2
这里的最麻烦的是
async
和promise
的执行顺序,实际上async
和await
就是Promise
和Generator
的语法糖,内部可以按Promise
的东西来理解就行。
以上的 async1
函数等同于以下代码:
async function async1(){
console.log('async1 start')
return new Promise((resolve)=>{
console.log('async2')
resolve()
}).then(()=>{
console.log('async1 end')
})
}
*执行代码七
此代码是代码六的变异版
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
await 'await hello'
console.log( 'async2');
}
console.log("script start");
setTimeout(function () {
console.log(", settimeout");
},0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
}).then(function () {
console.log("promise3");
});
console.log('script end');
解析:原理基本同 代码六
差不多,唯一就是在 async2()
中加入了 await
。async1()
函数等同如下
async function async1() {
console.log("async1 start");
return new Promise((resolve)=>{
return Promise.resolve().then(()=>{
console.log( 'async2');
resolve()
})
}).then(()=>{
console.log("async1 end");
})
}