在 JavaScript 中,在循环中使用 await 会阻塞循环吗?

IT技术 javascript async-await
2021-02-21 23:14:30

采取以下循环:

for(var i=0; i<100; ++i){
    let result = await some_slow_async_function();
    do_something_with_result();
}
  1. 是否await阻塞循环?还是iawaiting 时继续增加?

  2. do_something_with_result()关于 的顺序是否有保证i还是取决于await每个 ed 函数的速度i

6个回答
  1. 是否await阻塞循环?还是iawaiting 时继续增加?

“阻止”不是正确的词,但是是的,在等待时i不会继续递增。相反,执行会跳回到async函数被调用的地方,提供一个 promise 作为返回值,继续执行函数调用之后的其余代码,直到代码堆栈被清空。然后当等待结束时,恢复函数的状态,并在该函数内继续执行。每当该函数返回(完成)时,相应的Promise——之前返回的——就会被解析。

  1. do_something_with_result()关于 的顺序是否有保证i还是取决于await每个 ed 函数的速度i

订单是有保证的。后面的代码await也保证只有在调用堆栈被清空后才执行,即至少在下一个微任务可以执行时或之后执行。

查看此代码段中的输出如何。特别注意它说“在调用测试之后”:

async function test() {
    for (let i = 0; i < 2; i++) {
        console.log('Before await for ', i);
        let result = await Promise.resolve(i);
        console.log('After await. Value is ', result);
    }
}

test().then(_ => console.log('After test() resolved'));

console.log('After calling test');

@smorgs 堆栈解释实际上更多地与标准Promisethen行为有关,而不是与生成器有关。只有“跳回它被调用的地方”和“函数的状态已恢复”是yield.
2021-04-20 23:14:30
明白了;我发现栈模型清楚地看到.then,但不是那么有await,所以它的思想为yield解决我的困惑。
2021-04-22 23:14:30
堆栈解释提醒我,async/await直到最近才使用 generators/ 实现yield这么一想,一切就更清楚了。我会接受这个答案。
2021-05-03 23:14:30

正如@realbart 所说,它确实阻塞了循环,然后使调用顺序进行。

如果你想触发大量等待操作,然后一起处理它们,你可以这样做:

const promisesToAwait = [];
for (let i = 0; i < 100; i++) {
  promisesToAwait.push(fetchDataForId(i));
}
const responses = await Promise.all(promisesToAwait);
这个答案实际上并没有回答这个问题
2021-04-22 23:14:30
它不会阻塞循环,但也不会按预期执行。
2021-04-24 23:14:30
这没有任何意义。你的promisesToAwait数组永远不会包含Promise。
2021-05-08 23:14:30

您可以像这样在“FOR LOOP”中测试异步/等待:

(async  () => {
        for (let i = 0; i < 100; i++) {
                await delay();
                console.log(i);
        }
})();

function delay() {
        return new Promise((resolve, reject) => {
                setTimeout(resolve, 100);
        });
}

async函数返回一个 Promise,它是一个最终将“解析”为一个值或“拒绝”并带有错误的对象。await关键字表示要等到这个值(或错误)已经完成。

所以从运行函数的角度来看,它阻塞等待慢 async 函数的结果另一方面,javascript引擎看到这个函数被阻塞等待结果,所以它会去检查事件循环(即新的鼠标点击,或连接请求等),看看是否还有其他事情它可以继续工作,直到返回结果。

但是请注意,如果慢速异步函数很慢,因为它正在计算您的 javascript 代码中的大量内容,则 javascript 引擎将没有大量资源来执行其他操作(并且通过执行其他操作可能会使慢速异步函数甚至更慢)。异步函数的真正优势在于 I/O 密集型操作,例如查询数据库或传输大文件,其中 javascript 引擎真正地等待其他东西(即数据库、文件系统等)。

以下两位代码在功能上是等效的:

let result = await some_slow_async_function();

let promise = some_slow_async_function(); // start the slow async function
// you could do other stuff here while the slow async function is running
let result = await promise; // wait for the final value from the slow async function

在上面的第二个示例中,在没有await关键字的情况下调用了慢速异步函数,因此它将开始执行该函数并返回一个Promise。然后你可以做其他事情(如果你有其他事情要做)。然后await关键字用于阻塞,直到Promise实际“解决”。所以从for循环的角度来看,它将同步运行。

所以:

  1. 是的,该await关键字具有阻塞正在运行的函数的作用,直到异步函数用值“解析”或“拒绝”出现错误为止,但它不会阻塞 javascript 引擎,如果它有其他事情,它仍然可以做其他事情等待时要做的事情

  2. 是的,循环的执行将是顺序的

http://javascript.info/async 上有一个关于所有这些的很棒的教程

“看到这个函数被阻塞等待结果,所以它会去检查事件循环”:不,这是对发生的事情的误解。该函数实际上发生时返回await,并且在函数调用后继续执行代码。
2021-04-17 23:14:30
您还写道“因此它会检查事件循环(即新的鼠标点击或连接请求等)”这不是真的。(1) 代码继续执行而不考虑事件循环,以及 (2) 即使有鼠标点击等事件,它们也没有优先于处于不同队列(“Promise Job Queue”)中的 Promise 作业。Promise解决方案(将恢复 处的函数上下文await)优先于鼠标点击和其他代理驱动的事件。
2021-05-01 23:14:30
提到 Node“将检查事件循环”是一种误导。遇到时,节点不会检查事件循环await函数将返回,代码将继续执行,就像在任何函数调用之后一样事件循环仅在调用堆栈为空时起作用,而在await遇到不然顺便说一句:这个问题不是专门关于 Node,而是关于 JavaScript。
2021-05-07 23:14:30
@trincot 细微差别特别是在函数调用后继续执行“哪个”代码。是 1) 异步函数中继续的代码,还是 2) 等待之后调用函数中的代码,或者 3) 任何其他进入事件循环的活动。从调用函数的角度看它似乎被阻塞了(等待promise解决),从异步函数的角度看它正常运行(但可能在等待外部I/O什么的),从Node 它将检查事件循环以查看是否还有其他事情要做。
2021-05-08 23:14:30
@trincot 这也是我说“具有阻塞运行功能的‘效果’”的原因。它并没有真正被阻塞,Node 会从事件循环中做其他事情(以及通过执行异步函数继续工作),但是从调用函数的角度(不是被调用的异步函数)它将“似乎”被阻止。
2021-05-12 23:14:30

这是我关于这个有趣问题的测试解决方案:

import crypto from "crypto";

function diyCrypto() {
    return new Promise((resolve, reject) => {
        crypto.pbkdf2('secret', 'salt', 2000000, 64, 'sha512', (err, res) => {
            if (err) {
                reject(err)
                return 
            }
            resolve(res.toString("base64"))
        })
    })
}

setTimeout(async () => {
    console.log("before await...")
    const a = await diyCrypto();
    console.log("after await...", a)
}, 0);

setInterval(() => {
    console.log("test....")
}, 200);

在 setTimeout 的回调中,会await阻止执行。但是setInterval一直在运行,所以事件循环照常运行。