Resolve promises one after another (i.e. in sequence)?
Asked Answered
E

36

428

Consider the following code that reads an array of files in a serial/sequential manner. readFiles returns a promise, which is resolved only once all files have been read in sequence.

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  return new Promise((resolve, reject) => {
    var readSequential = function(index) {
      if (index >= files.length) {
        resolve();
      } else {
        readFile(files[index]).then(function() {
          readSequential(index + 1);
        }).catch(reject);
      }
    };

    readSequential(0); // Start with the first file!
  });
};

The above code works, but I don't like having to do recursion for things to occur sequentially. Is there a simpler way that this code can be re-written so that I don't have to use my weird readSequential function?

Originally I tried to use Promise.all, but that caused all of the readFile calls to happen concurrently, which is not what I want:

var readFiles = function(files) {
  return Promise.all(files.map(function(file) {
    return readFile(file);
  }));
};
Exclamation answered 5/7, 2014 at 11:48 Comment(8)
Anything that has to wait for a previous asynchronous operation to finish has to be done in a callback. Using promises doesn't change that. So you need the recursion.Wheatley
FYI, this isn't technically recursion as there is no stack frame build-up. The previous readFileSequential() has already returned before the next one is called (because it's async, it completes long after the original function call has already returned).Epergne
@Epergne Stack frame accumulation is not required for recursion - only self reference. This is just a technicality though.Loculus
@BenjaminGruenbaum - my point is that there is absolutely nothing wrong with having the function call itself to kick off the next iteration. There is zero downside to it and, in fact, it's an efficient way to sequence async operations. So, there's no reason to avoid something that looks like recursion. There are recursive solutions to some problems that are inefficient - this is not one of those.Epergne
I cannot disagree with that. The only thing I might change with OP's way of coding it is perhaps queue the operations in advance in a for loop, but that doesn't matter that much.Loculus
@Epergne There is a problem with OP's approach in that it swallows errors, but it could be modified to handle errors without changing the overall strategy.Masry
Hey, per a discussion and request in the JavaScript room I've edited this answer so we can point others to it as a canonical. If you disagree please let me know and I'll restore it and open a separate one.Loculus
Use Promise.all or an async function async promisesElswick
L
502

Update 2017: I would use an async function if the environment supports it:

async function readFiles(files) {
  for(const file of files) {
    await readFile(file);
  }
};

If you'd like, you can defer reading the files until you need them using an async generator (if your environment supports it):

async function* readFiles(files) {
  for(const file of files) {
    yield await readFile(file);
  }
};

Update: In second thought - I might use a for loop instead:

var readFiles = function(files) {
  var p = Promise.resolve(); // Q() in q

  files.forEach(file =>
      p = p.then(() => readFile(file)); 
  );
  return p;
};

Or more compactly, with reduce:

var readFiles = function(files) {
  return files.reduce((p, file) => {
     return p.then(() => readFile(file));
  }, Promise.resolve()); // initial
};

In other promise libraries (like when and Bluebird) you have utility methods for this.

For example, Bluebird would be:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));

var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param

readAll.then(function(allFileContents){
    // do stuff to read files.
});

Although there is really no reason not to use async await today.

Loculus answered 5/7, 2014 at 11:55 Comment(5)
@EmreTapcı, nope. An arrow function's "=>" already implies returning.Brownson
If you use TypeScript, I think the "for in" loop solution is best. Reduce returns recursive Promises eg. first call return type is Promise<void>, then second is Promise<Promise<void>> and so on - it's impossible to type without using any I thinkCatfish
@ArturTagisow TypeScript (at least new versions) have recursive types and should resolve the types correctly here. There is no such thing as a Promise<Promise<T>> since promises "recursively assimilate". Promise.resolve(Promise.resolve(15)) is identical to Promise.resolve(15).Loculus
@ArturTagisow typescriptlang.org/play/#code/…Loculus
files.forEach([arrayOfPromises]) is not sequential, as forEach will not wait for the promise to resolve before doing the next one. Same goes for Array.map, that's concurrent as well.Standice
E
94

This question is old, but we live in a world of ES6 and functional JavaScript, so let's see how we can improve.

Because promises execute immediately, we can't just create an array of promises, they would all fire off in parallel.

Instead, we need to create an array of functions that returns a promise. Each function will then be executed sequentially, which then starts the promise inside.

We can solve this a few ways, but my favorite way is to use reduce.

It gets a little tricky using reduce in combination with promises, so I have broken down the one liner into some smaller digestible bites below.

The essence of this function is to use reduce starting with an initial value of Promise.resolve([]), or a promise containing an empty array.

This promise will then be passed into the reduce method as promise. This is the key to chaining each promise together sequentially. The next promise to execute is func and when the then fires, the results are concatenated and that promise is then returned, executing the reduce cycle with the next promise function.

Once all promises have executed, the returned promise will contain an array of all the results of each promise.

ES6 Example (one liner)

/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs =>
    funcs.reduce((promise, func) =>
        promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))

ES6 Example (broken down)

// broken down to for easier understanding

const concat = list => Array.prototype.concat.bind(list)
const promiseConcat = f => x => f().then(concat(x))
const promiseReduce = (acc, x) => acc.then(promiseConcat(x))
/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([]))

Usage:

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const funcs = urls.map(url => () => $.ajax(url))

