简体   繁体   English

确保在另一个之前执行带有异步调用的forEach?

[英]Make sure a forEach with async calls is executed before another one?

I have a function with multiple forEach loops: 我有一个带有多个forEach循环的函数:

async insertKpbDocument(jsonFile) {
    jsonFile.doc.annotations.forEach((annotation) => {
      annotation.entities.forEach(async (entity) => {
        await this.addVertex(entity);
      });
      annotation.relations.forEach(async (relation) => {
        await this.addRelation(relation);
      });
    });
    return jsonFile;
  }

I need to make sure that the async code in the forEach loop calling the this.addVertex function is really done before executing the next one. 我需要确保在执行下一个函数之前,调用this.addVertex函数的forEach循环中的异步代码确实已完成。

But when I log variables, It seems that the this.addRelation function is called before the first loop is really over. 但是当我记录变量时,似乎在第一个循环真正结束之前this.addRelation函数。

So I tried adding await terms before every loops like so : 所以我尝试在每个循环之前添加await术语,如下所示:

await jsonFile.doc.annotations.forEach(async (annotation) => {
      await annotation.entities.forEach(async (entity) => {
        await this.addVertex(entity);
      });
      await annotation.relations.forEach(async (relation) => {
        await this.addRelation(relation);
      });
    });

But same behavior. 但同样的行为。

Maybe it is the log function that have a latency? 也许是日志函数有延迟? Any ideas? 有任何想法吗?

foreach will return void so awaiting it will not do much. foreach将返回void ,等待它不会做太多。 You can use map to return all the promises you create now in the forEach , and use Promise.all to await all: 您可以使用map返回您在forEach创建的所有承诺,并使用Promise.all等待所有:

async insertKpbDocument(jsonFile: { doc: { annotations: Array<{ entities: Array<{}>, relations: Array<{}> }> } }) {
    await Promise.all(jsonFile.doc.annotations.map(async(annotation) => {
        await Promise.all(annotation.entities.map(async (entity) => {
            await this.addVertex(entity);
        }));
        await Promise.all(annotation.relations.map(async (relation) => {
            await this.addRelation(relation);
        }));
    }));
    return jsonFile;
}

As we've discussed, await does not pause a .forEach() loop and does not make the 2nd item of the iteration wait for the first item to be processed. 正如我们所讨论的那样, await不会暂停.forEach()循环,也不会使迭代的第二项等待处理第一项。 So, if you're really trying to do asynchronous sequencing of items, you can't really accomplish it with a .forEach() loop. 所以,如果你真的想要对项进行异步排序,那么你无法用.forEach()循环来实现它。

For this type of problem, async/await works really well with a plain for loop because they do pause the execution of the actual for statement to give you sequencing of asynchronous operations which it appears is what you want. 对于这种类型的问题, async/await非常适用于for循环,因为它们会暂停执行实际的for语句,以便为您提供所需的异步操作的顺序。 Plus, it even works with nested for loops because they are all in the same function scope: 此外,它甚至适用于嵌套for循环,因为它们都在相同的函数范围内:

To show you how much simpler this can be using for/of and await , it could be done like this: 为了向您展示这可以for/ofawait ,可以这样做:

async insertKpbDocument(jsonFile) {
    for (let annotation of jsonFile.doc.annotations) {
        for (let entity of annotation.entities) {
            await this.addVertex(entity);
        }
        for (let relation of annotation.relations) {
            await this.addRelation(relation);
        }
    }
    return jsonFile;
}

You get to write synchronous-like code that is actually sequencing asynchronous operations. 您可以编写类似于同步的代码,实际上是对异步操作进行排序。


If you are really avoiding any for loop, and your real requirement is only that all calls to addVertex() come before any calls to addRelation() , then you can do this where you use .map() instead of .forEach() and you collect an array of promises that you then use Promise.all() to wait on the whole array of promises: 如果您真的避免任何for循环,并且您的真正要求只是在调用addRelation()之前调用addVertex() addRelation() ,那么您可以在使用.map()而不是.forEach()执行此操作。你收集一系列的promises然后使用Promise.all()来等待整个promises数组:

insertKpbDocument(jsonFile) {
    return Promise.all(jsonFile.doc.annotations.map(async annotation => {
        await Promise.all(annotation.entities.map(entity => this.addVertex(entity)));
        await Promise.all(annotation.relations.map(relation => this.addRelation(relation)));
    })).then(() => jsonFile);
}

To fully understand how this works, this runs all addVertex() calls in parallel for one annotation, waits for them all to finish, then runs all the addRelation() calls in parallel for one annotation, then waits for them all to finish. 为了完全理解它是如何工作的,这将为一个注释并行运行所有addVertex()调用,等待它们全部完成,然后为一个注释并行运行所有addRelation()调用,然后等待它们全部完成。 It runs all the annotations themselves in parallel. 它并行运行所有注释。 So, this isn't very much actual sequencing except within an annotation, but you accepted an answer that has this same sequencing and said it works so I show a little simpler version of this for completeness. 因此,除了注释之外,这不是实际的排序,但你接受了一个具有相同排序并且说它有效的答案,所以为了完整性我展示了一个更简单的版本。


