在JavaScript中,在循环内使用await是否会阻塞循环?


79

请执行以下循环:

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

  2. 有关的do_something_with_result()保证顺序是顺序的i吗?还是取决于await每个功能的ed功能有多快i


7
您真的尝试过了吗?
Bergi

3
是的,我得到一个顺序的结果。我不确定这是否是巧合(异步功能实际上是很快的)。我不确定是否将所有异步函数调用都放入一个数组,然后执行单个操作await Promise.all(arr),或者这种形式是否正确以及是否有其他原因妨碍了所需的异步性。如果我await对所有这些都执行一次操作,那么我就必须Promise.map处理所有这些。这使我怀疑.then在这种情况下是否比异步/等待更好。
smorgs

JS是确定性的。函数是异步的还是不是异步的,它都不依赖于执行某事物的“速度”。关于Promise.all,这样做有什么不同-它是否仍然正确(甚至更理想)取决于您的要求。
Bergi

当执行async涉及外部资源(例如数据库,文件I / O)的操作时,函数运行的时间是不确定的。
smorgs

2
是的,但是只要它是异步的,它就永远不会立即调用其回调,并且始终先运行到完成同步代码。
Bergi

Answers:


76
  1. await阻塞循环吗?还是ingi期间继续await增加?

“阻止”不是正确的词,但是是的,在等待时不会继续递增。取而代之的是执行跳回到async调用该函数的位置,提供一个诺言作为返回值,继续执行该函数调用之后的其余代码,直到清空了代码堆栈。然后,等待结束后,功能状态将恢复,并在该功能内继续执行。只要该函数返回(完成),就会解决相应的诺言-早先返回的诺言。

  1. 有关的do_something_with_result()保证顺序是顺序的i吗?还是取决于await每个功能的ed功能有多快i

订单得到保证。的代码之后的await也保证调用堆栈已被清空之后才执行,即至少上或之后的下一个microtask可以执行。

查看此代码段中的输出。请特别注意它说“调用测试后”的地方:

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');


堆栈说明让我想起了async/await直到最近才使用generators /实现的yield。以这种方式思考使一切变得更加清晰。我会接受这个答案。
smorgs

1
@smorgs堆栈解释实际上与标准的Promisethen行为有关,而不是与生成器有关。仅“跳回调用位置”和“功能状态已还原”是yield
Bergi

理解 我发现可以轻松地看到堆栈模型,.then但不能轻松地看到堆栈模型await,因此将其视为yield解决了我的困惑。
smorgs

13

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

如果要触发大量等待的操作,然后一起处理所有操作,则可以执行以下操作:

const promisesToAwait = [];
for (let i = 0; i < 100; i++) {
  promisesToAwait.push(fetchDataForId(i));
}
const responses = await Promise.all(promisesToAwait);

3
这个答案实际上并没有回答问题
Liam

这是没有道理的。您的promisesToAwait数组将永远不会包含承诺。
Bergi

1
它不会阻塞循环,但不会按预期执行。
艾伦·费利佩·穆拉拉

11

您可以在“ 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);
        });
}


0

没有事件循环未被阻止,请参见下面的示例

function sayHelloAfterSomeTime (ms) {
  return new Promise((resolve, reject) => {
    if (typeof ms !== 'number') return reject('ms must be a number')
    setTimeout(() => { 
      console.log('Hello after '+ ms / 1000 + ' second(s)')
      resolve()  
    }, ms)
  })
}

async function awaitGo (ms) {
   await sayHelloAfterSomeTime(ms).catch(e => console.log(e))
   console.log('after awaiting for saying Hello, i can do another things  ...')
}

function notAwaitGo (ms) {
	sayHelloAfterSomeTime(ms).catch(e => console.log(e))
    console.log('i dont wait for saying Hello ...')
}

awaitGo(1000)
notAwaitGo(1000)
console.log('coucou i am event loop and i am not blocked ...')


0

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

因此,从运行功能的角度来看,它阻止等待慢速异步功能的结果。另一方面,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使用该关键字进行阻止,直到承诺实际“解决”为止。因此,从for循环的角度来看,它将同步运行。

所以:

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

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

http://javascript.info/async上有一个很棒的教程。


“看到该函数被阻止等待结果,因此它将去检查事件循环”:不,这是对所发生情况的错误表示。当发生时,该函数实际上返回await,并且在函数调用之后代码执行继续。
特里科特

@trincot的细微差别特别是在函数调用后继续执行“哪个”代码。是1)继续执行的异步函数内的代码,还是2)等待后调用函数中的代码,还是3)入队到事件循环中的任何其他活动。从调用函数的角度来看,它似乎已被阻止(等待承诺的解决),从异步功能的角度来看,它正在正常运行(但可能正在等待外部I / O或某些东西),并且从节点它将检查事件循环,以查看是否还有其他事情要做。
Casey

@trincot这就是为什么我说“具有阻止运行功能的'效果'”的原因。它并没有真正被阻止,Node将从事件循环(以及通过执行异步功能继续进行工作)中做其他事情,但是从调用函数的角度(不是被调用的异步函数)进行它会“显示”为被阻止。
Casey

提到Node“将检查事件循环”是令人误解的。遇到问题时,节点将检查事件循环await。该函数将返回,并且代码执行将继续,就像在任何函数调用之后一样。事件循环仅在调用堆栈为空时才起作用,而在await遇到堆栈时则不起作用。顺便说一句:这个问题不是专门针对Node的,而是JavaScript。
特里科特

您还写道:“因此它将去检查事件循环(即,单击新鼠标或连接请求等)”。这不是真的。(1)代码执行将继续进行,而不考虑事件循环;(2)即使发生诸如鼠标单击之类的事件,它们也不会优先于位于不同队列(“承诺作业队列”)中的承诺作业。承诺解析(将在处还原功能上下文await)的优先级高于鼠标单击和其他代理驱动的事件。
特里科特

0

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

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保持运行状态,因此事件循环照常运行。


-3

等待阻塞循环了吗?还是i等待期间继续增加?

不,等待不会阻止循环。是的,i循环时继续增加。

是否确保do_something_with_result()的顺序是顺序的i?还是取决于每个函数的等待速度i

的顺序do_something_with_result()是顺序保证的,但与无关i。这取决于等待的功能运行的速度。

所有对tosome_slow_async_function()的调用都是批量的,即如果do_something_with_result()为a,console那么我们将看到它打印了循环运行的次数。然后依次执行,此后,将执行所有等待调用。

为了更好地理解,您可以在下面的代码段中运行:

async function someFunction(){
for (let i=0;i<5;i++){
 await callAPI();
 console.log('After', i, 'th API call');
}
console.log("All API got executed");
}

function callAPI(){
setTimeout(()=>{
console.log("I was called at: "+new Date().getTime())}, 1000);
}

someFunction();

可以清楚地看到如何console.log('After', i, 'th API call');在for循环的整个过程中首先打印行,然后在执行所有代码时结束时从中得到结果callAPI()

因此,如果等待后的行取决于从等待调用获得的结果,则它们将无法按预期工作。

总而言之,awaitinfor-loop不能确保成功处理从await调用获得的结果,这可能需要一些时间才能完成。

在节点中,如果将一个neo-async库与一起使用waterfall,则可以实现这一点。


3
这是因为您的callAPI函数没有返回promise,所以请await callAPI()立即返回。如果您更改callAPI()以退还诺言,那么它将起作用。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.