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

IT技术 javascript node.js asynchronous callback
2021-01-15 06:10:49

正如标题所暗示的那样。我该怎么做呢?

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

[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();
  }
);

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

预期输出:

3 done
1 done
2 done
All done!
6个回答

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

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

使用 ES6 Promise

(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'))
    
  2. 在没有“同步”执行的情况下处理所有异步请求(2 可能比 1 完成得更快)

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

使用异步库

还有其他异步库,async是最流行的,它们提供了表达您想要的内容的机制。

编辑

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

array.forEach同步的,所以同步的res.write,因此您可以在调用 foreach 之后简单地放置回调:

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

  res.end();
为了在循环中执行异步操作后触发回调,您可以使用异步实用程序的 each 方法:github.com/caolan/async#each
2021-03-13 06:10:49
为什么不直接if(index === array.length - 1)删除itemsProcessed
2021-03-20 06:10:49
@AminJafari 因为异步调用可能无法按照它们注册的确切顺序解析(假设您正在调用服务器并且它在第二次调用时稍微停顿,但可以很好地处理最后一次调用)。最后一个异步调用可以在前一个异步调用之前解决。变异计数器可以防止这种情况发生,因为所有回调都必须触发,而不管它们解决的顺序如何。
2021-03-20 06:10:49
为什么不if(index === array.length) {代替if(itemsProcessed === array.length) {它可以节省一个变量的内存和增量处理
2021-03-26 06:10:49
但是,请注意,如果 forEach 中存在异步内容(例如,您正在遍历一组 URL 并对它们执行 HTTP GET),则无法保证 res.end 将被最后调用。
2021-04-06 06:10:49

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

例如:

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

注意:functionAfterForEach是foreach任务完成后要执行的函数。 asynchronous是在foreach内部执行的异步函数。

大家好,自从 ES6 的最新更新带有 Promises 和 Async/await 以来,最好使用 Promises 和 Async/await 功能。这个解决方案现在已经过时了。
2021-03-30 06:10:49

奇怪的是,异步案例有多少不正确的答案可以简单地表明检查索引不提供预期的行为:

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

输出:

4000 started
2000 started
1: 2000
0: 4000

如果我们检查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);
});
还请考虑数组长度为零的情况,在这种情况下,永远不会调用回调
2021-03-25 06:10:49
这可能是唯一的解决办法。异步库是否也使用计数器?
2021-04-04 06:10:49
尽管其他解决方案可以完成这项工作,但这是最引人注目的,因为它不需要链接或增加复杂性。
2021-04-10 06:10:49

希望这能解决您的问题,当我需要在内部执行 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
      } 
}

function doAsynchronousFunction(entry,callback){
       //asynchronousjob with entry
       callback();
}
我在我的 Angular 9 代码中遇到了类似的问题,这个答案对我有用。虽然@Emil Reña Enriquez 的回答也对我有用,但我发现这是针对这个问题的更准确和简单的答案。
2021-04-02 06:10:49

在 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');
}
在 Node v10 中可用
2021-03-28 06:10:49