繁体   English   中英

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

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

我有一个带有多个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;
  }

我需要确保在执行下一个函数之前,调用this.addVertex函数的forEach循环中的异步代码确实已完成。

但是当我记录变量时,似乎在第一个循环真正结束之前this.addRelation函数。

所以我尝试在每个循环之前添加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);
      });
    });

但同样的行为。

也许是日志函数有延迟? 有任何想法吗?

foreach将返回void ,等待它不会做太多。 您可以使用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;
}

正如我们所讨论的那样, await不会暂停.forEach()循环,也不会使迭代的第二项等待处理第一项。 所以,如果你真的想要对项进行异步排序,那么你无法用.forEach()循环来实现它。

对于这种类型的问题, async/await非常适用于for循环,因为它们会暂停执行实际的for语句,以便为您提供所需的异步操作的顺序。 此外,它甚至适用于嵌套for循环,因为它们都在相同的函数范围内:

为了向您展示这可以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;
}

您可以编写类似于同步的代码,实际上是对异步操作进行排序。


如果您真的避免任何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);
}

为了完全理解它是如何工作的,这将为一个注释并行运行所有addVertex()调用,等待它们全部完成,然后为一个注释并行运行所有addRelation()调用,然后等待它们全部完成。 它并行运行所有注释。 因此,除了注释之外,这不是实际的排序,但你接受了一个具有相同排序并且说它有效的答案,所以为了完整性我展示了一个更简单的版本。


如果你真的需要对每个单独的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);
}

这将完全排序一切。 它会做这种类型的顺序:

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

在进入下一个操作之前等待每个操作完成的地方。

我知道你可以同时运行所有的addVertex。 结合使用reduce和map分成两组不同的promises,你可以做到。 我的点子:

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

你有更多的循环,但它确实做你需要的

async/await不在forEach

一个简单的解决方案:用for(.. of ..)替换.forEach()

这个类似问题的细节。

如果启用了no-iterator linting规则,则会出现使用for(.. of ..)的linting警告/错误。 关于这个主题有很多讨论/意见

恕我直言,这是一个我们可以用eslint-disable-next-line或方法/类来抑制警告的场景。

例:

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
}

代码非常易读并且按预期工作。 为了获得与.forEach()相似的功能,我们需要一些承诺/可观察的杂技,我认为这是浪费精力。

forEach对数组中的每个元素执行回调,不等待任何事情。 使用await基本上是用于编写promise.then()函数,并在then()回调中嵌套后面的所有内容。 但是, forEach不会返回一个承诺,所以await arr.forEach()是没有意义的。 它不是编译错误的唯一原因是因为async / await规范说你可以await任何东西,如果它不是一个承诺你只是得到它的值... forEach只是给你void

如果你想要按顺序发生某些事情,你可以在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

编辑:输入这几个其他的答案和评论发生...你不想使用for循环,你可以使用Promise.all但仍然可能有一些混乱,所以我会留下上面的解释,以防万一帮助。

暂无
暂无

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

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