Promise All retry - javascript

I know that promise.all() fails when even 1 of the promise is failed. I only want to try for failed promises and don't want to run promise.all() again.
Any recommendations on how I can achieve this in minimal way?

Promises are eager construct and model a value obtained asynchronously,
a Promise is produced using some kind of producer, like fetch for instance.
If you retain a reference to this producer then you can replay the nmechanism
that produced the Promise in the first place.
// producer function
function getData (arg) {
const result = new Promise();
return result.then(value => {
return { ok:true, value };
}, error => {
return {
ok: false,
value: error,
// retry is a function which calls the producer with the same arguments
retry: () => getData(arg)
};
})
}
Then if you have something like:
const data = [];
// Promise<{ok: boolean, value: any, retry?: function}>
// No promises will fail in this array
const asyncResults = data.map(getResults);
Promise.all(asyncResults)
.then((results) => {
const successes = results.filter(res => res.ok);
const retrys = results.filter(res => !res.ok).map(res => res.retry()); // retry all failed promises
})
Memory leaks, stack overflow: because I retain a reference to original arguments in order to retry and the algorithm is recursive there could be a memory leak. However the algorithm cannot "stack overflow":
getData calls do not get "deeper" over time (see retry definition)
the asyncrhonicity of the algorithm prevent this behaviour if a promise was never resolved
old data is properly discarded when accessing the results as const resultData = results.filter(res => res.ok).map(res => res.value);
However the algorithm could take a long time to settle if a promise keep on not getting resolved and prevent access to the rest of the values.
In an alternative I suggest you take a look at another async primitive, not yet part of the language (maybe some day) : Observables which are designed for this kind of tasks: lazy, retry-able async operations.

You may use async package and wrap all promise calls with closure with done argument.
Then simply resolve results.
const async = require('async');
const promiseAll = promises => {
return new Promise((resolve) => {
// preparing array of functions which has done method as callback
const parallelCalls = promises.map(promise => {
return done => {
promise
.then(result => done(null, result)
.catch(error => {
console.error(error.message);
done();
});
}
});
// calling array of functions in parallel
async.parallel(
parallelCalls,
(_, results) => resolve(results.filter(Boolean))
);
});
};
router.get('/something', async (req, res) => {
...
const results = await promiseAll(promises);
...
});
Or we can simply do Promise.all without using async package:
router.get('/something', async (req, res) => {
...
const results = (
await Promise.all(
promises.map(promise => {
return new Promise(resolve => {
promise.then(resolve).catch(e => resolve());
});
});
)
).filter(Boolean);
...
});

Related

How to exit function only in specific point

in an async function i, want to main function to return only in a specific point and not to return void at the end of the function
const fun = () => {
const list = [];
let streamFinished = 0;
let streamCount = files.length;
await fs.readdir(JSON_DIR, async(err, files) => {
await files.forEach((filename) => {
const readStream = fs.createReadStream(path.join("directory", filename));
const parseStream = json.createParseStream();
await parseStream.on('data', async(hostlist: Host[]) => {
hostlist.forEach(async host => {
list.push(host);
});
});
parseStream.on('end', () => {
streamFinished++;
if (streamFinished === streamCount) {
// End of all streams...
return list; //this should be the only point when the function return something
}
})
readStream.pipe(parseStream);
})
});
};
There are lots of things wrong with this code. It's a big mix of event-driven streams, callback-driven functions and promises. The first order of business is to make the main business logic just be promise-driven (as you can see in the new parseData() function and switch all control flow outside of that to just use promises. Here's one way to do it:
const fsp = fs.promises;
function parseData(filename) {
return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(path.join("directory", filename));
const parseStream = json.createParseStream();
const list = [];
parseStream.on('data', (hostlist: Host[]) => {
list.push(...hostlist);
}).on('end', () => {
resolve(list);
}).on('error', reject);
readStream.pipe(parseStream);
});
}
const fun = async () => {
const list = [];
const files = await fsp.readdir(JSON_DIR);
for (let filename of files) {
const listData = await parseData(filename);
list.push(...listData);
}
return list;
};
fun().then(result => {
console.log(result);
}).catch(err => {
console.log(err);
});
A few thoughts here:
The easiest way to "wait" for a stream operation to complete is to encapsulate it in a promise which you can then use await or .then() with. That's the purpose of the parseData() function. In addition, this also enables error handling by hooking stream errors to the promise rejection.
For processing a loop of asynchronous operations, you can either do them one at a time, using await on each asynchronous operation in the loop or you can run them in parallel by collecting an array of promises and using let data = await Promise.all(arrayOfPromises); on that array of promises.
It is only useful to use await if the thing you're awaiting is a promise that is connected to your asynchronous operation. So, things like await parseStream.on('data', ...) and await files.forEach(...) are pointless because neither of those return promises. Do NOT just stick await in places unless you KNOW you are awaiting a promise.
You will generally NOT want to use .forEach() with an asynchronous operation in the loop. Because .forEach() has no return value and no loop control, you can't really control much. Use a regular for loop instead which gives you full control. I consider .forEach() pretty much obsolete for asynchronous programming.
Don't mix promises and callbacks and don't mix promises and streams. If you have those, then "promisify" the stream or callback so all your main logic/control flow can be promises. This will vastly simplify your code and make error handling possible/practical.

