让我澄清一下,因为这里的一些答案有一些关于 Promise 执行如何工作的错误信息,特别是与事件循环相关时。
在本例中,await 将阻塞循环。 do_something_with_result() 不会被调用,直到 await 完成它的预定工作。
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await#handling_asyncawait_slowdown
至于其他点,Promise“jobs”在下一个事件循环周期之前运行,如microtasks。当您调用Promise.then() 或resolve() 内部的resolve() 函数时,您将创建一个Job。 await 和 async 都是 Promise 的包装器,它们都将创建一个 Job。微任务意味着在下一个事件循环周期之前运行。这意味着添加 Promise Job 意味着在进入下一个事件循环之前需要做更多的工作。
这里有一个示例,您可以如何锁定您的事件循环,因为您的承诺(作业)耗时太长。
let tick = 0;
let time = performance.now();
setTimeout(() => console.log('Hi from timeout'), 0);
const tock = () => console.log(tick++);
const longTask = async () => {
console.log('begin task');
for(let i = 0; i < 1_000_000_000; i++) {
Math.sqrt(i);
}
console.log('done task');
}
requestAnimationFrame(()=> console.log('next frame after', performance.now() - time, 'ms'));
async function run() {
await tock();
await tock();
await longTask(); // Will stall your UI
await tock(); // Will execute even though it's already dropped frames
await tock(); // This will execute too
}
run();
// Promise.resolve().then(tock).then(tock).then(longTask).then(tock).then(tock);
在这个示例中,总共创建了 5 个 Promise。 2 次调用 tock,1 次调用 longTask,然后 2 次调用 tock。所有 5 个都将在下一个事件循环之前运行。
执行将是:
- 开始执行JS
- 执行普通脚本
- 运行 5 个预定的 Promise 作业
- 结束 JS 执行
- 事件循环循环开始
- 请求动画帧触发
- 超时触发
最后一行注释行是没有async/await 的调度,结果相同。
基本上,除非你告诉你的 JS 执行它可以在哪里暂停,否则你将暂停下一个事件循环周期。您的 Promise 将继续在当前事件循环中运行,直到 它完成其调用堆栈。当你调用外部的东西(如fetch)时,它可能会使用让调用堆栈结束并有一个回调来解决待处理的 Promise。像这样:
function waitForClick() {
return new Promise((resolve) => {
// Use an event as a callback;
button.onclick = () => resolve();
// Let the call stack finish by implicitly not returning anything, or explicitly returning `undefined` (same thing).
// return undefined;
})
}
如果您有一个很长的作业要完成,请使用 Web Worker 运行它而不暂停,或者插入一些暂停,例如 setTimeout() 或 setImmediate()。
重塑longTask函数,你可以这样做:
const longTask = async () => {
console.log('begin task');
for(let i = 0; i < 1_000_000_000; i++)
if (i && i % (10_000_000) === 0) {
await new Promise((r) => setTimeout(r,0));
}
Math.sqrt(i);
console.log('done task');
}
基本上,与其一次性完成 10 亿条记录,不如只执行 1000 万条,然后等到下一个事件 (setTimeout) 再运行下一个。这里的坏处是它更慢,因为你有多少交回事件循环。相反,您可以使用 requestIdleCallback() 更好,但仍然不如通过 Web Worker 的多线程。
但请注意,仅在函数周围加上 await 或 Promise.resolve().then() 对事件循环没有帮助。两者都会等到函数返回一个 Promise 或一个值,然后才停止事件循环。您可以通过检查您正在调用的函数是否立即返回 unresolved Promise 来进行测试。