如何确保调用addVertex的异步forEach循环执行完成后再执行后续循环?
嘿,这个问题我太熟了——forEach和异步函数搭配的时候很容易踩坑!你现在的代码里,外层的forEach根本不会等待内部的异步操作完成,所以所有的addVertex和addRelation会几乎同时启动,函数甚至会在这些异步任务结束前就返回jsonFile,这肯定不是你想要的效果。
下面给你几种不同场景的解决方案,你可以根据需求选:
场景1:完全串行执行(最严格的顺序)
如果你希望处理完一个annotation的所有entities,再处理它的relations,全部完成后才处理下一个annotation,用for...of循环替代forEach是最稳妥的方式,因为for...of会等待内部的await执行完成:
async insertKpbDocument(jsonFile) { // 遍历每个annotation,确保前一个处理完再进下一个 for (const annotation of jsonFile.doc.annotations) { // 先逐个处理当前annotation的entities,串行执行 for (const entity of annotation.entities) { await this.addVertex(entity); } // 等当前annotation的entities全部处理完,再逐个处理relations for (const relation of annotation.relations) { await this.addRelation(relation); } } return jsonFile; }
场景2:同annotation内并行,跨annotation串行
如果允许同一个annotation里的所有entities并行执行,全部完成后再并行处理这个annotation的relations,之后再处理下一个annotation,可以用Promise.all来批量处理异步任务,效率会更高:
async insertKpbDocument(jsonFile) { for (const annotation of jsonFile.doc.annotations) { // 并行启动当前annotation的所有addVertex任务,等全部完成 await Promise.all(annotation.entities.map(entity => this.addVertex(entity))); // 等entities全部处理完,再并行启动当前annotation的所有addRelation任务 await Promise.all(annotation.relations.map(relation => this.addRelation(relation))); } return jsonFile; }
场景3:所有entities先并行,再所有relations并行
如果你的业务允许先把所有annotation的entities全部并行处理完,再统一处理所有relations,这种方式效率最高,但顺序上是所有entities先完成,再所有relations:
async insertKpbDocument(jsonFile) { // 收集所有annotation的entities任务,并行执行 await Promise.all(jsonFile.doc.annotations.flatMap(annotation => annotation.entities.map(entity => this.addVertex(entity)) )); // 等所有entities完成,再收集所有relations任务并行执行 await Promise.all(jsonFile.doc.annotations.flatMap(annotation => annotation.relations.map(relation => this.addRelation(relation)) )); return jsonFile; }
为什么原来的代码不生效?
简单说:forEach的回调是异步函数时,forEach本身不会等待这个回调返回的Promise完成,它会立刻调用下一个元素的回调。所以你的代码里,所有的await this.addVertex(entity)和await this.addRelation(relation)都是“飘在空中”的异步任务,外层函数不会等它们完成就直接返回了。
内容的提问来源于stack exchange,提问作者Baptiste Arnaud