How to reject with new message asynchronous javascript function

I am learning things about promises and I am confused with that function. I need to return a reject with message when connect() is not working. I tried a catch but i need to create reject for entire fucntion pooledDownload(). Please help me.
const pooledDownload = (connect, save, downloadList, maxConcurrency) => {
for (let i = 0; i < maxConcurrency; i++) {
connect()
.then((connection) => {
const { download, close } = connection;
download(downloadList[i])
.then((result) => save(result));
close();
})
.catch(() => {
throw new Error("connection failed")
});
}
}
module.exports = pooledDownload
When you call connect() in a for loop, you are creating multiple Promises. If you want to coalesce the results from all the Promise objects into one failure, you can use Promise.all or Promise.allSettled.
For example,
const pooledDownload = async (/*...*/) => {
return Promise.all(
// create an array of promises by mapping each element of an array of 1..N to a Promise
[...Array(maxConcurrency).keys()].map(() => {
return connect().then(/*...*/)
})
);
};
This will create a maxConcurrency-length array of Promise objects and pass it to Promise.all, which will reject if any of the items in the list reject and resolve if all the items in the list resolve. If you return that result, then your pooledDownload function will resolve/reject based on the success of all the individual connections.
Additional Note
It looks like your download function is also returning a promise. If you want the entire "connect -> download -> save" chain to act like one "unit" and succeed/fail together, then you need to return download(/*...*/) from your then call. Otherwise, the call to download() is creating a new promise that is not "connected to the chain" so to speak. You're also going to need to watch out for when you call close, since currently you don't wait for the download call to finish before closing.
Since I don't know which functions are async in this case, I can't exactly say how it should look, but this would be pretty close:
const singleDownload = async (connect, save, downloadListItem) => {
return connect().then((connection) => {
const { download, close } = connection;
return download(downloadListItem).then(async (result) => {
await save(result); // assume save is async
close();
});
});
};
const pooledDownload = async (connect, save, downloadList, maxConcurrency) => {
const allDownloads = [...Array(maxConcurrency).keys()].map((key) => {
return singleDownload(connect, save, downloadList[key]);
});
return Promise.all(allDownloads);
}

Handle a reject in promise.all() in javascript [duplicate]

