MST

星途 面试题库

面试题:JavaScript异步编程中微任务和宏任务队列与Promise、Async/Await的交互机制

详细说明JavaScript事件循环中微任务队列和宏任务队列的工作原理,以及Promise和Async/Await在这两个队列中的执行顺序和交互机制。给出一段复杂的包含多个Promise、Async/Await函数调用,以及定时器等异步操作的代码,分析其执行过程和最终输出结果,并解释原因。
35.5万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

JavaScript事件循环中微任务队列和宏任务队列的工作原理

  1. 宏任务队列(Macro - Task Queue)
    • 宏任务是由宿主环境提供的异步任务,常见的宏任务有setTimeoutsetIntervalsetImmediate(仅在Node.js环境中有)、I/O操作、UI渲染等。
    • 事件循环开始时,首先会从宏任务队列中取出一个宏任务执行。在执行这个宏任务的过程中,可能会产生新的宏任务或微任务,新产生的宏任务会被放入宏任务队列的末尾,新产生的微任务会被放入微任务队列的末尾。
  2. 微任务队列(Micro - Task Queue)
    • 微任务是由JavaScript语言层面提供的异步任务,常见的微任务有Promise.then.catch.finally本质上也是.then的变体)、MutationObserverprocess.nextTick(仅在Node.js环境中有)。
    • 当一个宏任务执行完毕后,事件循环会检查微任务队列,如果微任务队列中有任务,就会依次执行微任务队列中的所有任务,直到微任务队列为空,然后再从宏任务队列中取出下一个宏任务执行。

Promise和Async/Await在这两个队列中的执行顺序和交互机制

  1. Promise
    • Promise的构造函数是同步执行的,而Promise.then回调函数是异步的,会被放入微任务队列。例如:
    console.log('start');
    const p = new Promise((resolve) => {
        console.log('Promise constructor');
        resolve();
    });
    p.then(() => {
        console.log('Promise.then');
    });
    console.log('end');
    
    • 输出结果为:startPromise constructorendPromise.then。这是因为Promise构造函数同步执行,then回调放入微任务队列,等同步代码执行完后,事件循环会处理微任务队列中的Promise.then回调。
  2. Async/Await
    • async函数返回一个Promiseawait关键字只能在async函数内部使用,它会暂停async函数的执行,等待Promise被解决(resolved)或被拒绝(rejected)。当await后的Promise被解决时,await表达式的值就是该Promise的解决值。并且await后的代码会被包装成一个微任务。例如:
    async function asyncFunc() {
        console.log('asyncFunc start');
        await Promise.resolve();
        console.log('asyncFunc after await');
    }
    asyncFunc();
    console.log('global');
    
    • 输出结果为:asyncFunc startglobalasyncFunc after await。这是因为await Promise.resolve()会暂停asyncFunc执行,将await后的代码放入微任务队列,然后继续执行同步代码console.log('global'),最后事件循环处理微任务队列,执行asyncFuncawait后的代码。

复杂代码示例及分析

console.log('1');
setTimeout(() => {
    console.log('2');
    Promise.resolve().then(() => {
        console.log('3');
    });
    setTimeout(() => {
        console.log('4');
    }, 0);
}, 0);
async function asyncFunc() {
    console.log('5');
    await Promise.resolve();
    console.log('6');
}
asyncFunc();
Promise.resolve().then(() => {
    console.log('7');
});
console.log('8');
  1. 执行过程和输出结果
    • 首先,同步代码执行,输出158
    • 然后,asyncFunc函数中await Promise.resolve()console.log('6')包装成微任务放入微任务队列,Promise.resolve().then(() => { console.log('7'); })也将回调放入微任务队列。
    • 接着,宏任务队列中有setTimeout(() => { console.log('2'); Promise.resolve().then(() => { console.log('3'); }); setTimeout(() => { console.log('4'); }, 0); }, 0);这个宏任务,开始执行。输出2Promise.resolve().then(() => { console.log('3'); })将回调放入微任务队列,setTimeout(() => { console.log('4'); }, 0);将这个新的宏任务放入宏任务队列。
    • 此时宏任务执行完毕,开始处理微任务队列,依次输出763
    • 微任务队列处理完毕,从宏任务队列中取出新的setTimeout(() => { console.log('4'); }, 0);宏任务执行,输出4
    • 最终输出结果为:15827634
  2. 原因解释
    • 同步代码按顺序执行。
    • async/awaitPromise.then产生的微任务在宏任务执行完毕后按顺序执行。
    • 宏任务队列中的任务按顺序执行,每个宏任务执行过程中产生的新宏任务放入宏任务队列末尾,新微任务放入微任务队列末尾。