【问题标题】:Callback after all asynchronous forEach callbacks are completed所有异步 forEach 回调完成后的回调
【发布时间】:2013-09-29 18:13:32
【问题描述】:

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

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

[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!

【问题讨论】:

  • 如果标准数组forEach方法有done回调参数和allDone回调就好了!
  • 真可惜,这么简单的事情需要这么多的 JavaScript 折腾。

标签: javascript node.js asynchronous callback


【解决方案1】:

您不需要回调来遍历列表。只需在循环后添加end() 调用即可。

posts.forEach(function(v, i){
   res.write(v + ". Index " + i);
});
res.end();

【讨论】:

  • 没有。 OP 强调每次迭代都会执行异步逻辑。 res.write 不是异步操作,因此您的代码将无法运行。
【解决方案2】:

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 库可用于旧版浏览器):

  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.forEachsynchronousres.write,因此您可以在调用 foreach 之后简单地进行回调:

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

  res.end();

【讨论】:

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

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

例如:

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 功能。这个解决方案现在已经过时了。
【解决方案4】:

这是 Node.js 的异步解决方案。

使用异步 npm 包。

(JavaScript) Synchronizing forEach Loop with callbacks inside

【讨论】:

    【解决方案5】:

    希望这能解决您的问题,当我需要执行带有异步任务的 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 的回答也对我有用,但我发现这是解决这个问题的更准确和简单的答案。
    【解决方案6】:

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

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

    【讨论】:

    • 这可能是唯一的解决方案。异步库是否也使用计数器?
    • 虽然其他解决方案可以完成这项工作,但这是最引人注目的,因为它不需要链接或增加复杂性。亲吻
    • 请考虑数组长度为零的情况,这种情况下回调永远不会被调用
    【解决方案7】:

    我的解决方案:

    //Object forEachDone
    
    Object.defineProperty(Array.prototype, "forEachDone", {
        enumerable: false,
        value: function(task, cb){
            var counter = 0;
            this.forEach(function(item, index, array){
                task(item, index, array);
                if(array.length === ++counter){
                    if(cb) cb();
                }
            });
        }
    });
    
    
    //Array forEachDone
    
    Object.defineProperty(Object.prototype, "forEachDone", {
        enumerable: false,
        value: function(task, cb){
            var obj = this;
            var counter = 0;
            Object.keys(obj).forEach(function(key, index, array){
                task(obj[key], key, obj);
                if(array.length === ++counter){
                    if(cb) cb();
                }
            });
        }
    });
    

    例子:

    var arr = ['a', 'b', 'c'];
    
    arr.forEachDone(function(item){
        console.log(item);
    }, function(){
       console.log('done');
    });
    
    // out: a b c done
    

    【讨论】:

    • 解决方案创新但出现错误——“task is not a function”
    【解决方案8】:

    setInterval 怎么样,检查完整的迭代计数,带来保证。不确定它是否不会使范围超载,但我使用它并且似乎是一个

    _.forEach(actual_JSON, function (key, value) {
    
         // run any action and push with each iteration 
    
         array.push(response.id)
    
    });
    
    
    setInterval(function(){
    
        if(array.length > 300) {
    
            callback()
    
        }
    
    }, 100);
    

    【讨论】:

    • 这看起来逻辑上很简单
    【解决方案9】:

    我没有 Promise 的解决方案(这样可以确保每个动作都在下一个动作开始之前结束):

    Array.prototype.forEachAsync = function (callback, end) {
            var self = this;
        
            function task(index) {
                var x = self[index];
                if (index >= self.length) {
                    end()
                }
                else {
                    callback(self[index], index, self, function () {
                        task(index + 1);
                    });
                }
            }
        
            task(0);
        };
        
        
        var i = 0;
        var myArray = Array.apply(null, Array(10)).map(function(item) { return i++; });
        console.log(JSON.stringify(myArray));
        myArray.forEachAsync(function(item, index, arr, next){
          setTimeout(function(){
            $(".toto").append("<div>item index " + item + " done</div>");
            console.log("action " + item + " done");
            next();
          }, 300);
        }, function(){
            $(".toto").append("<div>ALL ACTIONS ARE DONE</div>");
            console.log("ALL ACTIONS ARE DONE");
        });
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <div class="toto">
    
    </div>

    【讨论】:

      【解决方案10】:

      我尝试了简单的方法来解决它,与你分享:

      let counter = 0;
                  arr.forEach(async (item, index) => {
                      await request.query(item, (err, recordset) => {
                          if (err) console.log(err);
      
                          //do Somthings
      
                          counter++;
                          if(counter == tableCmd.length){
                              sql.close();
                              callback();
                          }
                      });
      

      request是Node js中mssql库的函数。这可以替换您想要的每个功能或代码。 祝你好运

      【讨论】:

        【解决方案11】:
        var i=0;
        const waitFor = (ms) => 
        { 
          new Promise((r) => 
          {
           setTimeout(function () {
           console.log('timeout completed: ',ms,' : ',i); 
             i++;
             if(i==data.length){
              console.log('Done')  
            }
          }, ms); 
         })
        }
        var data=[1000, 200, 500];
        data.forEach((num) => {
          waitFor(num)
        })
        

        【讨论】:

          【解决方案12】:

          在 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 中可用
          【解决方案13】:
           var counter = 0;
           var listArray = [0, 1, 2, 3, 4];
           function callBack() {
               if (listArray.length === counter) {
                   console.log('All Done')
               }
           };
           listArray.forEach(function(element){
               console.log(element);
               counter = counter + 1;
               callBack();
           });
          

          【讨论】:

          • 它不起作用,因为如果你将在 foreach 中进行异步操作。
          【解决方案14】:

          在这个问题上有很多解决方案和方法可以实现这一目标!

          但是,如果您需要使用 ma​​pasync/await 执行此操作,那么就在这里

          // Execution Starts
          console.log("start")
          
          // The Map will return promises
          // the Execution will not go forward until all the promises are resolved.
          await Promise.all(
              [1, 2, 3].map( async (item) => {
                  await asyncFunction(item)
              })
          )
          
          // Will only run after all the items have resolved the asynchronous function. 
          console.log("End")
          

          输出将是这样的!可能因异步函数而异。

          start
          2
          3
          1
          end
          

          注意:如果您在地图中使用 await,它将始终返回 promises 数组。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-11-13
            • 2013-05-02
            • 1970-01-01
            • 2016-11-27
            相关资源
            最近更新 更多