I have an array of Promises that I'm resolving with Promise.all(arrayOfPromises);
I go on to continue the promise chain. Looks something like this
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler();
});
return Promise.all(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
I want to add a catch statement to handle an individual promise in case it errors, but when I try, Promise.all returns the first error it finds (disregards the rest), and then I can't get the data from the rest of the promises in the array (that didn't error).
I've tried doing something like ..
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler()
.then(function(data) {
return data;
})
.catch(function(err) {
return err
});
});
return Promise.all(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
But that doesn't resolve.
Thanks!
--
Edit:
What the answers below said were completely true, the code was breaking due to other reasons. In case anyone is interested, this is the solution I ended up with ...
Node Express Server Chain
serverSidePromiseChain
.then(function(AppRouter) {
var arrayOfPromises = state.routes.map(function(route) {
return route.async();
});
Promise.all(arrayOfPromises)
.catch(function(err) {
// log that I have an error, return the entire array;
console.log('A promise failed to resolve', err);
return arrayOfPromises;
})
.then(function(arrayOfPromises) {
// full array of resolved promises;
})
};
API Call (route.async call)
return async()
.then(function(result) {
// dispatch a success
return result;
})
.catch(function(err) {
// dispatch a failure and throw error
throw err;
});
Putting the .catch for Promise.all before the .then seems to have served the purpose of catching any errors from the original promises, but then returning the entire array to the next .then
Thanks!
Promise.all is all or nothing. It resolves once all promises in the array resolve, or reject as soon as one of them rejects. In other words, it either resolves with an array of all resolved values, or rejects with a single error.
Some libraries have something called Promise.when, which I understand would instead wait for all promises in the array to either resolve or reject, but I'm not familiar with it, and it's not in ES6.
Your code
I agree with others here that your fix should work. It should resolve with an array that may contain a mix of successful values and errors objects. It's unusual to pass error objects in the success-path but assuming your code is expecting them, I see no problem with it.
The only reason I can think of why it would "not resolve" is that it's failing in code you're not showing us and the reason you're not seeing any error message about this is because this promise chain is not terminated with a final catch (as far as what you're showing us anyway).
I've taken the liberty of factoring out the "existing chain" from your example and terminating the chain with a catch. This may not be right for you, but for people reading this, it's important to always either return or terminate chains, or potential errors, even coding errors, will get hidden (which is what I suspect happened here):
Promise.all(state.routes.map(function(route) {
return route.handler.promiseHandler().catch(function(err) {
return err;
});
}))
.then(function(arrayOfValuesOrErrors) {
// handling of my array containing values and/or errors.
})
.catch(function(err) {
console.log(err.message); // some coding error in handling happened
});
NEW ANSWER
const results = await Promise.all(promises.map(p => p.catch(e => e)));
const validResults = results.filter(result => !(result instanceof Error));
FUTURE Promise API
Chrome 76: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
You can download https://www.npmjs.com/package/promise.allsettled to get it now. In certain browsers allSettled comes preinstalled with the browser itself. It's worth downloading the package for peace of mind because eg. TypeScript doesn't have default definitions for allSettled.
ES2020 introduces new method for the Promise type: Promise.allSettled().
Promise.allSettled gives you a signal when all the input promises are settled, which means they’re either fulfilled or rejected. This is useful in cases where you don’t care about the state of the promise, you just want to know when the work is done, regardless of whether it was successful.
async function() {
const promises = [
fetch('/api.stackexchange.com/2.2'), // succeeds
fetch('/this-will-fail') // fails
];
const result = await Promise.allSettled(promises);
console.log(result.map(promise => promise.status));
// ['fulfilled', 'rejected']
}
Read more in the v8 blog post.
To continue the Promise.all loop (even when a Promise rejects) I wrote a utility function which is called executeAllPromises. This utility function returns an object with results and errors.
The idea is that all Promises you pass to executeAllPromises will be wrapped into a new Promise which will always resolve. The new Promise resolves with an array which has 2 spots. The first spot holds the resolving value (if any) and the second spot keeps the error (if the wrapped Promise rejects).
As a final step the executeAllPromises accumulates all values of the wrapped promises and returns the final object with an array for results and an array for errors.
Here is the code:
function executeAllPromises(promises) {
// Wrap all Promises in a Promise that will always "resolve"
var resolvingPromises = promises.map(function(promise) {
return new Promise(function(resolve) {
var payload = new Array(2);
promise.then(function(result) {
payload[0] = result;
})
.catch(function(error) {
payload[1] = error;
})
.then(function() {
/*
* The wrapped Promise returns an array:
* The first position in the array holds the result (if any)
* The second position in the array holds the error (if any)
*/
resolve(payload);
});
});
});
var errors = [];
var results = [];
// Execute all wrapped Promises
return Promise.all(resolvingPromises)
.then(function(items) {
items.forEach(function(payload) {
if (payload[1]) {
errors.push(payload[1]);
} else {
results.push(payload[0]);
}
});
return {
errors: errors,
results: results
};
});
}
var myPromises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject(new Error('3')),
Promise.resolve(4),
Promise.reject(new Error('5'))
];
executeAllPromises(myPromises).then(function(items) {
// Result
var errors = items.errors.map(function(error) {
return error.message
}).join(',');
var results = items.results.join(',');
console.log(`Executed all ${myPromises.length} Promises:`);
console.log(`— ${items.results.length} Promises were successful: ${results}`);
console.log(`— ${items.errors.length} Promises failed: ${errors}`);
});
Promise.allSettled
Instead of Promise.all use Promise.allSettled which waits for all promises to settle, regardless of the result
let p1 = new Promise(resolve => resolve("result1"));
let p2 = new Promise( (resolve,reject) => reject('some troubles') );
let p3 = new Promise(resolve => resolve("result3"));
// It returns info about each promise status and value
Promise.allSettled([p1,p2,p3]).then(result=> console.log(result));
Polyfill
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (promises) {
const convertedPromises = promises
.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
As #jib said,
Promise.all is all or nothing.
Though, you can control certain promises that are "allowed" to fail and we would like to proceed to .then.
For example.
Promise.all([
doMustAsyncTask1,
doMustAsyncTask2,
doOptionalAsyncTask
.catch(err => {
if( /* err non-critical */) {
return
}
// if critical then fail
throw err
})
])
.then(([ mustRes1, mustRes2, optionalRes ]) => {
// proceed to work with results
})
Using Async await -
here one async function func1 is returning a resolved value, and func2 is throwing a error and returning a null in this situation, we can handle it how we want and return accordingly.
const callingFunction = async () => {
const manyPromises = await Promise.all([func1(), func2()]);
console.log(manyPromises);
}
const func1 = async () => {
return 'func1'
}
const func2 = async () => {
try {
let x;
if (!x) throw "x value not present"
} catch(err) {
return null
}
}
callingFunction();
Output is - [ 'func1', null ]
if you get to use the q library https://github.com/kriskowal/q
it has q.allSettled() method that can solve this problem
you can handle every promise depending on its state either fullfiled or rejected
so
existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
return route.handler.promiseHandler();
});
return q.allSettled(arrayOfPromises)
});
existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
//so here you have all your promises the fulfilled and the rejected ones
// you can check the state of each promise
arrayResolved.forEach(function(item){
if(item.state === 'fulfilled'){ // 'rejected' for rejected promises
//do somthing
} else {
// do something else
}
})
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
For those using ES8 that stumble here, you can do something like the following, using async functions:
var arrayOfPromises = state.routes.map(async function(route){
try {
return await route.handler.promiseHandler();
} catch(e) {
// Do something to handle the error.
// Errored promises will return whatever you return here (undefined if you don't return anything).
}
});
var resolvedPromises = await Promise.all(arrayOfPromises);
Promise.allSettled with a filter
const promises = [
fetch('/api-call-1'),
fetch('/api-call-2'),
fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.
const resultFilter = (result, error) => result.filter(i => i.status === (!error ? 'fulfilled' : 'rejected')).map(i => (!error ? i.value : i.reason));
const result = await Promise.allSettled(promises);
const fulfilled = resultFilter(result); // all fulfilled results
const rejected = resultFilter(result, true); // all rejected results
Have you considered Promise.prototype.finally()?
It seems to be designed to do exactly what you want - execute a function once all the promises have settled (resolved/rejected), regardless of some of the promises being rejected.
From the MDN documentation:
The finally() method can be useful if you want to do some processing or cleanup once the promise is settled, regardless of its outcome.
The finally() method is very similar to calling .then(onFinally, onFinally) however there are couple of differences:
When creating a function inline, you can pass it once, instead of being forced to either declare it twice, or create a variable for it.
A finally callback will not receive any argument, since there's no reliable means of determining if the promise was fulfilled or rejected. This use case is for precisely when you do not care about the rejection reason, or the fulfillment value, and so there's no need to provide it.
Unlike Promise.resolve(2).then(() => {}, () => {}) (which will be resolved with undefined), Promise.resolve(2).finally(() => {}) will be resolved with 2.
Similarly, unlike Promise.reject(3).then(() => {}, () => {}) (which will be fulfilled with undefined), Promise.reject(3).finally(() => {}) will be rejected with 3.
== Fallback ==
If your version of JavaScript doesn't support Promise.prototype.finally() you can use this workaround from Jake Archibald: Promise.all(promises.map(p => p.catch(() => undefined)));
We can handle the rejection at the individual promises level, so when we get the results in our result array, the array index which has been rejected will be undefined. We can handle that situation as needed, and use the remaining results.
Here I have rejected the first promise, so it comes as undefined, but we can use the result of the second promise, which is at index 1.
const manyPromises = Promise.all([func1(), func2()]).then(result => {
console.log(result[0]); // undefined
console.log(result[1]); // func2
});
function func1() {
return new Promise( (res, rej) => rej('func1')).catch(err => {
console.log('error handled', err);
});
}
function func2() {
return new Promise( (res, rej) => setTimeout(() => res('func2'), 500) );
}
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
let sum = 0;
let promiseErrorArr = [];
Promise.allSettled(promises)
.then((results) => {
results.forEach(result => {
if (result.status === "rejected") {
sum += 1;
promiseErrorArr.push(result)
}
})
return ( (sum>0) ? promiseFailed() : promisePassed())
})
function promiseFailed(){
console.log('one or all failed!')
console.log(promiseErrorArr)
}
function promisePassed(){
console.log('all passed!')
}
// expected output:
// "one or all failed!"
// Array [Object { status: "rejected", reason: "foo" }]
Alternately, if you have a case where you don't particularly care about the values of the resolved promises when there is one failure but you still want them to have run, you could do something like this which will resolve with the promises as normal when they all succeed and reject with the failed promises when any of them fail:
function promiseNoReallyAll (promises) {
return new Promise(
async (resolve, reject) => {
const failedPromises = []
const successfulPromises = await Promise.all(
promises.map(
promise => promise.catch(error => {
failedPromises.push(error)
})
)
)
if (failedPromises.length) {
reject(failedPromises)
} else {
resolve(successfulPromises)
}
}
)
}
You can always wrap your promise returning functions in a way that they catches failure and returning instead an agreed value (e.g. error.message), so the exception won't roll all the way up to the Promise.all function and disable it.
async function resetCache(ip) {
try {
const response = await axios.get(`http://${ip}/resetcache`);
return response;
}catch (e) {
return {status: 'failure', reason: 'e.message'};
}
}
I've found a way (workaround) to do this without making it sync.
So as it was mentioned before Promise.all is all of none.
so... Use an enclosing promise to catch and force resolve.
let safePromises = originalPrmises.map((imageObject) => {
return new Promise((resolve) => {
// Do something error friendly
promise.then(_res => resolve(res)).catch(_err => resolve(err))
})
})
})
// safe
return Promise.all(safePromises)
You would need to know how to identify an error in your results. If you do not have a standard expected error, I suggest that you run a transformation on each error in the catch block that makes it identifiable in your results.
try {
let resArray = await Promise.all(
state.routes.map(route => route.handler.promiseHandler().catch(e => e))
);
// in catch(e => e) you can transform your error to a type or object
// that makes it easier for you to identify whats an error in resArray
// e.g. if you expect your err objects to have e.type, you can filter
// all errors in the array eg
// let errResponse = resArray.filter(d => d && d.type === '<expected type>')
// let notNullResponse = resArray.filter(d => d)
} catch (err) {
// code related errors
}
Not the best way to error log, but you can always set everything to an array for the promiseAll, and store the resulting results into new variables.
If you use graphQL you need to postprocess the response regardless and if it doesn't find the correct reference it'll crash the app, narrowing down where the problem is at
const results = await Promise.all([
this.props.client.query({
query: GET_SPECIAL_DATES,
}),
this.props.client.query({
query: GET_SPECIAL_DATE_TYPES,
}),
this.props.client.query({
query: GET_ORDER_DATES,
}),
]).catch(e=>console.log(e,"error"));
const specialDates = results[0].data.specialDates;
const specialDateTypes = results[1].data.specialDateTypes;
const orderDates = results[2].data.orders;
Unfortunately, I don't have enough reputation to comment (or do much of anything, really), so I'm posting this as an answer in response to Eric's answer here.
The executor function can also be an async function. However, this is usually a mistake, for a few reasons:
If an async executor function throws an error, the error will be lost and won’t cause the newly-constructed Promise to reject. This could make it difficult to debug and handle some errors.
If a Promise executor function is using await, this is usually a sign that it is not actually necessary to use the new Promise constructor, or the scope of the new Promise constructor can be reduced.
From this explanation as to why Promises should not utilize an async executor function
Instead, you should opt for Promise.allSettled(), as suggested here by Asaf.
With the help of allSettled,we can now read the status of
each promise is, and process each error individually, without losing any of this critical information
const promises = [
fetch('/api/first'), // first
fetch('/api/second') // second
];
The simplest way is to handle errors
const [firstResult, secondResult] = await Promise.allSettled(promises)
// Process first
if (firstResult.status === 'rejected') {
const err = firstResult.reason
// Here you can handle error
} else {
const first = firstResult.value
}
// Process second
if (secondResult.status === 'rejected') {
const err = secondResult.reason
// Here you can handle error
} else {
const second = secondResult.value
}
A nice way to handle error
const results = await Promise.allSettled(promises);
const [first, second] = handleResults(results)
function handleResults(results) {
const errors = results.filter(result => result.status === 'rejected').map(result => result.reason)
if (errors.length) {
// Aggregate all errors into one
throw new AggregateError(errors)
}
return results.map(result => result.value)
}
That's how Promise.all is designed to work. If a single promise reject()'s, the entire method immediately fails.
There are use cases where one might want to have the Promise.all allowing for promises to fail. To make this happen, simply don't use any reject() statements in your promise. However, to ensure your app/script does not freeze in case any single underlying promise never gets a response, you need to put a timeout on it.
function getThing(uid,branch){
return new Promise(function (resolve, reject) {
xhr.get().then(function(res) {
if (res) {
resolve(res);
}
else {
resolve(null);
}
setTimeout(function(){reject('timeout')},10000)
}).catch(function(error) {
resolve(null);
});
});
}
I wrote a npm library to deal with this problem more beautiful.
https://github.com/wenshin/promiseallend
Install
npm i --save promiseallend
2017-02-25 new api, it's not break promise principles
const promiseAllEnd = require('promiseallend');
const promises = [Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)];
const promisesObj = {k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)};
// input promises with array
promiseAllEnd(promises, {
unhandledRejection(error, index) {
// error is the original error which is 'error'.
// index is the index of array, it's a number.
console.log(error, index);
}
})
// will call, data is `[1, undefined, 2]`
.then(data => console.log(data))
// won't call
.catch(error => console.log(error.detail))
// input promises with object
promiseAllEnd(promisesObj, {
unhandledRejection(error, prop) {
// error is the original error.
// key is the property of object.
console.log(error, prop);
}
})
// will call, data is `{k1: 1, k3: 2}`
.then(data => console.log(data))
// won't call
.catch(error => console.log(error.detail))
// the same to `Promise.all`
promiseAllEnd(promises, {requireConfig: true})
// will call, `error.detail` is 'error', `error.key` is number 1.
.catch(error => console.log(error.detail))
// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [false, true, false]})
// won't call
.then(data => console.log(data))
// will call, `error.detail` is 'error', `error.key` is number 1.
.catch(error => console.log(error.detail))
// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [true, false, false]})
// will call, data is `[1, undefined, 2]`.
.then(data => console.log(data))
// won't call
.catch(error => console.log(error.detail))
————————————————————————————————
Old bad api, do not use it!
let promiseAllEnd = require('promiseallend');
// input promises with array
promiseAllEnd([Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)])
.then(data => console.log(data)) // [1, undefined, 2]
.catch(error => console.log(error.errorsByKey)) // {1: 'error'}
// input promises with object
promiseAllEnd({k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)})
.then(data => console.log(data)) // {k1: 1, k3: 2}
.catch(error => console.log(error.errorsByKey)) // {k2: 'error'}

