JavaScript运行在一个单线程上
- 所有同步任务都在主线程执行,形成一个执行栈
- 主线程之外,还存在任务队列(task queue)。只要异步任务有了运行结果,就会在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,其中对应的异步任务结束等待状态,进入执行栈开始执行
- 主线程不断重复上面的第三步。
这个过程被称为事件循环(event-loop)
JavaScript中有两种异步任务
宏任务 | 浏览器 | NodeJs |
---|---|---|
I/O | √ | √ |
setTimeout | √ | √ |
setInterval | √ | √ |
setImmediate | × | √ |
requestAnimationFrame | √ | × |
微任务 | 浏览器 | NodeJs |
---|---|---|
process.nextTick | × | √ |
MutationObeserver | √ | × |
Promise | √ | √ |
清空宏任务微任务的事件正是事件循环(event-loop)
在执行完执行栈内的任务后,会立刻先处理所有微任务队列中的事件, 然后再去宏任务队列中取出一个事件。同一次事件循环中, 微任务永远在宏任务之前执行。
微任务队列清空完后,选择任务队列中的下一个宏任务,宏任务执行完后检查微任务队列
一个例子
/* 1*/ const next = msg => {
/* 2*/ return new Promise(resolve => {
/* 3*/ console.log(msg);
/* 4*/ resolve(msg + ' resolve');
/* 5*/ }).then(value => {
/* 6*/ console.log(value);
/* 7*/ })
/* 8*/ }
/* 9*/ const one = async next => {
/* 10*/ console.log('>> one');
/* 11*/ await next('do one');
/* 12*/ console.log('<< one');
/* 13*/ }
/* 14*/ const two = next => {
/* 15*/ console.log('>> two');
/* 16*/ next('do two').then(() => {
/* 17*/ console.log('<< two');
/* 18*/ })
/* 19*/ }
/* 20*/ one(next);
/* 21*/ two(next);
结果如下:
// >> one
// do one
// >> two
// do two
// do one resolve
// do two resolve
// << two
// << one
让我们捋一下代码执行过程
先是全局执行环境中
第20行,进入了one函数
跳转至第10行
第10行,打印>> one
第11行,进入next函数
跳转至第3行
第3行,打印do one
第4行,添加微任务,此时微任务队列简略概括为['do one resolve' ]
弹出next函数、弹出one函数
跳转至第21行
第21行,进入了two函数
跳转至第15行
第15行,打印>> two
第16行,进入next函数
跳转至第3行
第3行,打印do two
第4行,添加微任务,此时微任务队列简略概括为["do one resolve", "do two resolve" ]
弹出next函数
跳转至第16行
第16行,添加微任务,此时微任务队列简略概括为["do one resolve", "do two resolve", "<< two" ]
执行栈为空,此时开始清空微任务队列
分别打印:
do one resolve
do two resolve
<< two
此时await返回的Promise对象状态为Resolved,
跳转至11行,
第12行,打印<< one
参考资料:微任务、宏任务与Event-Loop