【问题标题】:(JavaScript) Synchronizing forEach Loop with callbacks inside(JavaScript) 将 forEach 循环与内部回调同步
【发布时间】:2013-02-16 05:00:58
【问题描述】:

我在双 forEach 循环中进行一些计算,如下所示:

array.forEach(function(element){
    Object.keys(element).forEach(function(key){

        /* some complex computations with asynchronous callbacks  */        

    });
});

someFunctionHere();

有没有办法让循环在执行someFunctionHere( ) 函数之前先完成?或者程序在继续someFunctionHere( )之前知道循环是否完成的任何方式...

我可能错过了一些论坛,但我发现的那些并没有帮助我实现我想要实现的目标,顺便说一下我在 NodeJS 中这样做的方式,我也在问是否有现有的库可以实现这一点。

我忘了添加这个还是应该是另一个问题?

有没有办法同步进行迭代,只有在当前迭代完成后才会进行下一次迭代? (对不起)

感谢您的帮助...

【问题讨论】:

  • 是的,基本上100%保证“someFunctionHere”会在异步操作完成之前被调用。
  • 关于您的编辑:Golo 的回答显示了如何使用 async.js 来做您想做的事。 (我相信Clumpy.js 也可以用于此。)我的回答显示了如果由于某种原因您不能(或根本不想)使用第三方库,您如何“推出自己的”解决方案。
  • 我已经使用 async.forEach() 完成了上述问题,但它弄乱了我的数据库,因为迭代不是一次调用一个迭代(或者如果迭代是同步完成的,我可能会遇到逻辑错误在代码中),所以 Golo 的答案可能与我在 asyn.forEach() 中所做的相同,我的意思是从术语“异步”来看,迭代不是同步完成的吗?
  • 或者它可以一次完成一个但不等待当前迭代完成后再进行下一个迭代,对吗?

标签: javascript node.js


【解决方案1】:
let dataObject = {};
Promise.all(objectArray.map(object => {
        return new Promise(resolve => {
            yourFunction(object, anotherParameter).then(returnValue => {
                dataObject[object] = returnValue;
                resolve();
            });
        });
    })).then(() => {
        return dataObject;
    });