If you really need to sequence each individual addVertex() call so you don't call the next one until the previous one is done and you're still not going to use a for loop, then you can use the .reduce() promise pattern put into a helper function to manually sequence asynchronous access to an array: 如果你真的需要对每个单独的addVertex()调用进行排序,这样你就不会调用下一个调用,直到前一个完成并且你仍然不打算使用for循环,那么你可以使用.reduce()承诺模式放入辅助函数来手动排序对数组的异步访问:

// helper function to sequence asynchronous iteration of an array
// fn returns a promise and is passed an array item as an argument
function sequence(array, fn) {
    return array.reduce((p, item) => {
        return p.then(() => {
            return fn(item);
        });
    }, Promise.resolve());
}


insertKpbDocument(jsonFile) {
    return sequence(jsonFile.doc.annotations, async (annotation) => {
        await sequence(annotation.entities, entity => this.addVertex(entity));
        await sequence(annotation.relations, relation => this.addRelation(relation));
    }).then(() => jsonFile);
}

This will completely sequence everything. 这将完全排序一切。 It will do this type of order: 它会做这种类型的顺序:

addVertex(annotation1)
addRelation(relation1);
addVertex(annotation2)
addRelation(relation2);
....
addVertex(annotationN);
addRelation(relationN);

where it waits for each operation to finish before going onto the next one. 在进入下一个操作之前等待每个操作完成的地方。

I understand you can run all the addVertex concurrently. 我知道你可以同时运行所有的addVertex。 Combining reduce with map splitted into two different set of promises you can do it. 结合使用reduce和map分成两组不同的promises,你可以做到。 My idea: 我的点子:

const first = jsonFile.doc.annotations.reduce((acc, annotation) => {
  acc = acc.concat(annotation.entities.map(this.addVertex));

  return acc;
}, []);

await Promise.all(first);

const second = jsonFile.doc.annotations.reduce((acc, annotation) => {
  acc = acc.concat(annotation.relations.map(this.addRelation));

  return acc;
}, []);

await Promise.all(second);

You have more loops, but it does what you need I think 你有更多的循环,但它确实做你需要的

async/await does not within forEach . async/await不在forEach

A simple solution: Replace .forEach() with for(.. of ..) instead. 一个简单的解决方案:用for(.. of ..)替换.forEach()

Details in this similar question . 这个类似问题的细节。

If no-iterator linting rule is enabled, you will get a linting warning/error for using for(.. of ..) . 如果启用了no-iterator linting规则,则会出现使用for(.. of ..)的linting警告/错误。 There are lots of discussion/opinions on this topic. 关于这个主题有很多讨论/意见

IMHO, this is a scenario where we can suppress the warning with eslint-disable-next-line or for the method/class. 恕我直言,这是一个我们可以用eslint-disable-next-line或方法/类来抑制警告的场景。

Example: 例:

const insertKpbDocument = async (jsonFile) => {
  // eslint-disable-next-line no-iterator
  for (let entity of annotation.entities) {
    await this.addVertex(entity)
  }
  // eslint-disable-next-line no-iterator
  for (let relation of annotation.relations) {
    await this.addRelation(relation)
  }
  return jsonFile
}

The code is very readable and works as expected. 代码非常易读并且按预期工作。 To get similar functionality with .forEach() , we need some promises/observables acrobatics that i think is a waste of effort. 为了获得与.forEach()相似的功能,我们需要一些承诺/可观察的杂技,我认为这是浪费精力。

forEach executes the callback against each element in the array and does not wait for anything. forEach对数组中的每个元素执行回调,不等待任何事情。 Using await is basically sugar for writing promise.then() and nesting everything that follows in the then() callback. 使用await基本上是用于编写promise.then()函数,并在then()回调中嵌套后面的所有内容。 But forEach doesn't return a promise, so await arr.forEach() is meaningless. 但是, forEach不会返回一个承诺,所以await arr.forEach()是没有意义的。 The only reason it isn't a compile error is because the async/await spec says you can await anything, and if it isn't a promise you just get its value... forEach just gives you void . 它不是编译错误的唯一原因是因为async / await规范说你可以await任何东西,如果它不是一个承诺你只是得到它的值... forEach只是给你void

If you want something to happen in sequence you can await in a for loop: 如果你想要按顺序发生某些事情,你可以在for循环中await

for (let i = 0; i < jsonFile.doc.annotations.length; i++) {
  const annotation = jsonFile.doc.annotations[i]; 
  for (let j = 0; j < annotation.entities.length; j++) {
    const entity = annotation.entities[j];
    await this.addVertex(entity);
  });
  // code here executes after all vertix have been added in order

Edit: While typing this a couple other answers and comments happened... you don't want to use a for loop, you can use Promise.all but there's still maybe some confusion, so I'll leave the above explanation in case it helps. 编辑:输入这几个其他的答案和评论发生...你不想使用for循环,你可以使用Promise.all但仍然可能有一些混乱,所以我会留下上面的解释,以防万一帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM