完成所有异步forEach回调后的回调

Dan*_*son 221 javascript asynchronous callback node.js

正如标题所示.我该怎么做呢?

我想whenAllDone()在forEach-loop遍历每个元素并完成一些异步处理之后调用.

[1, 2, 3].forEach(
  function(item, index, array, done) {
     asyncFunction(item, function itemDone() {
       console.log(item + " done");
       done();
     });
  }, function allDone() {
     console.log("All done");
     whenAllDone();
  }
);
Run Code Online (Sandbox Code Playgroud)

有可能让它像这样工作吗?当forEach的第二个参数是一个回调函数,它在经过所有迭代后运行?

预期产量:

3 done
1 done
2 done
All done!
Run Code Online (Sandbox Code Playgroud)

Nic*_*lin 369

Array.forEach 没有提供这种精确性(哦,如果它会)但有几种方法来实现你想要的:

使用简单的计数器

function callback () { console.log('all done'); }

var itemsProcessed = 0;

[1, 2, 3].forEach((item, index, array) => {
  asyncFunction(item, () => {
    itemsProcessed++;
    if(itemsProcessed === array.length) {
      callback();
    }
  });
});
Run Code Online (Sandbox Code Playgroud)

(感谢@vanuan和其他人)这种方法保证在调用"完成"回调之前处理所有项目.您需要使用在回调中更新的计数器.取决于索引参数的值不提供相同的保证,因为不保证异步操作的返回顺序.

使用ES6承诺

(promise库可用于旧版浏览器):

  1. 处理保证同步执行的所有请求(例如1然后2然后3)

    function asyncFunction (item, cb) {
      setTimeout(() => {
        console.log('done with', item);
        cb();
      }, 100);
    }
    
    let requests = [1, 2, 3].reduce((promiseChain, item) => {
        return promiseChain.then(() => new Promise((resolve) => {
          asyncFunction(item, resolve);
        }));
    }, Promise.resolve());
    
    requests.then(() => console.log('done'))
    
    Run Code Online (Sandbox Code Playgroud)
  2. 处理所有异步请求而不执行"同步"(2可能比1更快完成)

    let requests = [1,2,3].map((item) => {
        return new Promise((resolve) => {
          asyncFunction(item, resolve);
        });
    })
    
    Promise.all(requests).then(() => console.log('done'));
    
    Run Code Online (Sandbox Code Playgroud)

使用异步库

还有其他异步库,async是最受欢迎的,它们提供了表达所需内容的机制.

编辑

问题的主体已被编辑,以删除以前的同步示例代码,所以我已更新我的答案澄清.原始示例使用同步代码来模拟异步行为,因此应用了以下内容:

array.forEach同步的,所以是res.write,这样你就可以简单地把你的回调后您的来电的foreach:

  posts.foreach(function(v, i) {
    res.write(v + ". index " + i);
  });

  res.end();
Run Code Online (Sandbox Code Playgroud)

  • 但请注意,如果forEach中存在异步内容(例如,您循环遍历URL数组并对其执行HTTP GET),则无法保证最后会调用res.end. (28认同)
  • @AminJafari因为异步调用可能无法按照它们注册的确切顺序解析(比如你正在呼叫服务器,它在第二次调用时稍微停止,但处理最后一次调用很好).最后一次异步调用可以在前一次调用之前解析.由于_all_回调必须解雇,无论他们解决的顺序如何,都要反击一个反击. (5认同)
  • 为什么不只是`if(index === array.length - 1)`并删除`itemsProcessed` (4认同)

Emi*_*uez 25

如果你遇到异步函数,并且你想确保在执行代码之前完成它的任务,我们总是可以使用回调功能.

例如:

var ctr = 0;
posts.forEach(function(element, index, array){
    asynchronous(function(data){
         ctr++; 
         if (ctr === array.length) {
             functionAfterForEach();
         }
    })
});
Run Code Online (Sandbox Code Playgroud)

注意:functionAfterForEach是在foreach任务完成后执行的函数.asynchronous是在foreach中执行的异步函数.

希望这可以帮助.

  • 这不起作用,因为异步请求的执行顺序未被存储.最后一个异步请求可以在其他请求之前结束,并在所有请求完成之前执行functionAfterForEach(). (7认同)
  • 是的,这是答案是完全错误的.如果我并行运行10次下载,则可以保证最后一次下载在其余下载之前完成,从而终止执行. (2认同)
  • 大家好,自从 ES6 的 Promises 和 Async/await 最新更新以来,最好利用 Promises 和 Async/await 功能。这个解决方案现在已经过时了。 (2认同)

Rsh*_*Rsh 14

很奇怪有多少错误答案给了异步案例!可以简单地显示检查索引不提供预期的行为:

// INCORRECT
var list = [4000, 2000];
list.forEach(function(l, index) {
    console.log(l + ' started ...');
    setTimeout(function() {
        console.log(index + ': ' + l);
    }, l);
});
Run Code Online (Sandbox Code Playgroud)

输出:

4000 started
2000 started
1: 2000
0: 4000
Run Code Online (Sandbox Code Playgroud)

如果我们检查index === array.length - 1,第一次迭代完成时将调用回调,而第一个元素仍然未决!

为了在不使用异步等外部库的情况下解决这个问题,我认为最好的办法是保存列表的长度,并在每次迭代后减少.由于只有一个线程,我们确信没有竞争条件的可能性.

var list = [4000, 2000];
var counter = list.length;
list.forEach(function(l, index) {
    console.log(l + ' started ...');
    setTimeout(function() {
        console.log(index + ': ' + l);
        counter -= 1;
        if ( counter === 0)
            // call your callback here
    }, l);
});
Run Code Online (Sandbox Code Playgroud)


小智 12

希望这能解决你的问题,当我需要执行forEach内部的异步任务时,我通常会使用它.

foo = [a,b,c,d];
waiting = foo.length;
foo.forEach(function(entry){
      doAsynchronousFunction(entry,finish) //call finish after each entry
}
function finish(){
      waiting--;
      if (waiting==0) {
          //do your Job intended to be done after forEach is completed
      } 
}
Run Code Online (Sandbox Code Playgroud)

function doAsynchronousFunction(entry,callback){
       //asynchronousjob with entry
       callback();
}
Run Code Online (Sandbox Code Playgroud)


Krz*_*bek 5

使用ES2018,您可以使用异步迭代器:

const asyncFunction = a => fetch(a);
const itemDone = a => console.log(a);

async function example() {
  const arrayOfFetchPromises = [1, 2, 3].map(asyncFunction);

  for await (const item of arrayOfFetchPromises) {
    itemDone(item);
  }

  console.log('All done');
}
Run Code Online (Sandbox Code Playgroud)

  • 在 Node v10 中可用 (2认同)