// execute them serially
serial(funcs)
    .then(console.log.bind(console))
Entresol answered 13/12, 2016 at 6:44 Comment(5)
very good, thanks, Array.prototype.concat.bind(result) is the part I was missing, had to do pushing to results manually which worked but was less coolDurman
Since we're all about modern JS, I believe the console.log.bind(console) statement in your last example is now usually unnecessary. These days you can just pass console.log. Eg. serial(funcs).then(console.log). Tested on current nodejs and Chrome.Aubergine
This was a little tough to wrap my head around but the reduce is essentially doing this correct? Promise.resolve([]).then((x) => { const data = mockApi('/data/1'); return Promise.resolve(x.concat(data)) }).then((x) => { const data = mockApi('/data/2'); return Promise.resolve(x.concat(data)); });Grubstake
@danecando, yes this looks correct. You can also drop the Promise.resolve in the return, any values returned will be automatically resolved unless you call Promise.reject on them.Entresol
@joelnet, in response to danecando's comment, I think what the reduce do should be more correct express in the following expression, do you agree? Promise.resolve([]).then(x => someApiCall('url1').then(r => x.concat(r))).then(x => someApiCall('url2').then(r => x.concat(r))) and so forthPliske
T
90

Here is how I prefer to run tasks in series.

function runSerial() {
    var that = this;
    // task1 is a function that returns a promise (and immediately starts executing)
    // task2 is a function that returns a promise (and immediately starts executing)
    return Promise.resolve()
        .then(function() {
            return that.task1();
        })
        .then(function() {
            return that.task2();
        })
        .then(function() {
            console.log(" ---- done ----");
        });
}

What about cases with more tasks? Like, 10?

function runSerial(tasks) {
  var result = Promise.resolve();
  tasks.forEach(task => {
    result = result.then(() => task());
  });
  return result;
}
Tingly answered 26/6, 2015 at 9:54 Comment(9)
And what about cases where you don't know the exact number of tasks?Mattah
And what about when you do know the number of tasks, but only at runtime?Bahadur
This example cannot handle errors! (a promise that rejects)Dhahran
"you don't want to operate over an array of promises at all. Per the promise spec, as soon as a promise is created, it begins executing. So what you really want is an array of promise factories" see Advanced mistake #3 here : pouchdb.com/2015/05/18/we-have-a-problem-with-promises.htmlBelvia
If you're into reducing line noise, you can also write result = result.then(task);Glossitis
there is return result; and also result is being overriden with result = result.then(() => task());, so for the function that called this runSerial what result would it refer to? the original result or the overridden one?Rainbow
It is the same variable. javascript has function scope for variables, and variables defined outside the current function is accessible inside the inner function, but not the other way around.Eerie
@DanielBuckmaster yes, but be careful, since if task() returns a value, it will be passed to the next invocation. If your task has optional arguments, this might cause side-effects. The current code swallows results and explicitly invokes the next task with no arguments.Benenson
Any failure will end the chain of .then()Hyposthenia
E
53

To do this simply in ES6:

function(files) {
  // Create a new empty promise (don't do that with real people ;)
  var sequence = Promise.resolve();

  // Loop over each file, and add on a promise to the
  // end of the 'sequence' promise.
  files.forEach(file => {

    // Chain one computation onto the sequence
    sequence = 
      sequence
        .then(() => performComputation(file))
        .then(result => doSomething(result)); 
        // Resolves for each file, one at a time.

  })

  // This will resolve after the entire chain is resolved
  return sequence;
}
Edvard answered 17/4, 2016 at 2:26 Comment(8)
It uses ES2015 Promises instead of a library like Q, or bluebird, which have some special functions.Edvard
Seems it's using underscore. You can simplify to files.forEach if files is an array.Melodiemelodion
@GustavoRodrigues great suggestion, didn't realize that es6 had added that.Edvard
Well... it's ES5. The ES6 way would be for (file of files) {...}.Melodiemelodion
Nice solution. I would suggest removing the "function" syntax and use arrow syntax instead, but that's just meUnremitting
You say that you shouldn't use Promise.resolve() to create an already-resolved promise in real life. Why not? Promise.resolve() seems cleaner than new Promise(success => success()).Brindled
@Brindled Sorry, it was just a joke with a play on words ("empty promises.."). Definitely use Promise.resolve(); in your code.Edvard
Nice solution, easy to follow. I didn't enclose mine in a function, so to resolve at the end instead of putting return sequence; I put sequence.then(() => { do stuff });Opiumism
L
35

Addition example

const addTwo = async () => 2;

const addThree = async (inValue) => new Promise((resolve) => setTimeout(resolve(inValue + 3), 2000));

const addFour = (inValue) => new Promise((res) => setTimeout(res(inValue + 4), 1000)); 

const addFive = async (inValue) => inValue + 5;

// Function which handles promises from above
async function sequenceAddition() {
  let sum = await [addTwo, addThree, addFour, addFive].reduce(
    (promise, currPromise) => promise.then((val) => currPromise(val)),
    Promise.resolve()
  );
  console.log('sum:', sum); // 2 + 3 + 4 + 5 =  14
}

// Run function. See console for result.
sequenceAddition();

General syntax to use reduce()

function sequence(tasks, fn) {
    return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}

UPDATE

items-promise is a ready to use NPM package doing the same.

