确定异步循环的结束

determining the end of an asynchronous loop

本文关键字:结束 循环 异步      更新时间:2023-09-26

在所附的代码中,我希望在运行完所有数据库查询后运行函数returnFile,但问题是我无法从查询响应中判断出哪个响应将是最后一个响应,所以我想的是将循环分开,只让最后一个回调运行returnFile函数,但这会大大降低速度。

for (var i = 0, len = articleRevisionData.length; i < len; i++) {
    tagNames=[]
    console.log("step 1, "+articleRevisionData.length+" i:"+i);
    if(articleRevisionData[i]["tags"]){
        for (var x = 0, len2 = articleRevisionData[i]["tags"].length; x < len2; x++) {
            console.log("step 2, I: "+i+" x: "+x+articleRevisionData[i]["articleID"])
            tagData.find({"tagID":articleRevisionData[i]["tags"][x]}).toArray( function(iteration,len3,iterationA,error, resultC){
                console.log("step 3, I: "+i+" x: "+x+" iteration: "+iteration+" len3: "+len3)
                if(resultC.length>0){
                   tagNames.push(resultC[0]["tagName"]);
                }
                    //console.log("iteration: "+iteration+" len: "+len3)
               if(iteration+1==len3){
                    console.log("step 4, iterationA: "+iterationA+" I: "+iteration)
                    articleRevisionData[iterationA]["tags"]=tagNames.join(",");
               } 
            }.bind(tagData,x,len2,i));
        }
    }
    if(i==len-1){
        templateData={
            name:userData["firstName"]+" "+userData["lastName"],
            articleData:articleData,
            articleRevisionData:articleRevisionData
        }
        returnFile(res,"/usr/share/node/Admin/anonymousAttempt2/Admin/Articles/home.html",templateData);
    }
}

从循环中调用异步函数很少是个好主意,因为正如您所发现的,您无法知道所有调用何时完成(这是异步的本质)

在您的示例中,需要注意的是,所有异步调用都是并发运行的,这可能会消耗比您希望的更多的系统资源。

我发现,解决这类问题的最佳方案是使用事件来管理执行流,如:

const EventEmitter = require('events');
const emitter = new EventEmitter();
let iterations = articleRevisionData.length;
// start up state
emitter.on('start', () => {
  // do setup here
  emitter.emit('next_iteration');
});
// loop state
emitter.on('next_iteration', () => {
  if(iterations--) {
    asyncFunc(args, (err,result) => {
      if(err) {
        emitter.emit('error', err);
        return;
      }
      // do something with result 
      emitter.emit('next_iteration');
    });
    return;
  }
  // no more iterations
  emitter.emit('complete');
});
// error state
emitter.on('error', (e) => {
  console.error(`processing failed on iteration ${iterations+1}: ${e.toString()}`);
});
// processing complete state
emitter.on('complete', () => {
  // do something with all results
  console.log('all iterations complete');
});
// start processing
emitter.emit('start');

请注意,这段代码是多么简单和干净,没有任何"回调地狱",以及可视化程序流是多么容易。

还值得注意的是,您可以使用事件来表达每种类型的执行控制(doWhile、doUntil、map/reduce、队列工作者等),并且由于事件处理是Node的核心,因此您会发现以这种方式使用它们将优于大多数(如果不是所有的话)其他解决方案。

有关Node中事件处理的更多信息,请参见Node Events。