当循环在异步函数内部而不是相反时,为啥 Async/Await 可以正常工作?
Posted
技术标签:
【中文标题】当循环在异步函数内部而不是相反时,为啥 Async/Await 可以正常工作?【英文标题】:Why does Async/Await work properly when the loop is inside the async function and not the other way around?当循环在异步函数内部而不是相反时,为什么 Async/Await 可以正常工作? 【发布时间】:2019-08-14 20:38:24 【问题描述】:当await
在promise
上时,我有三个循环三遍的sn-ps。
在第一个 sn-p 中,它按我的预期工作,i
的值随着每个await
而递减。
let i = 3;
(async () =>
while (i)
await Promise.resolve();
console.log(i);
i--;
)();
输出:
3
2
1
在第二个中,i
的值不断递减直到为零,然后所有await
s都被执行。
let i = 3;
while (i)
(async () =>
await Promise.resolve();
console.log(i);
)();
i--;
输出:
0
0
0
最后,这会导致Allocation failed - javascript heap out of memory
错误并且不打印任何值。
let i = 3;
while (i)
(async () =>
await Promise.resolve();
console.log(i);
i--;
)();
谁能解释他们为什么表现出这些不同的行为?谢谢。
【问题讨论】:
你知道async function
s 是如何脱糖的吗?
@Bergi 这只是处理承诺的语法糖,而不是使用.then()
和.catch()
方法。我说的对吗?
是的,基本上。那么如果使用then()
编写,您认为您的代码会是什么样子?
对于第二个 sn-p,它将是:while (i) Promise.resolve().then(res => console.log(i)); i--;
没错。现在看来很明显why this logs 0
three times。
【参考方案1】:
关于你的第二个 sn-p:
在不等待结果的情况下调用异步函数称为即发即弃。您告诉 JavaScript 它应该开始一些异步处理,但您并不关心它何时以及如何完成。这就是发生的事情。它循环,触发一些异步任务,当循环完成时,它们会在某个时候完成,并且在循环已经结束时将记录 0。如果你愿意:
await (async () =>
await Promise.resolve();
console.log(i);
)();
它会按顺序循环。
关于你的第三个 sn-p:
你永远不会在循环中减少i
,因此循环会永远运行。如果异步任务在某个时候执行,它将递减 i
,但这不会发生,因为 while 循环疯狂运行并阻塞和崩溃浏览器。
let i = 3;
while(i > 0)
doStuff();
【讨论】:
【参考方案2】:在您的特定示例中,它递减 i
,然后运行 async
代码,如:
let i = 3;
while (i)
i--; // <---------------------
(async () => // |
await Promise.resolve();// |
console.log(i); // |
)(); // |
// >---------------------------
关于你的第三个 sn-p,它永远不会减少 i
值,因此循环会永远运行,从而导致应用程序崩溃:
let i = 3;
while (i)
(async () =>
await Promise.resolve(); // await and resolve >-----------
// the following code doesn't run after it resolves // |
console.log(i); // |
i--; // |
)(); // |
// out from the (async() => )() <-------------------------
【讨论】:
并非如此,如果您删除await Promise.resolve
它将正确记录。
@Bhojendra Rauniyar 你能检查一下这第三个sn-p吗?
@JonasWilms 你的意思是说,如果我们有一个async
函数,而它的主体中没有await
,那么它将像任何其他函数一样阻塞并同步运行?
the asynchronous code runs in background
这是不正确的。异步代码被延迟,并且由于事件循环正忙于无限循环,异步代码永远不会运行。如果它在后台,输出数字将是随机的,但 i
最终将等于零。
@BhojendraRauniyar JavaScript 是单线程的,所以没有“背景”。一次只能运行一段代码。我在这里模糊了一些细节,但是有一个事件循环可以将挂起的回调出列并运行它直到完成。为了防止阻塞(例如等待计时器或网络请求),代码被分解成更小的回调,以便让竞争事件有机会在单线程中运行。 await
只是“在我们等待的东西可用后安排回调运行”的语法糖。【参考方案3】:
因为在第一种情况下,console.log 和 decrement 彼此同步工作,因为它们都在同一个异步函数中。
在第二种情况下,console.log 异步工作,递减同步工作。
所以先递减,异步函数等待同步函数执行完毕,再用i == 0
执行
在第三种情况下,循环体同步执行,并在每次迭代时运行异步函数。因此,减量直到循环结束才能起作用,因此循环中的条件始终为真。直到堆栈或内存已满
【讨论】:
【参考方案4】:主要关注最后一个例子:
let i = 3;
while (i)
(async () =>
await Promise.resolve();
console.log(i);
i--;
)();
如果我们在不使用 async/await 的情况下重写代码以揭示它真正在做什么,这可能会有所帮助。在后台,异步函数的代码执行被推迟到以后:
let callbacks = [];
let i = 0;
while (i > 0)
callbacks.push(() =>
console.log(i);
i--;
);
callbacks.forEach(cb =>
cb();
);
如您所见,在循环完成之前不会执行任何回调。由于循环永远不会停止,最终 vm 将耗尽存储回调的空间。
【讨论】:
以上是关于当循环在异步函数内部而不是相反时,为啥 Async/Await 可以正常工作?的主要内容,如果未能解决你的问题,请参考以下文章