Likeminded answered 19/2, 2017 at 8:0 Comment(3)
I would love to see this explained in greater detail.Cosenza
I provided a variation of this answer with explanation below. ThanksHeffernan
This is exactly what I do in pre-Node 7 environments not having access to async/await. Nice and clean.Benenson
B
13

I've had to run a lot of sequential tasks and used these answers to forge a function that would take care of handling any sequential task...

function one_by_one(objects_array, iterator, callback) {
    var start_promise = objects_array.reduce(function (prom, object) {
        return prom.then(function () {
            return iterator(object);
        });
    }, Promise.resolve()); // initial
    if(callback){
        start_promise.then(callback);
    }else{
        return start_promise;
    }
}

The function takes 2 arguments + 1 optional. First argument is the array on which we will be working. The second argument is the task itself, a function that returns a promise, the next task will be started only when this promise resolves. The third argument is a callback to run when all tasks have been done. If no callback is passed, then the function returns the promise it created so we can handle the end.

Here's an example of usage:

var filenames = ['1.jpg','2.jpg','3.jpg'];
var resize_task = function(filename){
    //return promise of async resizing with filename
};
one_by_one(filenames,resize_task );

Hope it saves someone some time...

Barchan answered 13/7, 2016 at 19:30 Comment(2)
Incredible solution, it's been the best one I've found in almost a week of stuggling.... It is very well explained, has logical inner names, a good example (could be better), I can call for it safely as many times as needed, and it includes the option to set callbacks. simply NICE! (Just changed the name to something that makes me more sense).... RECOMMENDATION for others... you can iterate an object using 'Object.keys(myObject)' as your 'objects_array'Alarise
Thanks for your comment! I am not using that name either, but I wanted to make it more obvious/simple here.Barchan
E
12

With Async/Await (if you have the support of ES7)

function downloadFile(fileUrl) { ... } // This function return a Promise

async function main()
{
  var filesList = [...];

  for (const file of filesList) {
    await downloadFile(file);
  }
}

(you must use for loop, and not forEach because async/await has problems running in forEach loop)

Without Async/Await (using Promise)

function downloadFile(fileUrl) { ... } // This function return a Promise

function downloadRecursion(filesList, index)
{
  index = index || 0;
  if (index < filesList.length)
  {
    downloadFile(filesList[index]).then(function()
    {
      index++;
      downloadRecursion(filesList, index); // self invocation - recursion!
    });
  }
  else
  {
    return Promise.resolve();
  }
}

function main()
{
  var filesList = [...];
  downloadRecursion(filesList);
}
Entasis answered 29/10, 2018 at 17:0 Comment(2)
Await inside forEach is not recommended.Aerophobia
@MarceloAgimóvel - I have updated to solution to not work with forEach (according to this)Entasis
T
9

First, you need to understand that a promise is executed at the time of creation.
So for example if you have a code:

["a","b","c"].map(x => returnsPromise(x))

You need to change it to:

["a","b","c"].map(x => () => returnsPromise(x))

Then we need to sequentially chain promises:

["a", "b", "c"].map(x => () => returnsPromise(x))
    .reduce(
        (before, after) => before.then(_ => after()),
        Promise.resolve()
    )

executing after(), will make sure that promise is created (and executed) only when its time comes.

Tenon answered 23/12, 2019 at 10:43 Comment(1)
If your promise has values you can collect them while chaining the promises .reduce( (before, after) => before.then(results => after().then(result => [...results, result])),Promise.reolve([]))Bonita
A
8

My preferred solution:

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

It's not fundamentally different from others published here but:

  • Applies the function to items in series
  • Resolves to an array of results
  • Doesn't require async/await (support is still quite limited, circa 2017)
  • Uses arrow functions; nice and concise

Example usage:

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

Tested on reasonable current Chrome (v59) and NodeJS (v8.1.2).

Aubergine answered 1/8, 2017 at 4:51 Comment(1)
I prefer this to the solution that mutates the array prototype; it is a clear functional approach using the applicative patternIr
C
6

Nicest solution that I was able to figure out was with bluebird promises. You can just do Promise.resolve(files).each(fs.readFileAsync); which guarantees that promises are resolved sequentially in order.

Circuitous answered 6/5, 2015 at 7:32 Comment(3)
Even better: Promise.each(filtes, fs.readFileAsync). Btw, don't you have to do .bind(fs)?Cha
Nobody here seems to understand the difference between an array and a sequence, that the latter implies unlimited/dynamic size.Deathlike
Note that Arrays in Javascript have nothing to do with fixed size arrays in C style languages. They are just objects with numerical key management bolted on, and have no prescribed size or limit (especially not when using new Array(int). All that does is preset the length key-value pair, affecting how many indices are used during length-based iteration. It has zero effect on the actual array's indexing or index bounds)Hudspeth
M
6

With async/await of ES2016 (and maybe some features of ES2018), this can be reduced to this form:

function readFile(file) {
  ... // Returns a promise.
}

async function readFiles(files) {
  for (file in files) {
     await readFile(file)
  }
}

I haven't seen another answer express that simplicity. The OP said parallel execution of readFile was not desired. However, with IO like this it really makes sense to not be blocking on a single file read, while keeping the loop execution synchronous (you don't want to do the next step until all files have been read). Since I just learned about this and am a bit excited about it, I'll share that approach of parallel asynchronous execution of readFile with overall synchronous execution of readFiles.

