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

Upvotes: 435

Views: 380893

Answers (30)

sdgfsdh
sdgfsdh

Reputation: 37105

Using modern ES:

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

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

    results.push(result);
  }

  return results;
};

//...

const tasks = files.map(file => {
  return () => readFile(file);
});

const readFiles = await series(tasks);

Note that promises are "hot" in JS so to defer each task from starting until the previous task completes, we wrap it in a lambda.

Upvotes: 3

Jeff Voss
Jeff Voss

Reputation: 3695

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}
]

Upvotes: -1

Jee Mok
Jee Mok

Reputation: 6566

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>

Upvotes: 4

Stephen Quan
Stephen Quan

Reputation: 26179

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.

Upvotes: 0

Nate
Nate

Reputation: 1274

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

Upvotes: 1

Pooya
Pooya

Reputation: 882

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.

Upvotes: 35

Rimian
Rimian

Reputation: 38428

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

Upvotes: 0

DamianoPantani
DamianoPantani

Reputation: 1386

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

Upvotes: 1

Kevin Buchs
Kevin Buchs

Reputation: 2872

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?

Upvotes: 7

whistling_marmot
whistling_marmot

Reputation: 3893

There's promise-sequence in nodejs.

const promiseSequence = require('promise-sequence');
return promiseSequence(arr.map(el => () => doPromise(el)));

Upvotes: 0

vitaly-t
vitaly-t

Reputation: 25930

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.

Upvotes: -1

Shubham Negi
Shubham Negi

Reputation: 84

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

Upvotes: -1

Rashomon
Rashomon

Reputation: 6772

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.

Upvotes: 5

Gil Epshtain
Gil Epshtain

Reputation: 9811

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

Upvotes: 12

oneat
oneat

Reputation: 10994

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.

Upvotes: 9

Shridhar Gupta
Shridhar Gupta

Reputation: 914

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

Upvotes: 53

Muthu Kumar
Muthu Kumar

Reputation: 480

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

Upvotes: 0

Alexander Mills
Alexander Mills

Reputation: 100240

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

Upvotes: 3

dimiguel
dimiguel

Reputation: 1579

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

Upvotes: -1

jtianling
jtianling

Reputation: 1997

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

Upvotes: 2

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276496

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.

Upvotes: 506

Ula
Ula

Reputation: 2748

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

Upvotes: 0

Gabriel Acosta
Gabriel Acosta

Reputation: 111

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

Upvotes: 2

Molomby
Molomby

Reputation: 6589

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

Upvotes: 8

Sarsaparilla
Sarsaparilla

Reputation: 6680

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(...)

Upvotes: 5

Jason Suttles
Jason Suttles

Reputation: 33

My answer based on https://stackoverflow.com/a/31070150/7542429.

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

Upvotes: 3

JLRishe
JLRishe

Reputation: 101738

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

Upvotes: 1

Victor Castro
Victor Castro

Reputation: 263

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.

Upvotes: 2

josemontesp
josemontesp

Reputation: 31

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

Upvotes: 1

sidanmor
sidanmor

Reputation: 5189

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

Upvotes: 2

Related Questions