await
关键字是异步编程中用于等待 Promise 对象解决的一种机制。其作用是暂停异步函数的执行,直到 Promise 对象变为已完成状态(即成功解决或失败拒绝),才会继续往下执行。这样可以避免异步代码执行时发生出错或异常情况,让程序具有更好的稳定性。
具体来说,当遇到 await
关键字时,JavaScript 引擎会将后面的操作封装为一个 Promise 对象,并将整个异步函数的执行权交还给宿主环境。然后引擎会去执行其他的 JavaScript 代码,直到 Promise 对象被解决。如果 Promise 对象被解决了,那么 JavaScript 引擎会恢复异步函数的执行,将 Promise 对象的 resolve()
或 reject()
的返回值作为 await
表达式的结果,并继续往下执行异步函数后面的代码。
由于 await
关键字在 Promise 对象解决前会一直等待,因此可以保证异步函数后面的代码在 Promise 对象解决后才被执行。这样就可以保证代码的顺序性,避免异步操作的返回值未知而导致程序出现错误行为。
从事件循环的角度上分析,await
关键字会将其后面的 Promise 对象加入到当前微任务队列中,在当前宏任务执行完毕后,JavaScript 引擎会优先执行微任务队列中的所有任务,然后才会去执行下一个宏任务。因此,当遇到 await
关键字时,JavaScript 引擎会暂停当前宏任务的执行,等到 Promise 对象被成功解决为止,然后再去执行余下的代码。
具体来说,当暂停异步函数的执行时,JavaScript 引擎会将该函数添加到正在执行的宏任务队列中,并将异步函数内部的微任务队列清空。接下来,如果异步函数中遇到了 await
关键字,JavaScript 引擎会先将 await
关键字后面的 Promise 对象加入到微任务队列中,然后交还执行权。
执行权交还给宿主环境,宿主环境会在后面的一段时间内处理宏任务队列中的任务。如果异步函数中所有同步任务都已执行完毕,而 Promise 对象还没有被解决,那么 JavaScript 引擎就会等待 Promise 对象解决,并在 Promise 对象被成功解决后将该 Promise 对象加入到微任务队列中。执行到微任务队列时,该任务才会被执行,即调用 resolve()
或 reject()
方法后将异步函数后面的代码加入到微任务队列中。
总之,await
关键字使得 JavaScript 引擎可以将宏任务任务拆分成微任务来执行,避免了一些执行上下文的切换,提高了执行效率,同时还可以保证异步函数中后面的代码在前面的 Promise 对象解决后才被执行。
假设我们需要模拟一个异步操作,它会在一段时间后返回一个随机数,并输出一条日志消息:
async function simulateAsync() {
console.log('Start simulateAsync function');
const randomDelay = Math.random() * 5000; // 随机生成 0~5000ms 的延迟时间
await new Promise(resolve => setTimeout(resolve, randomDelay));
const randomNumber = Math.floor(Math.random() * 100);
console.log(`Random number generated: ${randomNumber}`);
console.log('End simulateAsyn function');
}
console.log('Before calling simulateAsync function');
simulateAsync();
console.log('After calling simulateAsync function');
我们定义了一个名为 simulateAsync
的异步函数,该函数会随机生成一个延迟时间(以毫秒为单位),在这个延迟时间过后输出一个随机数和日志消息。然后我们在外部代码中,通过 console.log
打印出一系列日志消息,然后调用 simulateAsync
函数。我们来分析一下这段代码的执行顺序:
- 当外部代码执行时,首先会打印一条日志消息
Before calling simulateAsync function
,然后调用simulateAsync
函数。 - 在函数内部,首先会输出一条日志消息
Start simulateAsync function
,表明函数已经开始执行。 - 接着,我们使用了
await
关键字等待一个setTimeout
方法返回的 Promise 对象。该方法会在随机生成的时间后,将 Promise 对象的状态设置为已解决(resolved)。 - 在等待期间,由于
simulateAsync
函数已经暂停执行,JavaScript 引擎会将控制权交还宿主环境,以便宿主环境能执行其他任务。 - 如果在等待期间,宿主环境需要执行其他的异步任务,则引擎会将它们添加到微任务队列或宏任务队列中,等待异步任务完成后,再将其处理掉。但在这个例子中,我们没有添加其他的异步任务。
- 当 Promise 对象被解决时,JavaScript 引擎会将
console.log
的两条日志消息添加到微任务队列中,并在下一个宏任务执行前,按顺序将其执行。 - 在打印消息
Random number generated
后,我们又输出了一条日志消息End simulateAsync function
,表明函数已经执行完毕。 - 因此,当所有异步任务都被处理完毕后,JavaScript 引擎会顺序执行微任务,输出消息
Random number generated: xx
和End simulateAsync function
,然后执行下一个宏任务。 - 于是,最终会输出以下日志消息:
Before calling simulateAsync function
Start simulateAsync function
After calling simulateAsync function
Random number generated: xx
End simulateAsync function
从上面的例子中,我们可以看到,await
关键字的作用在于将异步操作转换为一个 Promise 对象,然后让 JavaScript 引擎能够在其完成之后,再去执行后面的代码。同时,Promise 的状态改变(即 resolve()
或 reject()
被调用)时,JavaScript 引擎会将相应的处理代码添加到微任务队列中,以便在下一个宏任务执行前按顺序执行。这一过程正是事件循环的核心特性之一。