async function readFiles(files) {
  await Promise.all(files.map(readFile))
}

Isn't that a thing of beauty?

Maricruzmaridel answered 8/2, 2021 at 17:44 Comment(1)
I cringe when I see awaits that aren't resolved. Also what's the point of having readFiles as async when you can just implicitly return the promise.all?Canberra
H
5

This is a slight variation of another answer above. Using native Promises:

function inSequence(tasks) {
    return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}

Explanation

If you have these tasks [t1, t2, t3], then the above is equivalent to Promise.resolve().then(t1).then(t2).then(t3). It's the behavior of reduce.

How to use

First You need to construct a list of tasks! A task is a function that accepts no argument. If you need to pass arguments to your function, then use bind or other methods to create a task. For example:

var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)
Heffernan answered 10/7, 2017 at 22:21 Comment(0)
Z
4

Most of the answers dont include the results of ALL promises individually, so in case someone is looking for this particular behaviour, this is a possible solution using recursion.

It follows the style of Promise.all:

  • Returns the array of results in the .then() callback.

  • If some promise fails, its returned immediately in the .catch() callback.

const promiseEach = (arrayOfTasks) => {
  let results = []
  return new Promise((resolve, reject) => {
    const resolveNext = (arrayOfTasks) => {
      // If all tasks are already resolved, return the final array of results
      if (arrayOfTasks.length === 0) return resolve(results)

      // Extract first promise and solve it
      const first = arrayOfTasks.shift()

      first().then((res) => {
        results.push(res)
        resolveNext(arrayOfTasks)
      }).catch((err) => {
        reject(err)
      })
    }
    resolveNext(arrayOfTasks)
  })
}

// Lets try it 😎

const promise = (time, shouldThrowError) => new Promise((resolve, reject) => {
  const timeInMs = time * 1000
  setTimeout(()=>{
    console.log(`Waited ${time} secs`)
    if (shouldThrowError) reject(new Error('Promise failed'))
    resolve(time)
  }, timeInMs)
})

const tasks = [() => promise(1), () => promise(2)]

promiseEach(tasks)
  .then((res) => {
    console.log(res) // [1, 2]
  })
  // Oops some promise failed
  .catch((error) => {
    console.log(error)
  })

Note about the tasks array declaration:

In this case is not possible to use the following notation like Promise.all would use:

const tasks = [promise(1), promise(2)]

And we have to use:

const tasks = [() => promise(1), () => promise(2)]

The reason is that JavaScript starts executing the promise immediatelly after its declared. If we use methods like Promise.all, it just checks that the state of all of them is fulfilled or rejected, but doesnt start the exection itself. Using () => promise() we stop the execution until its called.

Zhukov answered 26/1, 2020 at 14:23 Comment(1)
Thanks a lot! This solution solved my error handling problem when dynamically chaining promises in sequence.Endorse
G
3

I created this simple method on the Promise object:

Create and add a Promise.sequence method to the Promise object

Promise.sequence = function (chain) {
    var results = [];
    var entries = chain;
    if (entries.entries) entries = entries.entries();
    return new Promise(function (yes, no) {
        var next = function () {
            var entry = entries.next();
            if(entry.done) yes(results);
            else {
                results.push(entry.value[1]().then(next, function() { no(results); } ));
            }
        };
        next();
    });
};

Usage:

var todo = [];

todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);

// Invoking them
Promise.sequence(todo)
    .then(function(results) {}, function(results) {});

The best thing about this extension to the Promise object, is that it is consistent with the style of promises. Promise.all and Promise.sequence is invoked the same way, but have different semantics.

Caution

Sequential running of promises is not usually a very good way to use promises. It's usually better to use Promise.all, and let the browser run the code as fast as possible. However, there are real use cases for it - for example when writing a mobile app using javascript.

Grateful answered 29/7, 2015 at 12:32 Comment(7)
No, you cannot compare Promise.all and your Promise.sequence. One does take an iterable of promises, the other takes an array of functions that return promises.Cha
Btw, I'd recommend to avoid the promise constructor antipatternCha
Didn't know that it took an iterator. Should be easy enough to rewrite it though. Could you elaborate why this is the promise constructor antipattern? I did read your post here:https://mcmap.net/q/36569/-what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-itGrateful
@Cha I've updated the code to support iterators. I still don't see that this is an antipattern. Antipatterns generally are to be considered guidelines to avoid coding mistakes, and it's perfectly valid to create (library) functions that break those guidelines.Grateful
Yeah, if you consider it a library function it's OK, but still in this case a reduce like in Benjamin's answer is just much simpler.Cha
I didn't mean that Promise.sequence would need to take an iterable, but that your sentence "Promise.all and Promise.sequence is invoked the same way" is wrong. The one needs promises, the other needs functions, and your example snippet (with firstPromise, optionalPromise and lastPromise) is misleading.Cha
@Cha Thank you for your feedback. I guess what's "simple" is in the eye of the beholder. I prefer my variant over the reduce version.Grateful
M
3

My answer based on https://mcmap.net/q/80794/-resolve-promises-one-after-another-i-e-in-sequence.