React Promise asynchronous tasks order not correct

I am making multiple calls with Promise.
My API endpoints to fetch are:
https://www.api-football.com/demo/v2/statistics/357/5/2019-08-30
https://www.api-football.com/demo/v2/statistics/357/5/2019-09-30
https://www.api-football.com/demo/v2/statistics/357/5/2019-10-30
See the code
export function getTeamsStats(league, team, type) {
return function(dispatch) {
const url = "https://www.api-football.com/demo/v2/statistics";
let dates = ["2019-08-30", "2019-09-30", "2019-10-30"];
const getAllData = (dates, i) => {
return Promise.allSettled(dates.map(x => url + '/' + 357 + '/' + 5 + '/' + x).map(fetchData));
}
const fetchData = (URL) => {
return axios
.get(URL)
.then(res => {
const {
matchsPlayed: { total: teamsTotalMatchsPlayed},
} = res.data.api.statistics.matchs;
const matchsPlayed = teamsTotalMatchsPlayed;
dispatch(receivedTeamsStat(matchsPlayed, type));
})
.catch(e => {
console.log(e);
});
}
getAllData(dates).then(resp=>{console.log(resp)}).catch(e=>{console.log(e)})
}
}
Then in my component, I put in an array the matches played from this specific team ( Sao Paulo in this example ) from the initial date 30-8-2019 to 30-10-2019
const [dataHomeTeam, setDataHomeTeam] = useState([]);
useEffect(() => {
if (!team.matchsPlayed) {
return ;
}
setDataHomeTeam(prev =>
prev.concat([
{
matches: team.matchsPlayed,
}
])
);
},[team.matchsPlayed]);
console.log('Data Array', dataHomeTeam);
The problem is that normally the in the first render of the page I have the right order of the matches made from 30-8-2019 to 30-10-2019
See the console log image
But sometimes not, see here
So the question is, how can I make sure that the Promise is returning me the right order of the requests?
I am using Promise.allSettled, the multiple asynchronous tasks that are not dependent on one another to complete successfully, but the order is not always right.
This should be resolved via Promise.all because as described in documentation
Returned values will be in order of the Promises passed, regardless of
completion order.
The problem you have is that you are setting your state by calling dispatch for each promise based on it's completion and since promises can finish at any given time (e.g. 3rd request could finish first because they are sent at the same time), dispatch will be called and your state management will set that as first response (that's why you "sometimes" get such behaviour, because it's up to the network which one will finish first).
This would mean that either you should change your dispatch to receive array of already finished promises, or call dispatch one-by-one once they are finished which is shown in code below - resolve all and dispatch in order:
export function getTeamsStats(league, team, type) {
return function (dispatch) {
const url = "https://www.api-football.com/demo/v2/statistics";
let dates = ["2019-08-30", "2019-09-30", "2019-10-30"];
const getAllData = (dates, i) => {
return Promise.all(dates.map(x => url + '/' + 357 + '/' + 5 + '/' + x).map(fetchData));
}
const fetchData = (URL) => {
return axios
.get(URL)
.then(res => {
const {
matchsPlayed: { total: teamsTotalMatchsPlayed },
} = res.data.api.statistics.matchs;
return teamsTotalMatchsPlayed;
})
.catch(e => {
console.log(e);
});
}
getAllData(dates).then(resp => {
console.log(resp)
// 'resp' here are all 'teamsTotalMatchsPlayed' in correct order (here I mean order of call, not promise completion)
// so just dispatch them in order
resp.map(matchsPlayed => receivedTeamsStat(matchsPlayed, type));
}).catch(e => {
console.log(e)
})
}
}
Please note that I've maybe made some syntax error, but you get the idea.
The ideal way of achieving your requirment is by using Promise.all(). It is because of two main reasons,
To maintain the return value in the order of the Promises passed, regardless of completion order.
Returned values will be in order of the Promises passed, regardless of
completion order.
Refer to the Return value section in the link
To reject the returned promise (short circuit) if any of the promises in the iterable are rejected.
This is also important as well. We don't need to wait for all the asynchronous iterable promises to be resolved/rejected, if the first iterable promise of fetchData rejects we can short circuit and reject the returned promise.
On the other hand Promise.allSettled(),
Promise.allSettled() method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an
array of objects that each describes the outcome of each promise.
It also doesn't maintain the order in the returned promise.
Promise.allSettled() method never short-circuits. Always Promise fulfilled, never rejected.
Refer to the following comparison table between Promise.all() and Promise.allSettled(),
<script src="https://gist.github.com/Seralahthan/9934ba2bd185a8ccfbdd8e4b3523ea23.js"></script>
How, you have done,
function updateUI(value) {
console.log(value);
// Do something to update the UI.
}
// Note that order of resolution of Promises is 2, 1, 3
const promise1 = new Promise((resolve) => setTimeout(resolve, 200, 1)).then(updateUI);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 2)).then(updateUI);
const promise3 = new Promise((resolve) => setTimeout(resolve, 300, 3)).then(updateUI);
const promises = [promise1, promise2, promise3];
Promise.allSettled(promises).
then((value) => console.log('Nothing to do here', value));
// Output: 2, 1, 3
// Here we update the UI as soon as the result is obtained. As a result, the UI is also updated in the
// order in which the promise was resolved.
In other words, we wait not only for the network call but we wait for both the network call and the UI update to complete for each id which is not you want.
How you should have done instead,
// Note that order of resolution of Promises is 2, 1, 3 (Same as previous)
const promise1 = new Promise((resolve) => setTimeout(resolve, 200, 1));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 2));
const promise3 = new Promise((resolve) => setTimeout(resolve, 300, 3));
const promises = [promise1, promise2, promise3];
Promise.allSettled(promises).
then((results) => results.forEach((result) => updateUI(result.value)));
// Output: 1, 2, 3
// Here, we wait for all the network requests to complete and then loop through the results and update the UI.
// This ensures that the result is in order.
If you don't want to wait for all Promises to resolve and want to update the UI as soon as one resolves and still maintain order, then you need to pass the position of the element in the array and then use that position to update the element in place at the given position in the array.
Hope this helps.
export function getTeamsStats(league, team, type) {
return function (dispatch) {
const URL = "https://www.api-football.com/demo/v2/statistics";
let DATES = ["2019-08-30", "2019-09-30", "2019-10-30"];
return Promise.all(
DATES.map(date => axios.get(`${URL}/357/5/${date}`))
.then(responseList => {
responseList.map((res) => {
const {
matchsPlayed: { total: teamsTotalMatchsPlayed },
} = res.data.api.statistics.matchs;
const matchsPlayed = teamsTotalMatchsPlayed;
dispatch(receivedTeamsStat(matchsPlayed, type));
});
})
.catch((e) => console.log(e))
);
};
}
Promise.all preserves's the order. You should wait for all the api promise's to resolve first, before action dipatching.
Helpful Article: Promise.all: Order of resolved values

