First given an array with a list of promises:
var promises = [promise1, promise2, promise3];
I would like to be able to execute all these promises with a predicate. If the first predicate returns false then return immediately giving a result of what has been processed up to that point and cancelling any remaining promises. For example something like:
Promise.all(promises, p => p != false))
.then(results => console.log(results));
Results should have what has been processed up to the point where the first predicate failed.
The promises should be processed in series not parallel.
You cannot cancel a promise. You could however just start the next action when the previous was done:
async function doUntil(generator, predicate) {
const result = [];
let curr;
while(predicate(curr = await generator()))
result.push(curr);
return result;
}
// usable as:
const users = await doUntil(
() => User.getOneAsync(),
user => user.loggedIn
);
For sure that can easily be adopted to a list of promises, however then those promises that are not part of the result still get executed, there results go into nowhere:
const promises = [/*...*/];
const result = await doUntil(
() => promises.shift(),
p => p
);
My solution is very similar to the one promise by Yona, but in the sense that you only return the last returned promise value that satisfies the predicate.
In the code below, I simply constructed a test promise that returns a random number after 500ms. The random number is then passed into a predicate which returns true of the number exceeds 0.5.
An improvement is that we try to catch an event where the promise might fail. If the promise fails then the chain is immediately broken returning the last result, similar to if the promise returns a result that fails the predicate:
// Here we simply set a test promise that returns a random number after 0.5s
function testPromise() {
return new Promise(resolve => {
window.setTimeout(() => resolve(Math.random()), 500);
});
}
// Execute promises in a specified order, evaluate their output by a predicate
async function awaitSequentialPromises(promises, predicate) {
let result;
for (const promise of promises) {
// Try, so that we can catch if a promise fails
try {
const _result = await promise();
console.log(_result);
// Break out of loop if promise returns results that fails predicate
if (!predicate(_result))
break;
else
result = _result;
} catch(e) {
// Break out of loop if promise fails
break;
}
}
console.log(`Final result: ${result}`);
return result;
}
// Our predicate is simply if the promise returns a random number > 0.5
awaitSequentialPromises([testPromise, testPromise, testPromise], v => v > 0.5);
Here's a function that should do what you want. You may want to modify it to handle failing predicates differently.
As others have noted, you cannot "cancel" a promise, since it represents something that has already started, generally speaking.
/**
* Combines an array of promises with a predicate. Works like Promise.all, but checks each result from the given
* Promise against the supplied predicate, and if the result value from any Promise fails the predicate, the
* overall promise is immediatly resolved with those values which passed the predicate.
*/
function allWithPredicate(
promises /*: Array<Promise<T1>, Promise<T2>, ...> */,
predicate /*: (mostRecentResult, allResults, promiseIndex)*/
) /*: Promise<[T1, T2, ...]> */ {
// Handle empty input arrays
if (promises.length === 0)
return Promise.resolve([]);
// Create a manual result promise, as none of the built-in promise manipulation functions will do the trick.
return new Promise(
(resolve, reject) => {
// This array will collect the values from the promises. It needs to be the same size as the array of
// promises.
const results = new Array(promises.length);
// We track the number of resolved promises so we know when we're done. We can't use the length of the
// array above because the last promise might finish first, which would cause the array to be equal in
// length to the promises array.
let resolvedCount = 0;
// Now we go through each promise and set things up
promises.forEach(
(promise, index) => {
promise.then(
result => {
// A promise has finished successfully! Hooray. We increment the counter.
resolvedCount++;
// Now we check if the newly returned value from the promise is acceptable, using the
// supplied predicate.
if (predicate(result, results, index)) {
// If if it, we keep this result.
results[index] = result;
// If the resolved counter is equal to the original number of promises in the array,
// we are done and can return. Note that we do NOT check against the length of
// the promises array, as it may have been mutated since we were originally called.
if (resolvedCount === results.length) {
resolve(results);
}
} else {
// If not, we short-circuit by resolving the overall promise immediately.
// Note that as written, we do NOT include the unacceptable value in the result.
resolve(results);
}
},
error => {
reject(error);
}
)
}
)
}
);
}
Here's a link to a jsbin demo: https://jsbin.com/jasiguredu/edit?js,console
Promise.all only takes one argument
var promises = [promise1, promise2, promise3];
Promise.all(promises)
.then(p => {
if(p === false) {
throw new Error(false);
} else {
console.log(p);
}
})
.catch(error => {
console.log(error);
});
}
If a promise in Promise.all ends in a catch statement, then it finishes, you can have a counter outside the promise if you need.
Related
I'm doing multiple requests to different APIs (simulated by the delayedPromise function) which each take a certain amount of time to fulfill. If one of them fulfills I return the value it resolves and that's it. I'm currently doing those one after the other since the first request would be the most accurate while the second would be less accurate etc. So if the first request fails and the second fulfills I return the second
To speed things up I thought about parallelizing those requests. To accomplish that I'm creating an array that holds the promises and it awaits the result of the first element and if that fails it awaits the result of the second element etc...
Here's the code:
function promiseTest() {
return new Promise(function (resolve, reject) {
let promises = []
new Promise(async function (resolve, reject) {
promises.push(delayedPromise(true, 10000, "1")) //If true resolves after 10000 ms; false = reject
promises.push(delayedPromise(false, 1000, "2"))
promises.push(delayedPromise(false, 1000, "3"))
let answer;
let answer2
let answer3;
answer = await promises[0].catch(async err => {
answer2 = await promises[1].catch(async err => {
answer3 = await promises[2].catch(err => { reject(err) })
})
})
if (answer != undefined) {
resolve(answer)
}
else if (answer2 != undefined) {
resolve(answer2)
}
else if (answer3 != undefined) {
resolve(answer3)
}
}).then(res => resolve(res)).catch(err => {reject(err)})
})
}
function delayedPromise(bool, time,string ="") {
return new Promise(function (resolve, reject) {
if (bool) {
setTimeout(resolve(string), time)
}
else {
setTimeout(reject(), time)
}
})
}
The issue is that while this approach works it's
A: Most likely not the best solution and hardly scalable
and B: The exact code above is creating a bunch of uncaught exceptions errors. I believe this is when I receive a result for answer1 I'm not handling any of the rejections the other functions returned.
Is there any better way to do these kinds of things/ fix the above mentioned concerns?
I'd appreciate any ideas or improvements!
Many thanks in advance
PS: If you can phrase my title better please let me know I'm kind of struggling to convey my issue in one sentence
There is no builtin for this. Promise.any and Promise.allSettled are quite similar but do not do exactly what you want. You'll have to build it yourself indeed. This is what I would do:
async function first(iterable) {
const promises = [];
for (const thenable of iterable) {
const promise = Promise.resolve(thenable);
promises.push(promise);
promise.catch(() => {}); // ignore rejections, we'll handle them later
}
const errors = [];
for (const promise of promises) {
try {
return await promise;
} catch(error) {
errors.push(error);
}
}
throw new AggregateError(errors);
}
Code in the question simplifies to:
function promiseTest() {
let promises = [
delayedPromise(true, 10000, '1'),
delayedPromise(false, 1000, '2'),
delayedPromise(false, 1000, '3')
];
return promises[0]
.catch(() => promises[1])
.catch(() => promises[2]);
}
As soon as a successful promise is encountered, the chain will stop catching and the successful result will drop all the way through to be delivered (promise-wrpped) to promiseTest's caller.
The catch-chain could also be written as follows ...
Promise.reject()
.catch(() => promises[0])
.catch(() => promises[1])
.catch(() => promises[2]);
... which helps in formulating a proceduralized version for an array of any length, as follows:
function promiseTest() {
let promises = [...]; // array of Promises (any length)
return promises.reduce((p_, p) => p_.catch(() => p), Promise.reject());
}
Assuming that measures to avoid unhandled rejections are not taken in the formulation of the promises array, you can do so (with a series of "branch-catches") in the reduction ...
function promiseTest() {
let promises = [...]; // array of Promises (any length).
return promises.reduce((p_, p) => {
p.catch(() => null); // avoid unhandled rejections.
return p_.catch(() => p);
}, Promise.reject());
}
I think weighting the result to favor promises in order while still running them all in parallel (for best end-to-end time) will require building a special loop. I think you can do it like this:
const errResult = Symbol('errResult');
// fns is an array of functions to call
// args is a array of arguments to pass to each function (same args to each)
async function getResult(fns, args) {
// start all the async operations to run in parallel
const promises = fns.map(fn => {
// Rejections will resolve with a sentinel value so we can detect them
// This helps us avoid ever getting an unhandled rejection error
// on promises that reject after we've already found our desired value
return fn(...args).catch(err => {
return errResult;
});
});
// now await them in order so you can find the first
// one that completes (in order)
// Promises that rejected have been converted into an errResult
// resolve to help in avoiding unhandled rejections
// after we return with our good first result
for (let p of promises) {
let result = await p;
if (result !== errResult) {
return result;
}
}
throw new Error('no results');
}
// sample usage:
getResult([fn1, fn2, fn3, fn4], [argA,argB]).then(result => {
console.log(result);
}).catch(err => {
console.log("no results found");
});
If you want different args to each function, then just build that into fn1, fn2, etc... so they call the root function with their desired arguments and then let the args array be an empty array.
I am trying to understand how promises work by creating the Promise.all method from an exercise of this book:
https://eloquentjavascript.net/11_async.html#i_Ug+Dv9Mmsw
I tried looping through the whole array that's given as argument to the method itself, using .then for successful promises where the body of the handler pushes the result to a binding i defined previously outside of the loop, for rejected promises, i used .catch in a way that it takes the rejected value as a "reason" and rejects the main promise giving it an error
function Promise_all(promises) {
return new Promise((resolve, reject) => {
if(promises.length == 0) resolve(promises);
let fullArray = [];
for(let i=0; i<promises.length ; i++){
promises[i]
.then(x => fullArray.push(x))
.catch(reason => reject(new Error(reason)));
}
resolve(fullArray);
});
}
What i expected the function to do the following:
-Pick a promise from the "Promises" array.
-Solve the promise (if successful) by using the .then method on it with a handler function that just pushes the result to "fullArray".
-Solve the promise (if rejected) by using the .catch method on it with a handler function that simply calls the reject handler of the main promise that will be returned by "Promise_all".
-When the loop finishes, simply resolve the promise the "fullArray" of successful promises.
The code simply doesn't work the way i thought it would, using the test code of the book doesn't return the expected results:
Promise_all([]).then(array => {
console.log("This should be []:", array);
});
function soon(val) {
return new Promise(resolve => {
setTimeout(() => resolve(val), Math.random() * 500);
});
}
Promise_all([soon(1), soon(2), soon(3)]).then(array => {
console.log("This should be [1, 2, 3]:", array);
});
Promise_all([soon(1), Promise.reject("X"), soon(3)])
.then(array => {
console.log("We should not get here");
})
.catch(error => {
if (error != "X") {
console.log("Unexpected failure:", error);
}
});
The issue with the code, as #Bergi said, was that the ".then" and ".catch" callbacks are called asynchronously and as a consequence of this, "fullArray" wasn't even filled when "resolve()" was called even when it was outside of the loop. To solve this, i simply added "resolve()" inside of the promise that finishes last, to do this, i simply added a "counter" binding that has the value of the length of the "promises" array, and is reduced by 1 every time ".then" was called on a promise and when this "counter" equals 0, it calls "resolve()".
But that only solves the problem of filling the "fullArray" with the promises, not the issue of those promises being ordered correctly by the order they were called, to solve this i simply enumerated them in the array with the "i" binding of the loop, the end result was this:
function Promise_all(promises) {
return new Promise((resolve, reject) => {
if(promises.length == 0) resolve(promises);
let fullArray = [],
counter = promises.length;
for(let i=0; i< promises.length ; i++){
promises[i]
.then(x => {
fullArray[i] = x;
counter--;
if(counter == 0) resolve(fullArray)})
.catch(reason => reject(new Error(reason)));
}
});
}
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.
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):
Manual Iteration
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);
}
}
return next();
}
processArray(arr, processItem);
Manual Iteration Returning Promise
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";
}
return next();
}
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.
Iteration with .reduce()
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.
Iteration with .reduce() that Resolves With Array
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/
Iteration with .reduce() that Resolves With Array with delay
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
});
Iteration with Bluebird Promise Library
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
});
Using ES7 async/await
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().
Promises represent values of operations and not the operations themselves. The operations are already started so you can't make them wait for one another.
Instead, you can synchronize functions that return promises invoking them in order (through a loop with promise chaining for instance), or using the .each method in bluebird.
You can't simply run X async operations and then want them to be resolved in an order.
The correct way to do something like this is to run the new async operation only after the one before was resolved:
doSomethingAsync().then(function(){
doSomethingAsync2().then(function(){
doSomethingAsync3();
.......
});
});
Edit Seems like you want to wait for all promises and then invoke their callbacks in a specific order. Something like this:
var callbackArr = [];
var promiseArr = [];
promiseArr.push(doSomethingAsync());
callbackArr.push(doSomethingAsyncCallback);
promiseArr.push(doSomethingAsync1());
callbackArr.push(doSomethingAsync1Callback);
.........
promiseArr.push(doSomethingAsyncN());
callbackArr.push(doSomethingAsyncNCallback);
and then:
$.when(promiseArr).done(function(promise){
while(callbackArr.length > 0)
{
callbackArr.pop()(promise);
}
});
The problems that can occur with this is when one or more promises fail.
Although quite dense, here's another solution that will iterate a promise-returning function over an array of values and resolve with an array of results:
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
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);
Note that, because we're reducing from one promise to the next, each item is processed in series.
It's functionally equivalent to the "Iteration with .reduce() that Resolves With Array" solution from #jfriend00 but a bit neater.
I suppose two approaches for handling this question:
Create multiple promises and use the allWithAsync function as follow:
let allPromiseAsync = (...PromisesList) => {
return new Promise(async resolve => {
let output = []
for (let promise of PromisesList) {
output.push(await promise.then(async resolvedData => await resolvedData))
if (output.length === PromisesList.length) resolve(output)
}
}) }
const prm1= Promise.resolve('first');
const prm2= new Promise((resolve, reject) => setTimeout(resolve, 2000, 'second'));
const prm3= Promise.resolve('third');
allPromiseAsync(prm1, prm2, prm3)
.then(resolvedData => {
console.log(resolvedData) // ['first', 'second', 'third']
});
Use the Promise.all function instead:
(async () => {
const promise1 = new Promise(resolve => {
setTimeout(() => { console.log('first');console.log(new Date());resolve() }, 1000)
})
const promise2 = new Promise(resolve => {
setTimeout(() => {console.log('second');console.log(new Date()); resolve() }, 3000)
})
const promise3 = new Promise(resolve => {
setTimeout(() => { console.log('third');console.log(new Date()); resolve() }, 7000)
})
const promises = [promise1, promise2, promise3]
await Promise.all(promises)
console.log('This line is shown after 7000ms')
})()
In my opinion, you should be using a for loop(yes the only time I would recommend a for loop). The reason is that when you are using a for loop it allows you to await on each of the iterations of your loop where using reduce, map or forEach with run all your promise iterations concurrently. Which by the sounds of it is not what you want, you want each promise to wait until the previous promise has resolved. So to do this you would do something like the following.
const ids = [0, 1, 2]
const accounts = ids.map(id => getId(id))
const accountData = async() => {
for await (const account of accounts) {
// account will equal the current iteration of the loop
// and each promise are now waiting on the previous promise to resolve!
}
}
// then invoke your function where ever needed
accountData()
And obviously, if you wanted to get really extreme you could do something like this:
const accountData = async(accounts) => {
for await (const account of accounts) {
// do something
}
}
accountData([0, 1, 2].map(id => getId(id)))
This is so much more readable than any of the other examples, it is much less code, reduced the number of lines needed for this functionality, follows a more functional programming way of doing things and is using ES7 to its full potential!!!!
Also depending on your set up or when you are reading this you may need to add the plugin-proposal-async-generator-functions polyfill or you may see the following error
#babel/plugin-proposal-async-generator-functions (https://git.io/vb4yp) to the 'plugins' section of your Babel config to enable transformation.
I started diggin' in promises and found interesting Promise.all.
It is stated in MDN that
The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved.
Which basically means that set promises resolve after and if all promises in argument list have been resolved. I tried to implement it. I made simply promise ajax call.
var get = function(url) {
return new Promise(function(resolve,reject) {
var xhtml=new XMLHttpRequest();
xhtml.open("GET",url);
xhtml.responseType = 'blob';
xhtml.onload = function() {
if(xhtml.status==200){
resolve(xhtml.response);
} else {
reject(Error("Error"+statusText));
}
}
xhtml.send();
});
}
get("one.jpg").then(function(response){
var blob = window.URL.createObjectURL(response);
var img = document.createElement("img");
console.log("Success"+response);
img.src = blob;
document.body.appendChild(img);
});
Which works fine. But after I tried to add Promise.all it threw an error.
Promise.all(get).then(function(response){alert("done")});
this as i said threw an error " Argument 1 of Promise.all can't be converted to a sequence."
So I assume i didn't get the meaning of promise.all.
How does it work?
Promise.all takes an array (or any iterable) of promises and fulfills when all of them fulfill or rejects when one of them rejects. I think it's easier to understand if we implement it and understand why we need it.
A common use case might be to wait for the window to load and for the server to return data in order to run some code:
// a function that returns a promise for when the document is ready.
function windowReady(){
return new Promise(function(resolve){
window.addEventListener('DOMContentLoaded', resolve);
});
}
// function that returns a promise for some data
function getData(){
return fetch("/").then(function(r){ return r.json() });
}
Now, we want both of them to execute at the same time and then get the result. There are two items here but there could have easily been 5 things to wait for, or 100. So we use Promise.all:
Promise.all([windowReady(), getData()]).then(function(results){
// results[1] is the data, it's all in an array.
});
Let's see how we can implement it:
function all(iterable){ // take an iterable
// `all` returns a promise.
return new Promise(function(resolve, reject){
let counter = 0; // start with 0 things to wait for
let results = [], i = 0;
for(let p of iterable){
let current = i;
counter++; // increase the counter
Promise.resolve(p).then(function(res){ // treat p as a promise, when it is ready:
results[i] = res; // keep the current result
if(counter === 0) resolve(results) // we're done
}, reject); // we reject on ANY error
i++; // progress counter for results array
}
});
}
Or, in even more ES6ness:
let all = iterable => new Promise((resolve, reject) => {
let arr = [...iterable], c = arr.length, results = [];
arr.map(Promise.resolve, Promise).
map((p, i) => p.then(v => {
r[i] = v;
if(--c === 0) resolve(r);
} , reject));
});
Your get function returns a Promise. You are just passing a reference to the get function. You have to pass an array of Promises
Promise.all([get("one.jpg")]).then(...);
TLDR:
Promise.all is a Javascript method that takes an iterable (e.g. Array) of promises as an argument and returns a single promise when all the promises in the iterable argument have been resolved (or when iterable argument contains no promises). It resolves with an array of the resolved values and rejects with a single value of the first rejected Promise.
Example:
var promise1 = Promise.resolve(5);
var promise2 = Math.random() > 0.5? 1 : Promise.reject(1); // either resolves or rejects
var promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('foo'), 1000);
});
Promise.all([promise1, promise2, promise3]).then((val) => {
console.log(val);
}).catch((val) => {
console.log(val);
});
In the above example 3 Promises are passed into the Promise.all function as an array. Promise 1 and 3 always resolve. Promise 2 either resolves or rejects based on the random Nr generator. This Promise.all method then returns a resolved or rejected Promise based on the random Nr generator.
Then the then() method and the catch() method can be called on this promise which is returned from Promise.all. The then() method gets an array of all the resolved values, [5, 1, 'foo'] in this case. The catch() method gets the value of the first rejected Promise, 1 in this example.
When to use:
This method is very usefull when you want to execute multiple async operations and need to something with the results after the async operations. When using Promise.all all promises can be processed at the same time while still getting to operate on all the incoming data.
For example, when we need to get information using multiple AJAX requests and combine the data to something useful. It is essential to wait for all the data to be available otherwise we would try to combine non existing data which would lead to problems.
The Promise.all(iterables) function returns a single Promise.Here we provide multiple Promises as argument. Promise.all(iterables) function returns promise only when all the promises (argument) have resolved.
It rejects when first promise argument reject.
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
// expected output: Array [3, 42, "foo"]
});
Syntax:
Promise.all(func1, func2 [,funcN])
Parameters:
Read more at - https://www.oodlestechnologies.com/blogs/An-Introduction-To-Promise.all-Function
Disclaimer: I work for oodles technologies
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.
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):
Manual Iteration
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);
}
}
return next();
}
processArray(arr, processItem);
Manual Iteration Returning Promise
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";
}
return next();
}
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.
Iteration with .reduce()
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.
Iteration with .reduce() that Resolves With Array
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/
Iteration with .reduce() that Resolves With Array with delay
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
});
Iteration with Bluebird Promise Library
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
});
Using ES7 async/await
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().
Promises represent values of operations and not the operations themselves. The operations are already started so you can't make them wait for one another.
Instead, you can synchronize functions that return promises invoking them in order (through a loop with promise chaining for instance), or using the .each method in bluebird.
You can't simply run X async operations and then want them to be resolved in an order.
The correct way to do something like this is to run the new async operation only after the one before was resolved:
doSomethingAsync().then(function(){
doSomethingAsync2().then(function(){
doSomethingAsync3();
.......
});
});
Edit Seems like you want to wait for all promises and then invoke their callbacks in a specific order. Something like this:
var callbackArr = [];
var promiseArr = [];
promiseArr.push(doSomethingAsync());
callbackArr.push(doSomethingAsyncCallback);
promiseArr.push(doSomethingAsync1());
callbackArr.push(doSomethingAsync1Callback);
.........
promiseArr.push(doSomethingAsyncN());
callbackArr.push(doSomethingAsyncNCallback);
and then:
$.when(promiseArr).done(function(promise){
while(callbackArr.length > 0)
{
callbackArr.pop()(promise);
}
});
The problems that can occur with this is when one or more promises fail.
Although quite dense, here's another solution that will iterate a promise-returning function over an array of values and resolve with an array of results:
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
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);
Note that, because we're reducing from one promise to the next, each item is processed in series.
It's functionally equivalent to the "Iteration with .reduce() that Resolves With Array" solution from #jfriend00 but a bit neater.
I suppose two approaches for handling this question:
Create multiple promises and use the allWithAsync function as follow:
let allPromiseAsync = (...PromisesList) => {
return new Promise(async resolve => {
let output = []
for (let promise of PromisesList) {
output.push(await promise.then(async resolvedData => await resolvedData))
if (output.length === PromisesList.length) resolve(output)
}
}) }
const prm1= Promise.resolve('first');
const prm2= new Promise((resolve, reject) => setTimeout(resolve, 2000, 'second'));
const prm3= Promise.resolve('third');
allPromiseAsync(prm1, prm2, prm3)
.then(resolvedData => {
console.log(resolvedData) // ['first', 'second', 'third']
});
Use the Promise.all function instead:
(async () => {
const promise1 = new Promise(resolve => {
setTimeout(() => { console.log('first');console.log(new Date());resolve() }, 1000)
})
const promise2 = new Promise(resolve => {
setTimeout(() => {console.log('second');console.log(new Date()); resolve() }, 3000)
})
const promise3 = new Promise(resolve => {
setTimeout(() => { console.log('third');console.log(new Date()); resolve() }, 7000)
})
const promises = [promise1, promise2, promise3]
await Promise.all(promises)
console.log('This line is shown after 7000ms')
})()
In my opinion, you should be using a for loop(yes the only time I would recommend a for loop). The reason is that when you are using a for loop it allows you to await on each of the iterations of your loop where using reduce, map or forEach with run all your promise iterations concurrently. Which by the sounds of it is not what you want, you want each promise to wait until the previous promise has resolved. So to do this you would do something like the following.
const ids = [0, 1, 2]
const accounts = ids.map(id => getId(id))
const accountData = async() => {
for await (const account of accounts) {
// account will equal the current iteration of the loop
// and each promise are now waiting on the previous promise to resolve!
}
}
// then invoke your function where ever needed
accountData()
And obviously, if you wanted to get really extreme you could do something like this:
const accountData = async(accounts) => {
for await (const account of accounts) {
// do something
}
}
accountData([0, 1, 2].map(id => getId(id)))
This is so much more readable than any of the other examples, it is much less code, reduced the number of lines needed for this functionality, follows a more functional programming way of doing things and is using ES7 to its full potential!!!!
Also depending on your set up or when you are reading this you may need to add the plugin-proposal-async-generator-functions polyfill or you may see the following error
#babel/plugin-proposal-async-generator-functions (https://git.io/vb4yp) to the 'plugins' section of your Babel config to enable transformation.