Promise.series = function series(arrayOfPromises) {
    var results = [];
    return arrayOfPromises.reduce(function(seriesPromise, promise) {
      return seriesPromise.then(function() {
        return promise
        .then(function(result) {
          results.push(result);
        });
      });
    }, Promise.resolve())
    .then(function() {
      return results;
    });
  };

This solution returns the results as an array like Promise.all().

Usage:

Promise.series([array of promises])
.then(function(results) { 
  // do stuff with results here
});
Marimaria answered 12/4, 2017 at 19:1 Comment(0)
C
3

Use Array.prototype.reduce, and remember to wrap your promises in a function otherwise they will already be running!

// array of Promise providers

const providers = [
  function(){
     return Promise.resolve(1);
  },
  function(){
     return Promise.resolve(2);
  },
  function(){
     return Promise.resolve(3);
  }
]


const inSeries = function(providers){

  const seed = Promise.resolve(null); 

  return providers.reduce(function(a,b){
      return a.then(b);
  }, seed);
};

nice and easy... you should be able to re-use the same seed for performance, etc.

It's important to guard against empty arrays or arrays with only 1 element when using reduce, so this technique is your best bet:

   const providers = [
      function(v){
         return Promise.resolve(v+1);
      },
      function(v){
         return Promise.resolve(v+2);
      },
      function(v){
         return Promise.resolve(v+3);
      }
    ]

    const inSeries = function(providers, initialVal){

        if(providers.length < 1){
            return Promise.resolve(null)
        }

        return providers.reduce((a,b) => a.then(b), providers.shift()(initialVal));
    };

and then call it like:

inSeries(providers, 1).then(v => {
   console.log(v);  // 7
});
Confined answered 12/12, 2017 at 19:42 Comment(1)
The main limitation of this approach is that reduce has no (simple) way to exit on error. If you need your sequence of Promises to stop execution if an error is encountered, then you'll need a different solution such as for of.Dalston
A
3

I find myself coming back to this question many times and the answers aren't exactly giving me what I need, so putting this here for anyone that needs this too.

The code below does sequential promises execution (one after another), and each round consists of multiple callings:

async function sequence(list, cb) {
  const result = [];
  await list.reduce(async (promise, item) => promise
    .then(() => cb(item))
    .then((res) => result.push(res)
  ), Promise.resolve());
  return result;
}

Showcase:

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script type="text/babel">

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function readFile(url, index) {
  console.log('Running index: ', index);

  // First action
  const firstTime = await axios.get(url);
  console.log('First API response: ', firstTime.data.activity);
  
  // Second action
  await sleep(1000);
  
  // Third action
  const secondTime = await axios.get(url);
  console.log('Second API response: ', secondTime.data.activity);

  // Fourth action
  await sleep(1000);

  return secondTime.data;
}

async function sequence(urls, fn) {
  const result = [];
  await urls.reduce(async (promise, url, index) => promise.then(() => fn(url, index)).then((res) => result.push(res)), Promise.resolve());
  return result;
}

const urls = [
  'https://www.boredapi.com/api/activity',
  'https://www.boredapi.com/api/activity',
  'https://www.boredapi.com/api/activity',
];

(async function init() {
  const result = await sequence(urls, readFile);
  console.log('result', result);
})()

</script>
Appropriation answered 11/1, 2023 at 2:19 Comment(0)
H
2

You can use this function that gets promiseFactories List:

function executeSequentially(promiseFactories) {
    var result = Promise.resolve();
    promiseFactories.forEach(function (promiseFactory) {
        result = result.then(promiseFactory);
    });
    return result;
}

Promise Factory is just simple function that returns a Promise:

function myPromiseFactory() {
    return somethingThatCreatesAPromise();
}

It works because a promise factory doesn't create the promise until it's asked to. It works the same way as a then function – in fact, it's the same thing!

You don't want to operate over an array of promises at all. Per the Promise spec, as soon as a promise is created, it begins executing. So what you really want is an array of promise factories...

If you want to learn more on Promises, you should check this link: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

Hyams answered 22/12, 2016 at 16:24 Comment(0)
U
2

If you want you can use reduce to make a sequential promise, for example:

[2,3,4,5,6,7,8,9].reduce((promises, page) => {
    return promises.then((page) => {
        console.log(page);
        return Promise.resolve(page+1);
    });
  }, Promise.resolve(1));

it'll always works in sequential.

Ululant answered 10/2, 2017 at 15:34 Comment(0)
M
2

I really liked @joelnet's answer, but to me, that style of coding is a little bit tough to digest, so I spent a couple of days trying to figure out how I would express the same solution in a more readable manner and this is my take, just with a different syntax and some comments.

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const functions = urls.map((url) => {
  // For every url we return a new function
  return () => {
    return new Promise((resolve) => {
      // random wait in milliseconds
      const randomWait = parseInt((Math.random() * 1000),10)
      console.log('waiting to resolve in ms', randomWait)
      setTimeout(()=>resolve({randomWait, url}),randomWait)
    })
  }
})


const promiseReduce = (acc, next) => {
  // we wait for the accumulator to resolve it's promise
  return acc.then((accResult) => {
    // and then we return a new promise that will become
    // the new value for the accumulator
    return next().then((nextResult) => {
      // that eventually will resolve to a new array containing
      // the value of the two promises
      return accResult.concat(nextResult)
    })
  })
};
// the accumulator will always be a promise that resolves to an array
const accumulator = Promise.resolve([])

