angular $ q,如何在for循环内和之后链接多个promise


75

我想要一个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)关于您从循环内部调用的函数:它们必须顺序运行还是并行运行,在所有函数完成后仍需要最后一个块运行?并且:(2)如果其中之一导致错误怎么办?
Nikos Paraskevopoulos 2014年

如果您使用的是写函数,那么它们通常也是异步的,因此很可能一切都按预期工作。也就是说,angular正在启动所有写入操作(需要花费一小部分时间),但是写入操作本身要花费很长时间。您在写什么,使用什么API?
Hylianpuffball 2014年

@NikosParaskevopoulos(1)我并不在乎它们是并行运行还是顺序运行,它们可以并行运行,因为它们彼此不依赖。到目前为止,每个内部函数都返回一个promise并在操作结束时解析,这意味着它们是串行执行的。知道了,无论前一个操作是并行还是串行运行,最后一个操作必须始终是最后执行的操作。(2)好问题,我想可能会记录一些警告,但这并不重要。
SebastianRiemer

@Hylianpuffball我正在将JSONObject写入文件,并且使用chromes的文件系统进行存储。我想其中最重要的部分是,我解决了fileWriter.onwriteend,fileWriter.onerror等文件中的延迟问题。–
SebastianRiemer

Answers:


121

您需要使用的是$ q.all,它将多个诺言合并为一个诺言,只有当所有诺言都得到解决后才能解决。

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

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

1
出于兴趣,Angualr是否$q.defer().resolve()像jQuery中那样要求分离?换句话说,你能写writeSome('finish').then(defer.resolve);吗?如果是这样,代码将稍微紧凑一些,但其他方面相同。
甜菜根

1
好建议。'then'函数采用的函数将在解决promise时被调用,因此,是的,传递defer.resolve参数将起作用。我将保留现在的答案,因为该问题也在那里进行了登录(为清楚起见,我将其省略了)。
Gruff Bunny 2014年

谢谢您的建议@GruffBunny我将尽快调查一下,并告诉您!
SebastianRiemer

@GruffBunny感谢您的解释!我一直在寻找promises.push ...和$ q.all!
SebastianRiemer

1
@Jason,束缚诺言:例子
米歇尔·范·恩格伦

3

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

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

let results = await Promise.all(promises);

console.log(results);

3
您确定angular.forEach()这样吗?会let promises = $scope.testArray.map(writeSome);不会更好?
Roamer-1888

2
并且let results = await Promise.all($scope.testArray.map(writeSome));更加紧凑。
Roamer-1888

如果@ Roamer-1888不起作用,请随时进行编辑。我认为您是对的,我还没有完全测试过
Maurizio在丹麦

我也看到过在C#中等待。我认为他们都有相同的想法。
罗比·史密斯

1

您可以一起使用$q和“减少”链接承诺。

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

0

使用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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.