Chained promises in Angular

2019-08-14 06:05发布

问题:

I have a list of records I want to process in sequence. A "root" promise needs to be passed back to be added as part of another parent chain.

processCategories = function(categories) {
    var deferred = $q.defer();
    var promise = deferred.promise;

    // Process each category in sequence
    angular.forEach(categories, function (data, classIndex) {
        promise = promise.then(doSomethingAndReturnAPromise(categories, data, classIndex));

        if (classIndex === categories.length - 1) {
            // Last category has been reached, resolve this unit of work
            promise.then(function() {
                deferred.resolve(categories);
            });
        }
    });

    // Pass the root promise back to the chain
    return promise;
}

doSomethingAndReturnAPromise = function(categories, data, index) {
    console.log('item : ' + index + ' = started');
    var deferred = $q.defer();

    executeAnAsynchronousCall(function () {
            console.log('item : ' + index + ' = done');
            deferred.resolve(categories);

    }, function(status, text) {
            console.log('item : ' + index + ' = error');
            // Handle error here

    }, data);

    console.log('item : ' + index + ' = returned');
    return deferred.promise;
}

I'm getting some errors from my asynchronous call because the executions are occurring at the same time (and not sequentially). I added the logging above and observed the following:

2014-08-08 11:38:02.702 item : 1 = started
2014-08-08 11:38:02.702 item : 1 = returned
2014-08-08 11:38:02.702 item : 2 = started
2014-08-08 11:38:02.703 item : 2 = returned
2014-08-08 11:38:02.703 item : 3 = started
2014-08-08 11:38:02.704 item : 3 = returned
2014-08-08 11:38:02.704 item : 4 = started
2014-08-08 11:38:02.705 item : 4 = returned
2014-08-08 11:38:02.705 item : 5 = started
2014-08-08 11:38:02.706 item : 5 = returned
2014-08-08 11:38:02.707 item : 6 = started
2014-08-08 11:38:02.707 item : 6 = returned
2014-08-08 11:38:02.708 item : 7 = started
2014-08-08 11:38:02.709 item : 7 = returned
2014-08-08 11:38:02.709 item : 8 = started
2014-08-08 11:38:02.710 item : 8 = returned

2014-08-08 11:38:02.716 item : 2 = error
2014-08-08 11:38:02.718 item : 3 = error
2014-08-08 11:38:02.719 item : 4 = error
2014-08-08 11:38:02.720 item : 5 = error
2014-08-08 11:38:02.722 item : 1 = done // (This has finished, the server is now expecting #2)
2014-08-08 11:38:02.723 item : 6 = error
2014-08-08 11:38:02.735 item : 8 = error
2014-08-08 11:38:02.754 item : 7 = error 

I was expecting the .then to ensure the promises are run in sequence, but each time I run the code it appears they are all being fired at once.

Can anyone see where I'm going wrong here?

回答1:

Your second line is invoking the function rather than scheduling it to be invoked later

Change:

promise = promise.then(doSomethingAndReturnAPromise(categories, data, classIndex));

To:

promise = promise.then(doSomethingAndReturnAPromise.bind(this,categories, data, index));

Or a full blown anonymous function:

promise = promise.then(function(){
    return doSomethingAndReturnAPromise(categories, data, classIndex));
});

As a side note, if you need the promise to resolve with the categories, it would be simpler to change the return value directly rather than put that check in the forEach.

processCategories = function(categories) {
    return categories.reduce(function (prev, data, index) {
            return prev.then(function(){
                return doSomethingAndReturnAPromise(categories, data, index));
            });
    }, $q.when()).then(function(){
        return categories;
    });
}


回答2:

Change processCategories() to something like this:

processCategories = function(categories) {
    var promise, deferred;

    // Process each category in sequence
    angular.forEach(categories, function (data, classIndex) {
      if(promise) {
        promise = promise.then(function() {
           return doSomethingAndReturnAPromise(categories, data, classIndex);
        });
      } else
        promise = doSomethingAndReturnAPromise(categories, data, classIndex);
    });

    if(promise) {
      promise.then(function() {
        return categories;
      });
    } else {
      deferred = $q.defer();
      promise = deferred.promise;
      deferred.resolve(categories);
    }


    // Pass the last promise
    return promise;
}