30

Trying to figure-out how to find something that functional exactly like async.eachSeries, i need a list of async actions run in sequence (not in parallel) but can't find a way to do it in native ES6, can anyone advise, please?

p.s. thought about generators/yield but don't have the experience yet so I'm not realized how exactly it can help me.

Edit 1

per request, here is an example:

Assume this code:

let model1 = new MongooseModel({prop1: "a", prop2: "b"});
let model2 = new MongooseModel({prop1: "c", prop2: "d"});

let arr = [model1 , model2];

Now, I want to run over it in a series, not parallel, so with the "async" NPM it's easy:

async.eachSeries(arr, (model, next)=>{
    model.save.then(next).catch(next);
}, err=>{
    if(err) return reject(error);
    resolve();
})

My question is: with ES6, can I do it natively? without the NPM 'async' package?

Edit 2

With async/await it can be done easily:

let model1 = new MongooseModel({prop1: "a", prop2: "b"});
let model2 = new MongooseModel({prop1: "c", prop2: "d"});    

let arr = [model1 , model2];

for(let model of arr){
    await model.save();
}
Shlomi
  • 3,622
  • 5
  • 23
  • 34

5 Answers5

50

For those who like short answers:

[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());
jib
  • 40,579
  • 17
  • 100
  • 158
  • 5
    The second parameter is the [initial value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) of the reduction. E.g. this particular example is equivalent to `Promise.resolve().then(func1).then(func2)`. – jib Aug 17 '15 at 12:29
  • 1
    Thanks, where would you put the code that will execute when *the last promise* resolves? – SSH This Feb 07 '17 at 16:02
  • 1
    @SSHThis Put `.then(resultFromFunc2 => console.log("all done"))` at the end, before `;`. – jib Feb 07 '17 at 18:15
  • Interesting, though I'm not sure how this is much different from `Promise.all([func1, func2]);`. – Chad Johnson May 17 '17 at 23:46
  • @ChadJohnson The async functions run in sequence, not in parallel. – jib May 18 '17 at 02:02
14

Let's say you want to call some async function on an array of data and you want them called sequentially, not in parallel.

The interface for async.eachSeries() is like this:

eachSeries(arr, iterator, [callback])

Here's how to simulate that with promises:

// define helper function that works kind of like async.eachSeries
function eachSeries(arr, iteratorFn) {
    return arr.reduce(function(p, item) {
        return p.then(function() {
            return iteratorFn(item);
        });
    }, Promise.resolve());
}

This assumes that iteratorFn takes the item to process as an argument and that it returns a promise.

Here's a usage example (that assumes you have a promisified fs.readFileAsync()) and have a function called speak() that returns a promise when done:

 var files = ["hello.dat", "goodbye.dat", "genericgreeting.dat"];
 eachSeries(files, function(file) {
     return fs.readFileAsync(file).then(function(data) {
         return speak(data);
     });
 });

This lets the promise infrastructure sequence everything for you.


It is also possible for you to sequence things manually (though I'm not sure why):

function eachSeries(arr, iteratorFn) {
    return new Promise(resolve, reject) {
        var index = 0;

        function next() {
            if (index < arr.length) {
                try {
                    iteratorFn(arr[index++]).then(next, reject);
                } catch(e) {
                    reject(e);
                }
            } else {
                resolve();
            }
        }
        // kick off first iteration
        next();
    });
}

Or, a simpler version that manually chains the promises together:

function eachSeries(arr, iteratorFn) {
    var index = 0;

    function next() {
        if (index < arr.length) {
            return iteratorFn(arr[index++]).then(next);
        }
    }
    return Promise.resolve().then(next);
}

