【问题标题】:angular $q, How to chain multiple promises within and after a for-loopangular $q,如何在 for 循环内和之后链接多个 Promise
【发布时间】:2014-01-28 06:01:44
【问题描述】:

我想要一个 for 循环,每次迭代都会调用异步函数。

在 for 循环之后我想执行另一个代码块,但不是在 for 循环中的所有先前调用都已解决之前。

我目前的问题是,要么在所有异步调用完成之前执行 for 循环之后的代码块,要么根本不执行。

带有FOR循环的代码部分及其后的代码块(完整代码请见fiddle):

[..]
function outerFunction($q, $scope) {
    var defer = $q.defer();    
    readSome($q,$scope).then(function() {
        var promise = writeSome($q, $scope.testArray[0])
        for (var i=1; i < $scope.testArray.length; i++) {
             promise = promise.then(
                 angular.bind(null, writeSome, $q, $scope.testArray[i])
             );                                  
        } 
        // this must not be called before all calls in for-loop have finished
        promise = promise.then(function() {
            return writeSome($q, "finish").then(function() {
                console.log("resolve");
                // resolving here after everything has been done, yey!
                defer.resolve();
            });   
        });        
    });   

    return defer.promise;
}

我创建了一个 jsFiddle,可以在这里找到 http://jsfiddle.net/riemersebastian/B43u6/3/

目前看起来执行顺序很好(参见控制台输出)。

我的猜测是,这仅仅是因为每个函数调用都会立即返回而无需执行任何实际工作。我试图用 setTimeout 延迟 defer.resolve 但失败了(即最后一个代码块从未执行过)。您可以在小提琴的注释块中看到它。

当我使用写入文件和读取文件的实际函数时,最后一个代码块在最后一个写入操作完成之前执行,这不是我想要的。

当然,错误可能出在其中一个读/写函数中,但我想验证我在此处发布的代码没有任何问题。

【问题讨论】:

  • (1) 关于从inside循环调用的函数:它们必须顺序运行还是并行运行,仍然需要最后一个块在所有都完成了吗?并且:(2)如果其中一个导致错误应该怎么办?
  • 如果您使用的是 write 函数,这些函数通常也是异步的,因此很可能一切都“按预期”工作。也就是说,angular 正在启动所有写入(这需要一小部分时间),但写入本身需要很长时间。你在写什么,你用的是什么 API?
  • @NikosParaskevopoulos (1) 我真的不在乎它们是并行运行还是顺序运行,它们可以并行运行,因为它们不相互依赖。就目前而言,每个内部函数都返回一个承诺并在操作结束时解析,这意味着它们是串行执行的。你明白了,最后一个操作必须始终是最后一个执行的操作,不管前一个是并行运行还是串行运行。 (2) 好问题,我想可能会记录一些警告,但这并不重要。
  • @Hylianpuffball 我正在将 JSONObject 写入文件,并使用 chromes 的文件系统进行存储。我想最重要的部分是,我解决了 fileWriter.onwriteend、fileWriter.onerror 等中的延迟。

标签: angularjs promise angular-promise deferred


【解决方案1】:

您需要使用的是$q.all,它将多个promise 合并为一个,只有在所有promise 都解决后才会解决。

在您的情况下,您可以执行以下操作:

function outerFunction() {

    var defer = $q.defer();
    var promises = [];

    function lastTask(){
        writeSome('finish').then( function(){
            defer.resolve();
        });
    }

    angular.forEach( $scope.testArray, function(value){
        promises.push(writeSome(value));
    });

    $q.all(promises).then(lastTask);

    return defer.promise;
}

【讨论】:

  • 出于兴趣,Angualr 是否提供 $q.defer().resolve() 分离,就像在 jQuery 中一样?换句话说,你能写writeSome('finish').then(defer.resolve);吗?如果是这样,代码会稍微紧凑一些,但其他方面相同。
  • 好建议。 'then' 函数接受一个在 promise 被解析时将被调用的函数,所以传递 defer.resolve 的参数是可行的。我将暂时保留答案,因为该问题也有一些登录信息(为清楚起见,我已将其省略)。
  • 感谢您的建议@GruffBunny,我会尽快调查并通知您!
  • @GruffBunny 感谢您的解释! promises.push ... 和 $q.all 正是我想要的!
  • @Jason,链接承诺:example
【解决方案2】:

这对我使用 ES5 语法很有用

function outerFunction(bookings) {

    var allDeferred = $q.defer();
    var promises = [];

    lodash.map(bookings, function(booking) {
        var deferred = $q.defer();

        var query = {
            _id: booking.product[0].id,
            populate: true
        }

        Stamplay.Object("product").get(query)
        .then(function(res) {
            booking.product[0] = res.data[0];
            deferred.resolve(booking)
        })
        .catch(function(err) {
            console.error(err);
            deferred.reject(err);
        });

        promises.push(deferred.promise);
    });

    $q.all(promises)
    .then(function(results) { allDeferred.resolve(results) })
    .catch(function(err) { allDeferred.reject(results) });

    return allDeferred.promise;
}

【讨论】:

    【解决方案3】:

    您可以同时使用 $q 和 'reduce' 来链接承诺。

    function setAutoJoin() {
        var deferred = $q.defer(), data;
        var array = _.map(data, function(g){
                return g.id;
            });
    
        function waitTillAllCalls(arr) {
            return arr.reduce(function(deferred, email) {
                return somePromisingFnWhichReturnsDeferredPromise(email);
            }, deferred.resolve('done'));
        }
    
        waitTillAllCalls(array);
    
        return deferred.promise;
    }
    

    【讨论】:

      【解决方案4】:

      使用新的 ES7,您可以以更直接的方式获得相同的结果:

      let promises =  angular.forEach( $scope.testArray, function(value){
          writeSome(value);
      });
      
      let results = await Promise.all(promises);
      
      console.log(results);
      

      【讨论】:

      • 你确定angular.forEach() 是这样工作的吗? let promises = $scope.testArray.map(writeSome); 会不会更好?
      • let results = await Promise.all($scope.testArray.map(writeSome)); 更加紧凑。
      • @Roamer-1888 如果它不起作用,请随时编辑。我认为你是对的,我还没有完全测试过
      • 我在 C# 中也见过 await。我认为他们都有相同的想法。
      猜你喜欢
      • 2019-01-07
      • 1970-01-01
      • 2016-11-28
      • 2015-01-19
      • 2021-09-09
      • 2017-07-25
      • 1970-01-01
      • 2023-04-06
      • 1970-01-01
      相关资源
      最近更新 更多