写这篇文章的原由是在群里看到了各位再讨论这部分的内容,这一块本身也不太懂,一时手痒就写了这篇文章这一块不少初学者也是一知半懂,学到一半发现又麻烦又复杂,索性放弃了。 原本打算考完操做系统就写完的,结果又遇到了 CPU 课设...因此这篇文章断断续续写了不少天javascript
简单点讲 event loop 就是对 JS 代码执行顺序的一个规定(任务调度算法)前端
先看看两幅图java
JS enginegit
via: sessionstackgithub
JS runtimeweb
via: sessionstack算法
NOTE:express
一个 web worker 或者一个跨域的 iframe 都有本身的栈,堆和消息队列。两个不一样的运行时只能经过 postMessage 方法进行通讯。若是另外一运行时侦听 message 事件,则此方法会向其添加消息。跨域
HTML Eventloop浏览器
这幅图就是对 whatwg 组织制定 HTML 规范中的 event loop 的可视化
咱们一般在编写 web 代码的时候,都是和JS runtime打交道
毫无疑问是按顺序执行
console.log(2); // 非异步代码
console.log(3); // 非异步代码
复制代码
显然结果是 2 3
通常分为两种任务,macroTasks 和 microTasks
event loop 里面有维护了两个不一样的异步任务队列 macroTasks(Tasks) 的队列 microTasks 的队列
宏任务包括:setTimeout, setInterval, setImmediate, I/O, UI rendering
微任务包括: 原生 Promise(有些实现的 Promise 将 then 方法放到了宏任务中), Object.observe(已废弃), MutationObserver, MessageChannel
每次开始执行一段代码(一个 script 标签)都是一个 macroTask
一、event-loop start
二、从 macroTasks 队列抽取一个任务,执行
三、microTasks 清空队列执行,如有任务不可执行,推入下一轮 microTasks
四、结束 event-loop
值得一提的是,在 HTML 标准中提到了一个 compound microtasks 当它执行时可能会去执行一个 subTask,执行 compound microTasks 是一件很复杂的事情,在 whatwg 我也没找到这部分具体的执行流程
const p = Promise.resolve();
p.then(() => {
Promise.resolve().then(() => {
console.log('subTask');
});
}).then(() => {
console.log('compound microTasks');
});
// subTask
// compound microTasks
复制代码
按理说 p 的两个 then 先执行,在执行 then 函数回调的时候又发现了 microTask,那应该是下一轮 eventLoop 执行了,可是结果确是相反的
浏览器执行代码的真正过程是下面整个流程,而咱们编写代码感知的过程是红框里面的(因此之后要是有人再问起你 macroTask 和 microTask 哪一个先执行,可别再说 microTask 了)
setTimeout(() => {
console.log(123);
});
const p = Promise.resolve(
new Promise(resolve => {
setTimeout(() => {
resolve('p');
console.log(55);
}, 1000);
new Promise(resolve => {
resolve('p1');
}).then(r => console.log(r));
})
);
setTimeout(() => {
console.log(456);
});
p.then(r => console.log(r));
复制代码
你们能够先猜猜这段代码的执行顺序,相信若是没有上面的介绍,我以为不少人在这就晕了 不过有了上面的介绍加上我们一步一步的分析,你必定会明白的
函数名后面的数字或者变量,是这个函数打印的东西,借此区分函数
扫描完这些代码,各任务队列的状况以下图(注意此时由浏览器提供的 setTimeout 会检查各定时任务是否到时间,若是到了则推入任务队列,因此此时定时 1000ms 的回调函数并未出如今 macroTask 中) 而后执行完同步代码,开始按上面介绍的状况开始执行 macro Task 和 micro Task
先执行 micro Task,拿出 p.then p1 发现可执行,打印 p1;而后拿出 p.then p 发现不可执行,即 status 为“pending”, 这一轮 micro Task 执行完毕 开始执行 macro Task,拿出 setTimeout 123,发现可执行(此时同步代码已执行完毕),打印 123,检查执行 micro Task, p.then p 依旧不可执行 等到 macro Task 执行完一段时间,发现 micro Task 里面的 p.then p 可执行了,打印,结束 event loop
因此这一段代码的打印结果是
5
p1
123
456
55
p
复制代码
你有作对吗,这只是小 case,还没加上 async 函数呢,接下来看看 async 函数
当一个 async 函数里面执行 await 的时候,实际上是标志这个 async 函数要让出线程了(我我的以为这就像执行 一个特殊的 函数同样,该函数会推动第一轮微任务队列末尾),当 async 函数里面的 await 语句后面的函数或者表达式执行完,该函数立马退出执行,调用栈也会撤销, 当本轮事件循环完毕的时候又会回来执行剩下的代码
再来看看 MDN 咋说的
An async function can contain an await expression that pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value.
翻译过来就是 async 函数能够包含一个 await 表达式,该表达式暂停执行 async 函数并等待返回的 Promise resovle/reject 完成,而后恢复 async 函数的执行并返回已解析的值
看完你应该知道为啥 await 表达式会让 async 函数让出线程了吧?(若是不让出线程,还不如写同步代码了,阻塞后面全部代码), 结合前面的 Event Loop,能够肯定,await 表达式须要等待 Promise 解析完成,await 恢复 async 函数执行须要等待执行完第一轮微任务之后,毕竟不是每一个 async 函数都是直接返回一个非 Promise 的值或者当即解析的 Promise,因此等 mainline JS 执行完还须要等待一轮 event loop
await 阻塞的是当前属于 async 函数做用域下后面的代码
答案是当每一轮 microTask 执行完毕后恢复,具体哪一轮,看返回的 Promise 何时解析完成
async function b() {
console.log('1');
}
async function c() {
console.log('7');
}
async function a() {
console.log('2');
await b();
//console.log(3);
await c();
console.log(8);
}
a();
console.log(5);
Promise.resolve()
.then(() => {
console.log(4);
})
.then(() => {
console.log(6);
});
new Promise(resolve => {
setTimeout(() => resolve(), 1000);
}).then(() => console.log(55555555));
setTimeout(() => {
console.log(123);
});
复制代码
有了上面的解释,加上下面这个 GIF,上面这段代码执行过程一目了然了 我就再也不赘述了,你们直接看我单步执行这些代码顺序应该就懂了(使用了定时器可能单步调试打印的信息可能会和正常执行不同)
macroTask 先执行(毕竟标准就是这么定的),至于为何,我我的认为是由于 macroTask 都是和用户交互有关的事件,因此须要及时响应
// 验证
const p = new Promise(resolve => resolve());
console.log(p === Promise.resolve(p)); // true
复制代码
await 语句会先执行其后面的表达式,(若是该表达式是函数且该函数里面遇到 await,则会按一样的套路执行),而后阻塞属于当前 async 函数做用域下后面的代码
当执行完 await 语句以后的 某一轮 eventloop 结束后恢复执行(它须要等待它右侧的返回的 Promise 解析完成,而 Promise 解析多是同步的(new Promise),也多是异步的(.then),而 then 回调须要等到 eventloop 最后去执行)
来源 | 连接 |
---|---|
IMWeb 前端博客 | imweb.io/ |
MDN | developer.mozilla.org/en-US/ |
前端精读周刊 | github.com/dt-fe/weekl… |
sessionstack | blog.sessionstack.com/ |
v8 博客 fastasync(中文版) | v8.js.cn/blog/fast-a… |
Tasks, microtasks, queues and schedules | jakearchibald.com/2015/tasks-… |
Secrets of the JavaScript Ninja | livebook.manning.com/#!/book/sec… |