Note how one of the manual versions has to surround iteratorFn() with try/catch in order to make sure it is throw-safe (convert exceptions into a rejection). .then() is automatically throw safe so the other schemes don't have to manually catch exceptions since .then() already catches them for you.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • As i thought it must use recursive part to fetch the array till it's end.. in this case i assume i'll keep use aync.eachSeries.. – Shlomi Aug 15 '15 at 20:16
  • @ShlomiSasson - there's nothing recursive in my first example so a recursive part is not necessary. It just uses `array.reduce()` to iterate through the array and uses promises to chain together all the operations. – jfriend00 Aug 15 '15 at 20:17
  • Yes, you're right, wasn't familiar with the recude method and now i understand that. So for your example, how i'll now when all async actions has done? i don't see a place you invoke the general "callback".. – Shlomi Aug 15 '15 at 20:26
  • Could you explain this? For instance, in the `EachSeries` function, you call `Promise.resolve());` without specifying `Promise` anywhere. Also, what are the `p, item` that come from `array.reduce?` – Startec Nov 15 '16 at 06:46
  • 1
    @Startec - `.reduce()` accepts two arguments. The first is a function, the second is the initial value of what you are reducing. `Promise.resolve()` is producing that initial value. The callback you pass is then called with two arguments. The first argument is the current value in the reduction. I've named that `p` in my code and it's initial value will be the `Promise.resolve()`. Subsequent values will be the return value of my callback `return p.then(...)` which is another promise. The second argument to the callback is the next value in the array that we are reducing. – jfriend00 Nov 15 '16 at 06:49
  • 1
    @Startec - See [here on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) for doc on `.reduce()`. You call it like this: `var reducedValue = array.reduce(fn, initialValue)`. My `Promise.resolve()` is that `initialValue`. It's just a way to start a promise chain by initializing it to an already resolved promise. It's kind of like `Promise.resolve().then(fn1).then(fn2).then(fn3)`. – jfriend00 Nov 15 '16 at 06:50
  • I really appreciate your implementation. Is this as simple as possible though? I thought that promises were supposed to help create something like this more easily but this is much more complicated than just using the async library. – Startec Nov 15 '16 at 07:55
  • 1
    @Startec - When you factor in proper error handling and potential nested operations, promises are hands-down the best way to manage things. Plus they are the core of `async` and `await` in ES7 so they are the future of the language too. If I wanted to sequence through an array asynchronously, I'd use something like `Promise.mapSeries()` in the Bluebird promise library which is the library I generally use to add extended promise capabilities. – jfriend00 Nov 15 '16 at 08:02
  • I see, but just looking at this code, I want to loop through an array of items, and sum the total by looking them up in a database, then send a response with the total. This code here requires 5 `return` statements to do that. It seems like a lot as I am still trying to wrap my head around all of them. – Startec Nov 15 '16 at 08:08
  • 1
    @Startec - Perhaps you should post a question with a good description of what you're trying to do and ask how the best way to do it with Promises is. I can't see your actual problem in my head from just the words you're using so it would be better to see your code and then folks could offer better/different ways to do it. I've never found a situation where using promises needed more code than not using promises. Like with many things there are good ways to write promise code and not so good ways. – jfriend00 Nov 15 '16 at 08:56
  • Please correct me if I'm wrong. The difference between the two approaches is that the first one creates a promise up front for each item in the list, whereas the second approach creates promises on demand. I'm trying to phase out the second approach, but I want to make sure I understand the real-world tradeoffs. Does the second approach scale better to longer lists than the first? – Dave Causey Nov 26 '16 at 16:34
  • 1
    @DaveCausey - Yes, the first approach creates an initial promise chain of the entire length of the iteration in advance and then lets it run. The other approaches chain one new promise at a time as the prior one resolves and the older ones can be GCed. If you had a really long iteration (like millions), then the latter options might use a noticeably lower amount of peak memory. – jfriend00 Apr 19 '17 at 19:39
1

As an extension to the answer provided by @jib... you can also map an array of items to async functions like so:

[item1, item2]
    .map(item => async (prev_result) => await something_async(item))
    .reduce((p, f) => p.then(f), Promise.resolve())
    .then(() => console.log('all done'));

Notice how prev_result will be the value returned by the previous evaluation of something_async, this is roughly equivalent to a hybrid between async.eachSeries and async.waterfall.

Blake Regalia
  • 2,677
  • 2
  • 20
  • 29
0

You can chain by returning in the then callback. For instance:

new Promise(function(resolve, reject){ 
  resolve(1)
}).then(function(v){
  console.log(v);
  return v + 1;
}).then(function(v){
  console.log(v)
});

Will print:

1
2

This of course work when asynchronously resolving promises:

new Promise(function(resolve, reject){
  setTimeout(function(){
    resolve(1);
  }, 1000)
}).then(function(result){
   return new Promise(function(resolve, reject){
     setTimeout(function(){
       console.log(result);
       resolve(result + 1);
     }, 1000)
   });
}).then(function(results){
  console.log(results);
});

Printing:

1
2

Kit Sunde
  • 35,972
  • 25
  • 125
  • 179
  • Thanks, but i need it for a dynamic collection of data, not for some static known amount of iterations.. – Shlomi Aug 15 '15 at 19:56
  • @ShlomiSasson It's just an example. All `then` does is return a promise, you pass callbacks and create promises as needed. – Kit Sunde Aug 15 '15 at 19:57
  • Kit, so what you say is to create a recursive function that attach more and more callbacks to the "then" method? – Shlomi Aug 15 '15 at 19:59
  • 1
    @ShlomiSasson Well you'd don't have to recurse, just what jfriend00 has done with reduce. There's a ES7 proposal to make this particular case easier btw: https://github.com/lukehoban/ecmascript-asyncawait – Kit Sunde Aug 15 '15 at 20:08
0

//Uploading this for systems that run lower nodejs version (Azure :/) Not the shortest but the nicest i can think of

for example lets say "functionWithPromise" returns some promise and expects some item.

functionWithPromise(item);

promisesArray =[];

//syncornized
itemsArray.forEach(function (item){
   promisesArray.push(functionWithPromise(item));
});

Promise.all(promisesArray).then(function (values){
//profit
});
adi ben
  • 491
  • 5
  • 15