How do I sequentially chain promises with angularj

2020-01-27 04:31发布

问题:

In the promise library Q, you can do the following to sequentially chain promises:

var items = ['one', 'two', 'three'];
var chain = Q();
items.forEach(function (el) {
  chain = chain.then(foo(el));
});
return chain;

however, the following doesn't work with $q:

var items = ['one', 'two', 'three'];
var chain = $q();
items.forEach(function (el) {
  chain = chain.then(foo(el));
});
return chain;

回答1:

Redgeoff, your own answer is the way I used to translate an array into a chained series of promises.

The emergent de facto pattern is as follows :

function doAsyncSeries(arr) {
    return arr.reduce(function (promise, item) {
      return promise.then(function(result) {
        return doSomethingAsync(result, item);
      });
    }, $q.when(initialValue));
}

//then
var items = ['x', 'y', 'z'];
doAsyncSeries(items).then(...);

Notes:

  • .reduce is raw javascript, not part of a library.
  • result is the previous async result/data and is included for completeness. The initial result is initialValue. If it's not necessary to pass `result, then simply leave it out.
  • adapt $q.when(initialValue) depending on which promise lib you use.
  • in your case, doSomethingAsync is foo (or what foo() returns?) - in any case, a function.

If you are like me, then the pattern will look, at first sight, like an impenetrable cludge but once your eye becomes attuned, you will start to regard it as an old friend.

Edit

Here's a demo, designed to demonstrate that the pattern recommended above does in fact execute its doSomethingAsync() calls sequentially, not immediately while building the chain as suggested in the comments below.



回答2:

Simply use the $q.when() function:

var items = ['one', 'two', 'three'];
var chain = $q.when();
items.forEach(function (el) {
  chain = chain.then(foo(el));
});
return chain;

Note: foo must be a factory, e.g.

function setTimeoutPromise(ms) {
  var defer = $q.defer();
  setTimeout(defer.resolve, ms);
  return defer.promise;
}

function foo(item, ms) {
  return function() {
    return setTimeoutPromise(ms).then(function () {
      console.log(item);
    });
  };
}

var items = ['one', 'two', 'three'];
var chain = $q.when();
items.forEach(function (el, i) {
  chain = chain.then(foo(el, (items.length - i)*1000));
});
return chain;


回答3:

In perhaps a simpler manner than redgeoff's answer, if you don't need it automated, you can chain promises using $q.when() combined with .then() as shown in the beginning of this post. return $q.when() .then(function(){ return promise1; }) .then(function(){ return promise2; });



回答4:

var when = $q.when();

for(var i = 0; i < 10; i++){
    (function() {
         chain = when.then(function() {
        return $http.get('/data');
      });

    })(i); 
}


回答5:

Having this:

let items = ['one', 'two', 'three'];

One line (well, 3 for readability):

return items
    .map(item => foo.bind(null, item))
    .reduce($q.when, $q.resolve());


回答6:

I prefer preparing functions that will return promises using angular.bind (or Function.prototype.bind) and then linking them into a chain using reduce shortcut. For example

// getNumber resolves with given number
var get2 = getNumber.bind(null, 2);
var get3 = getNumber.bind(null, 3);
[get2, get3].reduce(function (chain, fn) {
   return chain.then(fn);
}, $q.when())
.then(function (value) {
   console.log('chain value =', value);
}).done();
// prints 3 (the last value)


回答7:

Your answer is correct. However, I thought I'd provide an alternative. You may be interested in $q.serial if you find yourself serially chaining promises often.

var items = ['one', 'two', 'three'];
var tasks = items.map(function (el) {
  return function () { foo(el, (items.length - i)*1000)); });
});

$q.serial(tasks);

function setTimeoutPromise(ms) {
  var defer = $q.defer();
  setTimeout(defer.resolve, ms);
  return defer.promise;
}

function foo(item, ms) {
  return function() {
    return setTimeoutPromise(ms).then(function () {
      console.log(item);
    });
  };
}