任务队列
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行;
- 异步任务:异步执行的任务,比如ajax网络请求,setTimeout 定时函数,异步任务会通过任务队列的机制(先进先出的机制)来进行协调。
主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。
上述过程的不断重复就是我们说的 Event Loop (事件循环)
宏任务和微任务
- Macro Task (宏任务):script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
在ECMAScript中,macrotask可称为task - Micro Task(微任务):Promise、MutaionObserver、process.nextTick(Node.js 环境)
在ECMAScript中,microtask称为jobs 在node环境下,process.nextTick的优先级高于Promise。简单理解:在宏任务结束后会先执行微任务队列中的nextTickQueue部分,然后才会执行微任务中的Promise部分。
每个宏任务结束后, 都要清空所有的微任务
1.在此次 tick 中选择最先进入队列的任务( oldest task ),如果有则执行(一次) 2.检查是否存在 Microtasks ,如果存在则不停地执行,直至清空Microtask Queue 3.更新 render 4.主线程重复执行上述步骤
示例一
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
- 整体script作为第一个宏任务进入主线程,遇到
console.log('script start'); ,输出script start - 遇到
setTimeout ,其回调函数被分发到宏任务Event Queue 中 - 遇到
Promise ,其then函数被分到微任务Event Queue中,记为then1,然后又遇到then函数,将其分配到微任务Event Queue中,记为then2 - 遇到
console.log('script end'); ,输出script end
现在宏任务队列和微任务队列如下:
- 执行微任务,首先执行then1,输出 promise1;
然后执行 then2,输出 promise2 ,清空所有微任务 - 执行宏任务 setTimeout ,输出 setTimeout
至此,输出的顺序是:script start, script end, promise1, promise2, setTimeout
Async/Await
async
当我们在函数前使用async的时候,使得该函数返回的是一个Promise对象 async的函数会在这里帮我们隐式使用Promise.resolve(1)
async function test() {
return 1
}
等价于下面的代码
function test() {
return new Promise(function(resolve, reject) {
resolve(1)
})
}
await
await表示等待,是右侧「表达式」的结果,这个表达式的计算结果可以是 Promise 对象的值或者一个函数的值(换句话说,就是没有特殊限定)。并且只能在带有async的内部使用
使用await时,会从右往左执行,当遇到await时,会阻塞函数内部处于它后面的代码,去执行该函数外部的同步代码,当外部同步代码执行完毕,再回到该函数内部执行剩余的代码,并且当await执行完毕之后,会先处理微任务队列的代码
示例二
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' )
使用事件循环机制分析:
- 首先执行同步代码,
console.log( 'script start' ) - 遇到
setTimeout ,会被推入宏任务队列 - 执行async1(), 它也是同步的,只是返回值是Promise,在内部首先执行
console.log( 'async1 start' ) - 然后执行async2(), 然后会打印
console.log( 'async2' ) - 从右到左会执行, 当遇到await的时候,阻塞后面的代码,去外部执行同步代码
- 进入
new Promise ,打印console.log( 'promise1' ) - 将
.then 放入事件循环的微任务队列 - 继续执行,打印
console.log( 'script end' ) - 外部同步代码执行完毕,接着回到async1()内部。
由于async2()其实是返回一个Promise, await async2() 相当于获取它的值,其实就相当于这段代码Promise.resolve(undefined).then((undefined) => {}) ,所以.then会被推入微任务队列, 所以现在微任务队列会有两个任务。 接下来处理微任务队列,打印console.log( 'promise2' ) ,后面一个.then不会有任何打印,但是会执行 - 执行后面的代码, 打印
console.log( 'async1 end' ) - 进入第二次事件循环,执行宏任务队列, 打印
console.log( 'setTimeout' )
|