【讨论】:

    【解决方案2】:

    对于支持 Promise(或使用 polyfill)/nodejs 的浏览器,我自己实现了一个带有回调的 forEach 的同步版本,所以我将在这里分享..

    回调必须返回一个承诺..

    function forEachSync(array, callback) {
        let lastIndex = array.length - 1;
        let startIndex = 0;
    
        return new Promise((resolve, reject) => { // Finish all
            let functionToIterateWith = (currIndex) => {
                if (currIndex > lastIndex) {
                    return resolve();
                } else {
                    callback(array[currIndex]).then(() => {
                        functionToIterateWith(currIndex + 1);
                    }).catch(err => reject(err));
                }
            }
    
            functionToIterateWith(startIndex);
        });
    }
    

    【讨论】:

      【解决方案3】:

      高性能解决方案:

      在某些情况下,您可能希望/允许异步/并行处理数组,但希望在处理完所有 forEach 成员后调用函数。 示例:

      var async = require('async');
      
      async.forEach(array,function(elementOfArray, callback){
          //process each element of array in parallel 
          // ..
          // ..
          // .. 
          callback(); //notify that this iteration is complete
      }, function(err){
       if(err){throw err;}
       console.log("processing all elements completed");
      });
      

      因此,您可以通过这种方式执行非阻塞 CPU 密集型操作。

      注意:当您将 eachSeries 用于大型数组时,如此多的迭代回调可能会溢出堆栈。

      在这里阅读: https://github.com/caolan/async#control-flow

      【讨论】:

        【解决方案4】:

        看看 async.js,尤其是它的 control flow 语句,例如 each whilstuntil

        使用 async.js 你可以得到你想要的。

        在您的实际情况下,您需要的是 each 函数(以前称为 forEach),分别是 eachSeries 函数,它不是并行运行单次迭代,而是串行运行(参见documentation for eachSeries了解详情)。

        举个例子:

        async.eachSeries([ 2, 3, 5, 7, 11 ], function (prime, callback) {
          console.log(prime);
          callback(); // Alternatively: callback(new Error());
        }, function (err) {
          if (err) { throw err; }
          console.log('Well done :-)!');
        });
        

        这将遍历素数数组并以正确的顺序打印它们,一个接一个,最后打印出Well done :-)!

        【讨论】:

        • 感谢这个,但是使用异步库不是一个选项,我的意思是循环中的每次迭代都应该是同步的,因为它正在执行密集的数据库操作,它应该只在一次迭代时间,我忘了把这个问题也很抱歉
        • 样本准确地显示了这种行为:循环中的每次迭代都是同步运行的。完美序列化。
        • 在提出问题之前,我在之前的解决方案中使用了 async.forEach() ,所以异步中的迭代一次完成一个?因为如果是这样,我的代码中可能会出现一些我没有看到的逻辑错误,或者可以一次完成一个但不会等待当前迭代完成,然后再进行下一次迭代
        • stackoverflow.com/questions/10390041/… ,这是我正在谈论的 async.forEach() ,也许这不是真正的解决方案,但你给我的链接描述是我想要的。跨度>
        • 正是我需要的!谢谢。 :)
        【解决方案5】:

        您可以将回调包装在倒计时闭包中:

        var len = array.length;
        
        function countdownWrapper(callback, callbackArgs) {
            callback(callbackArgs);
            if (--len == 0) {
                someFunctionHere();
            }
        }
        
        array.forEach(function(element){
            Object.keys(element).forEach(function(key){
        
                var wrappedCallback = countdownWrapper.bind(callback);
                /* some complex computations with asynchronous WRAPPED callbacks  */
        
            });
        });
        

        如果回调有不同数量的参数,您可以对arguments 做一些小手术,而不是使用显式的callbackArgs 参数。

        编辑您的编辑阐明了您希望在前一个计算完成回调后开始每个复杂计算。这也可以通过闭包轻松安排:

        function complexOp(key, callback) { /* . . . */ }
        
        function originalCallback(...) { /* . . . */ }
        
        function doSomethingElse() { /* . . . */ }
        
        var iteratorCallback = (function (body, callback, completion) {
            var i = 0, len = array.length;
            return function iterator() {
                callback.apply(null, arguments);
                if (++i < len) {
                    body(array[i], iterator);
                } else {
                    completion();
                }
            };
        }(complexOp, originalCallback, doSomethingElse)); 
        
        // kick everything off:
        complexOp(array[0], iteratorCallback);
        

        【讨论】:

        • 这真的很有帮助,它可能会解决我的一个问题,我仍然需要检查一下,谢谢!
        • 这基本上就是 async.js 为您提供的开箱即用的东西。它只是不是“标准化”,而是“专有”。
        • @GoloRoden - 专有?这太荒谬了。网络上有很多这个技巧的例子,而且很容易就可以在飞行中想出它。 (为了记录,我没有从任何地方复制我帖子中的代码。)帖子的重点是你不需要像async.jsClumpy.js这样的整个包;这只是几行代码。
        • 这就是为什么我把它放在引号里。当然,它原本的含义并不是专有的,但为什么要第 1000 次重新发明轮子呢?当然你可以自己做而不是使用库,但你也可以在汇编程序中做所有事情。您想自己做这件事根本没有任何有意义的理由。
        • @GoloRoden - 好的,感谢您的澄清。您的第一条评论听起来像是某种指责。也许是我过于敏感了。当然,使用 async.js 或 Clumpy.js 之类的包是首选方法。但是,使用任何第三方库都有一个学习曲线。此外,有时由于非技术原因,在您的代码中包含第三方库可能会出现问题。知道如何自己做这些事情总是好的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-24
        • 2013-01-18
        • 2022-07-21
        • 2018-08-10
        • 1970-01-01
        相关资源
        最近更新 更多