use forEach() with promises while access previous promise results in a .then() chain?

I have the following functions with promises:
const ajaxRequest = (url) => {
return new Promise(function(resolve, reject) {
axios.get(url)
.then((response) => {
//console.log(response);
resolve(response);
})
.catch((error) => {
//console.log(error);
reject();
});
});
}
const xmlParser = (xml) => {
let { data } = xml;
return new Promise(function(resolve, reject) {
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(data,"text/xml");
if (xmlDoc.getElementsByTagName("AdTitle").length > 0) {
let string = xmlDoc.getElementsByTagName("AdTitle")[0].childNodes[0].nodeValue;
resolve(string);
} else {
reject();
}
});
}
I'm trying to apply those functions for each object in array of JSON:
const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]
I came up with the following solution:
function example() {
_.forEach(array, function(value) {
ajaxRequest(value.url)
.then(response => {
xmlParser(response)
.catch(err => {
console.log(err);
});
});
}
}
I was wondering if this solution is acceptable regarding 2 things:
Is it a good practice to apply forEach() on promises in the following matter.
Are there any better ways to pass previous promise results as parameter in then() chain? (I'm passing response param).
You can use .reduce() to access previous Promise.
function example() {
return array.reduce((promise, value) =>
// `prev` is either initial `Promise` value or previous `Promise` value
promise.then(prev =>
ajaxRequest(value.url).then(response => xmlParser(response))
)
, Promise.resolve())
}
// though note, no value is passed to `reject()` at `Promise` constructor calls
example().catch(err => console.log(err));
Note, Promise constructor is not necessary at ajaxRequest function.
const ajaxRequest = (url) =>
axios.get(url)
.then((response) => {
//console.log(response);
return response;
})
.catch((error) => {
//console.log(error);
});
The only issue with the code you provided is that result from xmlParser is lost, forEach loop just iterates but does not store results. To keep results you will need to use Array.map which will get Promise as a result, and then Promise.all to wait and get all results into array.
I suggest to use async/await from ES2017 which simplifies dealing with promises. Since provided code already using arrow functions, which would require transpiling for older browsers compatibility, you can add transpiling plugin to support ES2017.
In this case your code would be like:
function example() {
return Promise.all([
array.map(async (value) => {
try {
const response = await ajaxRequest(value.url);
return xmlParser(response);
} catch(err) {
console.error(err);
}
})
])
}
Above code will run all requests in parallel and return results when all requests finish. You may also want to fire and process requests one by one, this will also provide access to previous promise result if that was your question:
async function example(processResult) {
for(value of array) {
let result;
try {
// here result has value from previous parsed ajaxRequest.
const response = await ajaxRequest(value.url);
result = await xmlParser(response);
await processResult(result);
} catch(err) {
console.error(err);
}
}
}
Another solution is using Promise.all for doing this, i think is a better solution than looping arround the ajax requests.
const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]
function example() {
return Promise.all(array.map(x => ajaxRequest(x.url)))
.then(results => {
return Promise.all(results.map(data => xmlParser(data)));
});
}
example().then(parsed => {
console.log(parsed); // will be an array of xmlParsed elements
});
Are there any better ways to pass previous promise results as
parameter in then() chain?
In fact, you can chain and resolve promises in any order and any place of code. One general rule - any chained promise with then or catch branch is just new promise, which should be chained later.
But there are no limitations. With using loops, most common solution is reduce left-side foldl, but you also can use simple let-variable with reassign with new promise.
For example, you can even design delayed promises chain:
function delayedChain() {
let resolver = null
let flow = new Promise(resolve => (resolver = resolve));
for(let i=0; i<100500; i++) {
flow = flow.then(() => {
// some loop action
})
}
return () => {
resolver();
return flow;
}
}
(delayedChain())().then((result) => {
console.log(result)
})

Categories

Resources