// we call reduce with the reduce function and the accumulator initial value
functions.reduce(promiseReduce, accumulator)
  .then((result) => {
    // let's display the final value here
    console.log('=== The final result ===')
    console.log(result)
  })
Murry answered 29/8, 2017 at 16:15 Comment(0)
S
2

As Bergi noticed, I think the best and clear solution is use BlueBird.each, code below:

const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);
Secretarial answered 18/11, 2018 at 15:41 Comment(0)
D
2

Using modern ES:

const series = async (tasks) => {
  const results = [];

  for (const task of tasks) {
    const result = await task;

    results.push(result);
  }

  return results;
};

//...

const readFiles = await series(files.map(readFile));
Dendritic answered 19/11, 2019 at 11:20 Comment(0)
S
1

I use the following code to extend the Promise object. It handles rejection of the promises and returns an array of results

Code

/*
    Runs tasks in sequence and resolves a promise upon finish

    tasks: an array of functions that return a promise upon call.
    parameters: an array of arrays corresponding to the parameters to be passed on each function call.
    context: Object to use as context to call each function. (The 'this' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
    return new Promise((resolve, reject)=>{

        var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
        var output = new Array(tasks.length + 1);
        var errorFlag = false;

        tasks.forEach((task, index) => {
            nextTask = nextTask.then(r => {
                output[index] = r;
                return task.apply(context, parameters[index+1]);
            }, e=>{
                output[index] = e;
                errorFlag = true;
                return task.apply(context, parameters[index+1]);
            });
        });

        // Last task
        nextTask.then(r=>{
            output[output.length - 1] = r;
            if (errorFlag) reject(output); else resolve(output);
        })
        .catch(e=>{
            output[output.length - 1] = e;
            reject(output);
        });
    });
};

Example

function functionThatReturnsAPromise(n) {
    return new Promise((resolve, reject)=>{
        //Emulating real life delays, like a web request
        setTimeout(()=>{
            resolve(n);
        }, 1000);
    });
}

var arrayOfArguments = [['a'],['b'],['c'],['d']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);


Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);
Salesgirl answered 4/1, 2017 at 5:30 Comment(0)
M
1

Your approach is not bad, but it does have two issues: it swallows errors and it employs the Explicit Promise Construction Antipattern.

You can solve both of these issues, and make the code cleaner, while still employing the same general strategy:

var Q = require("q");

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  var readSequential = function(index) {
    if (index < files.length) {
      return readFile(files[index]).then(function() {
        return readSequential(index + 1);
      });
    }
  };

  // using Promise.resolve() here in case files.length is 0
  return Promise.resolve(readSequential(0)); // Start!
};
Masry answered 30/3, 2017 at 11:13 Comment(0)
C
1

This is my sequentially implementation that I use in various projects:

const file = [file1, file2, file3];
const fileContents = sequentially(readFile, files);

// somewhere else in the code:

export const sequentially = async <T, P>(
  toPromise: (element: T) => Promise<P>,
  elements: T[]
): Promise<P[]> => {
  const results: P[] = [];
  await elements.reduce(async (sequence, element) => {
    await sequence;
    results.push(await toPromise(element));
  }, Promise.resolve());

  return results;
};

Cynthla answered 30/7, 2021 at 12:19 Comment(0)
M
1

Here is my Angular/TypeScript approach, using RxJS:

  1. Given an array of URL strings, convert it into an Observable using the from function.
  2. Use pipe to wrap the Ajax request, immediate response logic, any desired delay, and error handling.
  3. Inside of the pipe, use concatMap to serialize the requests. Otherwise, using Javascript forEach or map would make the requests at the same time.
  4. Use RxJS ajax to make the call, and also to add any desired delay after each call returns.

Working example: https://stackblitz.com/edit/rxjs-bnrkix?file=index.ts

The code looks like this (I left in some extras so you can choose what to keep or discard):

import { ajax } from 'rxjs/ajax';
import { catchError, concatMap, delay, from, of, map, Observable } from 'rxjs';

const urls = [
  'https://randomuser.me/api/',
  'https://randomuser.me/api/',
  'https://randomuser.me/api/',
];
const delayAfterCall = 500;

from(urls)
  .pipe(
    concatMap((url: string) => {
      return ajax.getJSON(url).pipe(
        map((response) => {
          console.log('Done! Received:', response);
          return response;
        }),
        catchError((error) => {
          console.error('Error: ', error);
          return of(error);
        }),
        delay(delayAfterCall)
      );
    })
  )
  .subscribe((response) => {
    console.log('received email:', response.results[0].email);
  });
Mercedezmerceer answered 27/6, 2022 at 23:1 Comment(0)
M
0

On the basis of the question's title, "Resolve promises one after another (i.e. in sequence)?", we might understand that the OP is more interested in the sequential handling of promises on settlement than sequential calls per se.

This answer is offered :

  • to demonstrate that sequential calls are not necessary for sequential handling of responses.
  • to expose viable alternative patterns to this page's visitors - including the OP if he is still interested over a year later.
  • despite the OP's assertion that he does not want to make calls concurrently, which may genuinely be the case but equally may be an assumption based on the desire for sequential handling of responses as the title implies.

If concurrent calls are genuinely not wanted then see Benjamin Gruenbaum's answer which covers sequential calls (etc) comprehensively.

If however, you are interested (for improved performance) in patterns which allow concurrent calls followed by sequential handling of responses, then please read on.

It's tempting to think you have to use Promise.all(arr.map(fn)).then(fn) (as I have done many times) or a Promise lib's fancy sugar (notably Bluebird's), however (with credit to this article) an arr.map(fn).reduce(fn) pattern will do the job, with the advantages that it :

  • works with any promise lib - even pre-compliant versions of jQuery - only .then() is used.
  • affords the flexibility to skip-over-error or stop-on-error, whichever you want with a one line mod.

Here it is, written for Q.

var readFiles = function(files) {
    return files.map(readFile) //Make calls in parallel.
    .reduce(function(sequence, filePromise) {
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

Note: only that one fragment, Q(), is specific to Q. For jQuery you need to ensure that readFile() returns a jQuery promise. With A+ libs, foreign promises will be assimilated.

The key here is the reduction's sequence promise, which sequences the handling of the readFile promises but not their creation.

And once you have absorbed that, it's maybe slightly mind-blowing when you realise that the .map() stage isn't actually necessary! The whole job, parallel calls plus serial handling in the correct order, can be achieved with reduce() alone, plus the added advantage of further flexibility to :

  • convert from parallel async calls to serial async calls by simply moving one line - potentially useful during development.

Here it is, for Q again.

var readFiles = function(files) {
    return files.reduce(function(sequence, f) {
        var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

That's the basic pattern. If you wanted also to deliver data (eg the files or some transform of them) to the caller, you would need a mild variant.

Marshy answered 9/9, 2015 at 1:47 Comment(5)
I don't think it's a good idea to answer questions contrary to the OPs intentions…Cha
This sequence.then(() => filePromise) thing is an antipattern - it does not propagate errors as soon as they could (and creates unhandledRejection in libs that support them). You rather should use Q.all([sequence, filePromise]) or $.when(sequence, filePromise). Admittedly, this behaviour might be what you want when you aim to ignore or skip errors, but you should at least mention this as a disadvantage.Cha
@Bergi, I'm hoping the OP will step in and provide judgement on whether this is truly contrary to his intentions or not. If not, I'll delete the answer I guess, meanwhile I hope I've justified my position. Thanks for taking it seriously enough to provide decent feedback. Can you explain more about the anti-pattern, or provide a reference please? Does the same apply to the article where I found the basic pattern?Marshy
Yes, the third version of his code (that is "both parallel and sequential") has the same problem. The "antipattern" needs sophisticated error handling and is prone to attach handlers asynchronously, which causes unhandledRejection events. In Bluebird you can work around this by using sequence.return(filePromise) which has the same behaviour but handles rejections fine. I don't know any reference, I've just come up with it - I don't think the "(anti)pattern" has a name yet.Cha
@Bergi, you can clearly see something I can't :( I wonder if this new anti-pattern needs to be documented somewhere?Marshy
R
0

If someone else needs a guaranteed way of STRICTLY sequential way of resolving Promises when performing CRUD operations you also can use the following code as a basis.

As long as you add 'return' before calling each function, describing a Promise, and use this example as a basis the next .then() function call will CONSISTENTLY start after the completion of the previous one:

getRidOfOlderShoutsPromise = () => {
    return readShoutsPromise('BEFORE')
    .then(() => {
        return deleteOlderShoutsPromise();
    })
    .then(() => {
        return readShoutsPromise('AFTER')
    })
    .catch(err => console.log(err.message));
}

deleteOlderShoutsPromise = () => {
    return new Promise ( (resolve, reject) => {
        console.log("in deleteOlderShouts");
        let d = new Date();
        let TwoMinuteAgo = d - 1000 * 90 ;
        All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
            if (err) reject();
            console.log("DELETED OLDs at "+d);
            resolve();        
        });
    });
}

readShoutsPromise = (tex) => {
    return new Promise( (resolve, reject) => {
        console.log("in readShoutsPromise -"+tex);
        All_Shouts
        .find({})
        .sort([['dateTime', 'ascending']])
        .exec(function (err, data){
            if (err) reject();
            let d = new Date();
            console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
            resolve(data);
        });    
    });
}
Rotarian answered 21/6, 2018 at 14:46 Comment(0)
J
0

Array push and pop method can be used for sequence of promises. You can also push new promises when you need additional data. This is the code, I will use in React Infinite loader to load sequence of pages.

var promises = [Promise.resolve()];

function methodThatReturnsAPromise(page) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(`Resolve-${page}! ${new Date()} `);
			resolve();
		}, 1000);
	});
}

function pushPromise(page) {
	promises.push(promises.pop().then(function () {
		return methodThatReturnsAPromise(page)
	}));
}

pushPromise(1);
pushPromise(2);
pushPromise(3);
Juliannajulianne answered 12/5, 2019 at 13:13 Comment(0)
S
0

There's promise-sequence in nodejs.

const promiseSequence = require('promise-sequence');
return promiseSequence(arr.map(el => () => doPromise(el)));
Scrofula answered 6/1, 2021 at 14:31 Comment(1)
You mean a third party NPM package exists in the NPM ecosystem called promise-sequence.Aesthesia
K
0

There's an npm package Promise Serial that does this quite nicely:

const Promise_serial = require('promise-serial');

 const promises =
    Array(15).fill()
    .map((_, i) =>
        () => new Promise(resolve => {
            console.log('promise '+i+' start');
            setTimeout(
                () => {
                    console.log('promise '+i+' end');
                    resolve('output-'+i);
                },
                500
            );
        })
    );


console.log('### Run promises in sequence')

Promise_serial(promises)

Outputs:

promise 0 start
promise 0 end
promise 1 start
promise 1 end
promise 2 start
promise 2 end
promise 3 start
promise 3 end
promise 4 start
promise 4 end
promise 5 start
promise 5 end
promise 6 start
promise 6 end
promise 7 start
promise 7 end

... etc

You can also batch or parallelize them.

See: https://www.npmjs.com/package/promise-serial

Karolkarola answered 22/10, 2021 at 5:23 Comment(0)
T
0

I wanted to echo what many have said in that, the best way of solving this is using async/await functions. I wanted to reiterate that solution, but, also indicate another solution where async/await isn't present:

function readFile(file) {
    return new Promise(function (resolve, reject) {
        console.log(`Simulate reading of file ${file}`);
        setTimeout(resolve, 1000);
    } );
}

(async function() {
    let files = [ "file1.txt", "file2.txt", "file3.txt" ];
    for (let file of files)
        await readFile(file);
})();

For JavaScript environments that do not support async/await we have the option to use https://babeljs.io to transpile the above but using a plugin such as babel-plugin-transform-async-to-generator. The following was generated using v6.24.1 of the plugin:

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

function readFile(file) {
  return new Promise(function (resolve, reject) {
    console.log(`Simulate reading of file ${file}`);
    setTimeout(resolve, 1000);
  });
}

_asyncToGenerator(function* () {
  let files = ["file1.txt", "file2.txt", "file3.txt"];
  for (let file of files)
    yield readFile(file);
})();

If you study what happens in _asyncToGenerator() you see that it is consuming a Promise chain using recursion. You supply that Promise through that function generator hence, you can focus on the business logic.

Thi answered 30/8, 2022 at 6:11 Comment(0)
C
0

Clean version using lodash.flow

// Example promises
const promise1 = async () => {
  await new Promise((resolve) => {
    setTimeout(() => resolve(), 1000);
  });
  return [{
    name: 'John',
    age: 30
  }];
};

const promise2 = async (result) => {
  await new Promise((resolve) => {
    setTimeout(() => resolve(), 2000);
  });
  return [...result, {
    name: 'Jane',
    age: 25
  }];
};

const promise3 = async (result) => {
  await new Promise((resolve) => {
    setTimeout(() => resolve(), 1500);
  });
  return [...result, {
    name: 'Bob',
    age: 40
  }];
};

// Array of promises
const promises = [promise1, promise2, promise3];

// Execute promises sequentially and combine results
const executePromisesSequentially = _.flow(
  ...promises.map((promise) => async (result) => promise(await result))
);

// Invoke the sequential execution
(async () => {
  try {
    const finalResult = await executePromisesSequentially();
    console.log('Final Result:', finalResult);
  } catch (error) {
    console.error('Error:', error);
  }
})();

Output:

Final Result: 
[
  {name: 'John', age: 30}
  {name: 'Jane', age: 25}
  {name: 'Bob', age: 40}
]
Compony answered 20/5, 2023 at 14:0 Comment(0)
D
-1

This is to extend on how to process a sequence of promises in a more generic way, supporting dynamic / infinite sequences, based on spex.sequence implementation:

var $q = require("q");
var spex = require('spex')($q);

var files = []; // any dynamic source of files;

var readFile = function (file) {
    // returns a promise;
};

function source(index) {
    if (index < files.length) {
        return readFile(files[index]);
    }
}

function dest(index, data) {
    // data = resolved data from readFile;
}

spex.sequence(source, dest)
    .then(function (data) {
        // finished the sequence;
    })
    .catch(function (error) {
        // error;
    });

Not only this solution will work with sequences of any size, but you can easily add data throttling and load balancing to it.

Deathlike answered 28/10, 2015 at 18:1 Comment(0)
I
-1

Lots of answers here but I didn't see this simple solution:

await array.reduce(
  async (promise, member) => await myLongSequentialPromise(member),
  array[0]
)

Proof: https://jsbin.com/nulafus/1/edit?js,console

Ignite answered 27/2, 2019 at 23:29 Comment(0)
A
-1
(function() {
  function sleep(ms) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        return resolve();
      }, ms);
    });
  }

  function serial(arr, index, results) {
    if (index == arr.length) {
      return Promise.resolve(results);
    }
    return new Promise(function(resolve, reject) {
      if (!index) {
        index = 0;
        results = [];
      }
      return arr[index]()
        .then(function(d) {
          return resolve(d);
        })
        .catch(function(err) {
          return reject(err);
        });
    })
      .then(function(result) {
        console.log("here");
        results.push(result);
        return serial(arr, index + 1, results);
      })
      .catch(function(err) {
        throw err;
      });
  }

  const a = [5000, 5000, 5000];

  serial(a.map(x => () => sleep(x)));
})();

Here the key is how you call the sleep function. You need to pass an array of functions which itself returns a promise instead of an array of promises.

Asa answered 26/2, 2020 at 17:4 Comment(1)
How does using sleep help run the commands serially rather than in parallel?Aesthesia

© 2022 - 2024 — McMap. All rights reserved.