How to wait for multiple nested promises to resolve? - javascript

I am attempting to make several http requests (using axios), one to each url in a list. As soon as an individual request resolves, I want to make another request to a url constructed from the previous request's response. Finally, when all secondary requests are resolved, I want to do something with all of their responses.
I have attempted to accomplish this as follows:
let promises = [];
urls.forEach(url => {
const promise = axios.get(url);
promise.then(response => {
promises.push(axios.get(response.data.url))
});
});
Promise.all(promises).then(responses => {
// do something with responses
})
I expect the responses of all secondary url requests to be resolved when .then(responses => {}) triggers, yet when this code triggers the responses array is empty.
How can I achieve something like this?

Return the nested axios.get to a mapping callback, so that Promise.all will wait for all nested requests to complete first.
Promise.all(
urls.map(
url => axios.get(url).then(
response => axios.get(response.data.url)
)
)
)
.then((results) => {
// ...
})

This is logical considering that very first promise.then() statement is asynchronous. The urls.forEach althoughy seemingly asynchronous, it is synchronous. Thus the Promise.all() is called before any promises are pushed to your array.
You could set it up like so:
let promises = [];
urls.forEach(url => {
promises.push(axios.get(url).then((response) => {
return axios.get(response.data.url);
});
});
Promise.all(promises).then(responses => {
// do something with responses
});
This chains the .then() directly onto the first axios request, which is then pushed onto the promises array.

Related

Promises: combine single resolve methods and Promise.all()

I have multiple promises fetching different assets. I want to achieve:
Executing some code first to handle some of these promises via then().
When everything is resolved/fetched, executing some code that launches my app via Promise.all().then().
I need to make sure 1 is excuted before 2. It does seem to work from what I have tested.
// handle promises one by one
promise1.then(results => {});
promise2.then(results => {});
// common process that should come after the above then()s
Promise.all([promise1, promise2])
.then(results => {});
But can I rely on it? Are "single" then() always executed before a then() on Promise.all()?
While Promise.all is stalled because it waits for all the promises to finish, there is no guarantee that it will resolve after the .then call on the promise that resolves last.
Instead you should try creating new promises from the .then calls.
// handle promises one by one
const promise3 = promise1.then(results => {});
const promise4 = promise2.then(results => {});
// common process that should come after the above then()s
Promise.all([promise3, promise4])
.then(results => {});
This way you can guarantee that the Promise all will resolve after the then calls
You can around by using Async/await. With waiting for each promise, you can write code to process data at the end.
Example:
(async () => {
const responses = [];
await new Promise((res) => res("promise1")).then((res) => {
console.log("From promise1 first then");
responses.push(res);
});
// Code at the end to do after all promises
console.log("Coming from second");
console.log("Responses:", responses);
})();

Send many parallel fetch request (more than max browser support)

I want to know: if I want to send multiple fetch request, that exceeds max browser parallel request support, can I send all request in parallel
and browser automatically will handle them? or I should first shrink them to sufficient size bunches and chain them.
Promise.all([allpromisses])
or any other solutions like:
function fetchAll(urls) {
const requestPromises = urls.map(url => {
return fetch(url).then(response => response.json());
});
requestPromises.reduce((chain, requestPromise) => {
return chain.then(() => requestPromise)
.then(data => data);
}, Promise.resolve());
}
or
getBunch([promises1]).then(getBunch([promises2]). ...
If you don't want to continue until all of the fetches return, it's worth just using Promise.all() and letting the browser handle queuing the requests. One nice thing is that Promise.all() will "fail fast". Meaning that unless you're handing errors individually, one failed promise will reject the Promise.all().
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all#Promise.all_fail-fast_behaviour

How can i fetch urls sequentially?

I have array that contain urls. Firstly I need to fetch first and second urls simultaneously and when one of this will be resolved, i will fetch next url. Repeat while all urls wont be fetched. How can i do this ?
You don't show any code, so I'll have to provide a generic example. fetch() returns a promise. If you want to run a loop sequentially waiting for the promise of each fetch() operation, then the simplest way to do that is with async and await:
async function someFunc(array) {
for (let item of array) {
let result = await fetch(/* pass arguments here using item */);
// process result here
}
return someValue; // this becomes the resolved value of the promise
// the async function returns
}
// usage
// all async functions return a promise
// use .then() on the promise to get the resolved value
someFunc(someArray).then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});

Make Promise.race finish only when one is successful

I have three asynchronous functions that return an object. If one of the functions are not successful, they return an empty object. The issue is that if I try to Promise.race these functions, one of the not successful functions may finish first and have an empty object return. My Promise code looks like this:
let product = await Promise.race([
get_info_1(code),
get_info_2(code),
get_info_3(code)
])
Is it possible to stop one of the asynchronous get_info functions from running without stopping the Promise.race? Or could I somehow tell Promise.race to wait until the returned object contains some information?
You can make the rejected Promise never complete
const never_resolve = new Promise(() => {})
{
(async function() {
let product = await Promise.race([
Promise.reject(1).catch(_ => never_resolve),
Promise.resolve(2).catch(_ => never_resolve),
Promise.reject(3).catch(_ => never_resolve)
])
console.log(product)
})()
}

Nested promises and how to get around em

I am using a promise to get some JSON from a URL. The JSON that is returned includes a list of new URLs that return JSON. My current implementation is failing due to the nested promises.
I need to do the following:
request parent JSON url
request each of the child JSON urls
After each child promise returns JSON, I need to do some stuff with the child's JSON and the parent JSON.
I am getting the following error.
Warning: a promise was created in a handler at main.development.js:661:61 but was not returned from it
Boiled down version of my code:
myPromise(url)
.then(response => {
// process the data into an array of items
items.forEach(item => {
myPromise(item.url)
.then(response2 => {
// Do a thing here with data from response and response2
});
});
});
Here I've done your example, using Bluebird map.
I've also added the concurrency option, this is very handy.. Leaving out, will just work a bit like promise.all, and putting a value of 1, would be were you want to do all the promises in series..
myPromise(url)
.then(response => {
// process the data into an array of items
return Promise.map(items, item => {
return myPromise(item.url)
.then(response2 => {
// Do a thing here with data from response and response2
});
}, {concurrency:10}); //lets do a max of 10 promises at a time.
});
You error is actually just a warning. It is there for good reason; a common mistake is doing something like this
myPromise(url)
.then(response => {
somethingElseAsync(response);
})
.then(myCallback);
and expecting myCallback to be invoked after somethingElseAsync has finished work. As far as I can tell, this is not your case, since you are not collecting the results of your child promises.
To suppress the warning, you can follow Keith's answer. As a bonus, you can tack another promise onto your chain which will resolve when all child promises have resolved.
As an alternative to Promise.map, if you are okay with spawning all child tasks simultaneously, you can get away with Promise.all, like this:
myPromise(url).then(response => {
return Promise.all(items.map(item => {
return myPromise(item.url).then(response2 => {
// handle response and response2, return some result
return result;
});
}));
}).then(results => {
// ^^^ an array of results returned from child promise callbacks
}).catch(error => {
// either the parent promise or one of the child promises has rejected
});

Categories