整理复习准备面试 ing ……
? 如有错误 欢迎指正
什么是 Event Loop?
Event Loop
即时间循环,是浏览器/Node
解决 JavaScript
单线程运行不阻塞的机制,也就是异步的原理。
浏览器中的 Event Loop
从一道题说起
console.log('script start');
// 1-1
setTimeout(() => {
console.log('史莱姆是一只猫');
}, 2000);
// 2-1
Promise.resolve()
.then(function() {
// 2-2
console.log('promise1');
}).then(function() {
// 2-3
console.log('promise2');
}).finally(() => {
// 2-4
console.log('finally')
});
// 3-1
async function foo() {
await bar()
// 3-2
console.log('async1 end')
}
foo()
// 4-1
async function errorFunc () {
try {
// 4-2
await Promise.reject('error!!!')
} catch(e) {
// 4-3
console.log(e)
}
// 4-4
console.log('async1');
return Promise.resolve('async1 success')
}
errorFunc().then(res => console.log(res)) // 4-5
// 5-1
function bar() {
console.log('async2 end')
}
// 6-1
console.log('script end');
复制代码
这个题并不难,答案也显而易见。
script start
async2 end
script end
promise1
async1 end
error!!!
async1
promise2
finally
async1 success
史莱姆是一只猫
复制代码
宏任务/微任务
我们用一张图来解析
主线程是一条加工线,待执行的任务就是流水线上的原料,需要一个个加工,event loop
便是加工的工人,能够直接被处理的原料会被依次处理,这就是同步任务(macrotask/宏任务),倘若需要先预处理的原料会被按照种类排序处理后,再次放回流水线,这就是异步任务(microtask/微任务)。
常见宏任务:script
全部代码、setTimeout
、setInterval
、setImmediate
、I/O
、UI Rendering
。
常见微任务:Process.nextTick
(Node独有)、Promise
、MutationObserver
执行过程
- 主线程不断循环
- 开始执行,同步任务创建执行上下文进入执行栈。
- 同步任务执行过程中,会判断执行代码是否有同步/异步任务,有就分别放入各自的队列中。
- 同步任务执行完毕,会来处理异步任务队列中的微任务,清空整队的微任务。
- 微任务队列清空,下一个同步任务进入执行栈,重复上面的循环。
开始分析上面的题目
接着我们开始分析上面的题目
- 首先,打印
script start
- 运行到 1-1,发现是宏任务,放入队列
- 运行到 2-1,
Promise.resolve()
运行,后面的then()
放入微任务队列 - 运行到 3-1,
foo()
函数,await bar()
,打印async2 end
,后面放入 微任务队列
这里需要注意 await 的用法
await 表达式可以理解为异步等待获取结果,await 作为语法糖就是为了 简化 promise().then() 的写法
3-1await bar()
后面的代码可以理解为
Promise.then((res) => res).then(() => { console.log('async1 end') })) 复制代码
- 运行到 4-1,
errorFunc()
运行,
await Promise.reject('error!!!')
后面的代码可以理解为
Promise.then((res) => res).then(() => { console.log(e) console.log('async1'); }).then(() => Promise.resolve('async1 success')) 复制代码
- 运行到 6-1,打印
script end
,主线程运行完毕,开始运行微任务队列。 - 打印 2-2
promise1
,后面的then()
放入微任务队列。 - 打印 3-2
async1 end
- 打印 4-3
error!!!
- 打印 4-4
async1
,后面的then()
放入微任务队列。 - 打印 2-3
promise2
- 打印 2-4
finally
- 打印 4-5
async1 success
- 微任务队列清空,主线程拉取宏任务
- 打印 1-1
史莱姆是一只猫
NodeJS Event Loop
Node.js 的运行机制
- V8 引擎解析 JavaScript 脚本。
- 解析后的代码,调用 Node API。
- libuv 库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个 Event Loop(事件循环),以异步的方式将任务的执行结果返回给 V8 引擎。
- V8 引擎再将结果返回给用户。
六个阶段
- timers: 执行
setTimeout
和setInterval
中到期的callback
。 - pending callback: 上一轮循环中少数的
callback
会放在这一阶段执行。 - idle, prepare: 仅在内部使用。
- poll: 获取新的 I/O 事件,适当的条件下 node 将阻塞在这里。
- check: 执行
setImmediate
的回调。 - close callbacks: 执行
socket
的close
事件回调。
Process.nextTick()
process.nextTick()
虽然它是异步 API
的一部分,但未在图中显示。这是因为 process.nextTick()
从技术上讲,它不是事件循环的一部分。
当每个阶段完成后,如果存在 nextTick
队列,就会清空队列中的所有回调函数,并且优先于其他 microtask
执行。
题目
console.log('start')
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
Promise.resolve().then(function() {
console.log('promise3')
})
function tick(callback) {
process.nextTick(callback);
}
let cat
tick(() => {
console.log('name', cat); // 1
});
cat = 1;
console.log('end')
复制代码
结果(node > v11)
start
end
name 1
promise3
timer1
promise1
复制代码
Node 与浏览器的 Event Loop 差异
Node
端,microtask
在事件循环的各个阶段之间执行- 浏览器端,
microtask
在事件循环的macrotask
执行完之后执行
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END