I have an array of promise objects that must be resolved in the same sequence in which they are listed in the array, i.e. we cannot attempt resolving an element till the previous one has been resolved (as method Promise.all([...])
does).
And if one element is rejected, I need the chain to reject at once, without attempting to resolve the following element.
How can I implement this, or is there an existing implementation for such sequence
pattern?
function sequence(arr) { return new Promise(function (resolve, reject) { // try resolving all elements in 'arr', // but strictly one after another; }); }
EDIT
The initial answers suggest we can only sequence
results of such array elements, not their execution, because it is predefined in such example.
But then how to generate an array of promises in such a way as to avoid early execution?
Here's a modified example:
function sequence(nextPromise) { // while nextPromise() creates and returns another promise, // continue resolving it; }
I wouldn't want to make it into a separate question, because I believe it is part of the same problem.
SOLUTION
Some answers below and discussions that followed went a bit astray, but the eventual solution that did exactly what I was looking for was implemented within spex library, as method sequence. The method can iterate through a sequence of dynamic length, and create promises as required by the business logic of your application.
Later on I turned it into a shared library for everyone to use.
Putting it all together: 'use strict'; let asyncTask = () => new Promise(resolve => { let delay = Math. floor(Math. random() * 100); setTimeout(function () { resolve(delay); }, delay); }); let makeMeLookSync = fn => { let iterator = fn(); let loop = result => { !
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.
A promise is used to handle the asynchronous result of an operation. JavaScript is designed to not wait for an asynchronous block of code to completely execute before other synchronous parts of the code can run. With Promises, we can defer the execution of a code block until an async request is completed.
Here are some simple examples for how you sequence through an array executing each async operation serially (one after the other).
Let's suppose you have an array of items:
var arr = [...];
And, you want to carry out a specific async operation on each item in the array, one at a time serially such that the next operation does not start until the previous one has finished.
And, let's suppose you have a promise returning function for processing one of the items in the array fn(item)
:
function processItem(item) { // do async operation and process the result // return a promise }
Then, you can do something like this:
function processArray(array, fn) { var index = 0; function next() { if (index < array.length) { fn(array[index++]).then(next); } } next(); } processArray(arr, processItem);
If you wanted a promise returned from processArray()
so you'd know when it was done, you could add this to it:
function processArray(array, fn) { var index = 0; function next() { if (index < array.length) { return fn(array[index++]).then(function(value) { // apply some logic to value // you have three options here: // 1) Call next() to continue processing the result of the array // 2) throw err to stop processing and result in a rejected promise being returned // 3) return value to stop processing and result in a resolved promise being returned return next(); }); } } else { // return whatever you want to return when all processing is done // this returne value will be the ersolved value of the returned promise. return "all done"; } } processArray(arr, processItem).then(function(result) { // all done here console.log(result); }, function(err) { // rejection happened console.log(err); });
Note: this will stop the chain on the first rejection and pass that reason back to the processArray returned promise.
If you wanted to do more of the work with promises, you could chain all the promises:
function processArray(array, fn) { return array.reduce(function(p, item) { return p.then(function() { return fn(item); }); }, Promise.resolve()); } processArray(arr, processItem).then(function(result) { // all done here }, function(reason) { // rejection happened });
Note: this will stop the chain on the first rejection and pass that reason back to the promise returned from processArray()
.
For a success scenario, the promise returned from processArray()
will be resolved with the last resolved value of your fn
callback. If you wanted to accumulate a list of results and resolve with that, you could collect the results in a closure array from fn
and continue to return that array each time so the final resolve would be an array of results.
And, since it now seems apparent that you want the final promise result to be an array of data (in order), here's a revision of the previous solution that produces that:
function processArray(array, fn) { var results = []; return array.reduce(function(p, item) { return p.then(function() { return fn(item).then(function(data) { results.push(data); return results; }); }); }, Promise.resolve()); } processArray(arr, processItem).then(function(result) { // all done here // array of data here in result }, function(reason) { // rejection happened });
Working demo: http://jsfiddle.net/jfriend00/h3zaw8u8/
And a working demo that shows a rejection: http://jsfiddle.net/jfriend00/p0ffbpoc/
And, if you want to insert a small delay between operations:
function delay(t, v) { return new Promise(function(resolve) { setTimeout(resolve.bind(null, v), t); }); } function processArrayWithDelay(array, t, fn) { var results = []; return array.reduce(function(p, item) { return p.then(function() { return fn(item).then(function(data) { results.push(data); return delay(t, results); }); }); }, Promise.resolve()); } processArray(arr, 200, processItem).then(function(result) { // all done here // array of data here in result }, function(reason) { // rejection happened });
The Bluebird promise library has a lot of concurrency controlling features built right in. For example, to sequence iteration through an array, you can use Promise.mapSeries()
.
Promise.mapSeries(arr, function(item) { // process each individual item here, return a promise return processItem(item); }).then(function(results) { // process final results here }).catch(function(err) { // process array here });
Or to insert a delay between iterations:
Promise.mapSeries(arr, function(item) { // process each individual item here, return a promise return processItem(item).delay(100); }).then(function(results) { // process final results here }).catch(function(err) { // process array here });
If you're coding in an environment that supports async/await, you can also just use a regular for
loop and then await
a promise in the loop and it will cause the for
loop to pause until a promise is resolved before proceeding. This will effectively sequence your async operations so the next one doesn't start until the previous one is done.
async function processArray(array, fn) { let results = []; for (let i = 0; i < array.length; i++) { let r = await fn(array[i]); results.push(r); } return results; // will be resolved value of promise } // sample usage processArray(arr, processItem).then(function(result) { // all done here // array of data here in result }, function(reason) { // rejection happened });
FYI, I think my processArray()
function here is very similar to Promise.map()
in the Bluebird promise library which takes an array and a promise producing function and returns a promise that resolves with an array of resolved results.
@vitaly-t - Here some some more detailed comments on your approach. You are welcome to whatever code seems best to you. When I first started using promises, I tended to use promises only for the simplest things they did and write a lot of the logic myself when a more advanced use of promises could do much more of it for me. You use only what you are fully comfortable with and beyond that, you'd rather see your own code that you intimately know. That's probably human nature.
I will suggest that as I understood more and more of what promises can do for me, I now like to write code that uses more of the advanced features of promises and it seems perfectly natural to me and I feel like I'm building on well tested infrastructure that has lots of useful features. I'd only ask that you keep your mind open as you learn more and more to potentially go that direction. It's my opinion that it's a useful and productive direction to migrate as your understanding improves.
Here are some specific points of feedback on your approach:
You create promises in seven places
As a contrast in styles, my code has only two places where I explicitly create a new promise - once in the factory function and once to initialize the .reduce()
loop. Everywhere else, I'm just building on the promises already created by chaining to them or returning values within them or just returning them directly. Your code has seven unique places where you're creating a promise. Now, good coding isn't a contest to see how few places you can create a promise, but that might point out the difference in leverage the promises that are already created versus testing conditions and creating new promises.
Throw-safety is a very useful feature
Promises are throw-safe. That means that an exception thrown within a promise handler will automatically reject that promise. If you just want the exception to become a rejection, then this is a very useful feature to take advantage of. In fact, you will find that just throwing yourself is a useful way to reject from within a handler without creating yet another promise.
Lots of Promise.resolve()
or Promise.reject()
is probably an opportunity for simplification
If you see code with lots of Promise.resolve()
or Promise.reject()
statements, then there are probably opportunities to leverage the existing promises better rather than creating all these new promises.
Cast to a Promise
If you don't know if something returned a promise, then you can cast it to a promise. The promise library will then do it's own checks whether it is a promise or not and even whether it's the kind of promise that matches the promise library you're using and, if not, wrap it into one. This can save rewriting a lot of this logic yourself.
Contract to Return a Promise
In many cases these days, it's completely viable to have a contract for a function that may do something asynchronous to return a promise. If the function just wants to do something synchronous, then it can just return a resolved promise. You seem to feel like this is onerous, but it's definitely the way the wind is blowing and I already write lots of code that requires that and it feels very natural once you get familiar with promises. It abstracts away whether the operation is sync or async and the caller doesn't have to know or do anything special either way. This is a nice use of promises.
The factory function can be written to create one promise only
The factory function can be written to create one promise only and then resolve or reject it. This style also makes it throw safe so any exception occuring in the factory function automatically becomes a reject. It also makes the contract to always return a promise automatic.
While I realize this factory function is a placeholder function (it doesn't even do anything async), hopefully you can see the style to consider it:
function factory(idx) { // create the promise this way gives you automatic throw-safety return new Promise(function(resolve, reject) { switch (idx) { case 0: resolve("one"); break; case 1: resolve("two"); break; case 2: resolve("three"); break; default: resolve(null); break; } }); }
If any of these operations were async, then they could just return their own promises which would automatically chain to the one central promise like this:
function factory(idx) { // create the promise this way gives you automatic throw-safety return new Promise(function(resolve, reject) { switch (idx) { case 0: resolve($.ajax(...)); case 1: resole($.ajax(...)); case 2: resolve("two"); break; default: resolve(null); break; } }); }
Using a reject handler to just return promise.reject(reason)
is not needed
When you have this body of code:
return obj.then(function (data) { result.push(data); return loop(++idx, result); }, function (reason) { return promise.reject(reason); });
The reject handler is not adding any value. You can instead just do this:
return obj.then(function (data) { result.push(data); return loop(++idx, result); });
You are already returning the result of obj.then()
. If either obj
rejects or if anything chained to obj
or returned from then .then()
handler rejects, then obj
will reject. So you don't need to create a new promise with the reject. The simpler code without the reject handler does the same thing with less code.
Here's a version in the general architecture of your code that tries to incorporate most of these ideas:
function factory(idx) { // create the promise this way gives you automatic throw-safety return new Promise(function(resolve, reject) { switch (idx) { case 0: resolve("zero"); break; case 1: resolve("one"); break; case 2: resolve("two"); break; default: // stop further processing resolve(null); break; } }); } // Sequentially resolves dynamic promises returned by a factory; function sequence(factory) { function loop(idx, result) { return Promise.resolve(factory(idx)).then(function(val) { // if resolved value is not null, then store result and keep going if (val !== null) { result.push(val); // return promise from next call to loop() which will automatically chain return loop(++idx, result); } else { // if we got null, then we're done so return results return result; } }); } return loop(0, []); } sequence(factory).then(function(results) { log("results: ", results); }, function(reason) { log("rejected: ", reason); });
Working demo: http://jsfiddle.net/jfriend00/h3zaw8u8/
Some comments about this implementation:
Promise.resolve(factory(idx))
essentially casts the result of factory(idx)
to a promise. If it was just a value, then it becomes a resolved promise with that return value as the resolve value. If it was already a promise, then it just chains to that promise. So, it replaces all your type checking code on the return value of the factory()
function.
The factory function signals that it is done by returning either null
or a promise whose resolved value ends up being null
. The above cast maps those two conditions to the same resulting code.
The factory function catches exceptions automatically and turns them into rejects which are then handled automatically by the sequence()
function. This is one significant advantage of letting promises do a lot of your error handling if you just want to abort processing and feed the error back on the first exception or rejection.
The factory function in this implementation can return either a promise or a static value (for a synchronous operation) and it will work just fine (per your design request).
I've tested it with a thrown exception in the promise callback in the factory function and it does indeed just reject and propagate that exception back to reject the sequence promise with the exception as the reason.
This uses a similar method as you (on purpose, trying to stay with your general architecture) for chaining multiple calls to loop()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With