javascriptpromiseqsequentialserial-processing

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


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

Solution

  • 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.