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

Bap*_*aud 3 javascript promise typescript

我有一个多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;
  }
Run Code Online (Sandbox Code Playgroud)

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

但是当我记录变量时,似乎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);
      });
    });
Run Code Online (Sandbox Code Playgroud)

但同样的行为.

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

jfr*_*d00 6

正如我们所讨论的,await不会暂停.forEach()循环并且不会使迭代的第二项等待第一项被处理。所以,如果你真的想对项目进行异步排序,你不能真正用.forEach()循环来完成它。

对于这种类型的问题,async/await使用普通for循环非常有效,因为它们确实会暂停实际for语句的执行,以便为您提供异步操作的顺序,这似乎是您想要的。另外,它甚至适用于嵌套for循环,因为它们都在同一个函数范围内:

为了向您展示使用for/ofand可以简单得多await,可以这样做:

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;
}
Run Code Online (Sandbox Code Playgroud)

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


如果您真的要避免任何for循环,并且您真正的要求只是所有调用addVertex()都在任何调用之前进行addRelation(),那么您可以在使用.map()而不是的地方执行此操作,然后.forEach()收集一系列承诺,然后用于Promise.all()等待整个过程一系列承诺:

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);
}
Run Code Online (Sandbox Code Playgroud)

为了完全理解这是如何工作的,这将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);
}
Run Code Online (Sandbox Code Playgroud)

这将完全排序一切。它将执行这种类型的订单:

addVertex(annotation1)
addRelation(relation1);
addVertex(annotation2)
addRelation(relation2);
....
addVertex(annotationN);
addRelation(relationN);
Run Code Online (Sandbox Code Playgroud)

在进入下一个操作之前,它等待每个操作完成。


Tit*_*mir 5

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;
}
Run Code Online (Sandbox Code Playgroud)