I just read an Article related to promise and was unable to comprehend how we can do multiple API call using Axios via Promise.all
So consider there are 3 URL, lets call it something like this
let URL1 = "https://www.something.com"
let URL2 = "https://www.something1.com"
let URL3 = "https://www.something2.com"
And an array in which we will store Value
let promiseArray = []
Now, I want to run this in parallel (Promise.all), but I am unable to figure our how will we do it? Because axios have a promise in itself (or at-least that's how I have used it).
axios.get(URL).then((response) => {
}).catch((error) => {
})
Question: Can someone please tell me how we can we send multiple request using promise.all and axios
The axios.get() method will return a promise.
The Promise.all() requires an array of promises. For example:
Promise.all([promise1, promise2, promise3])
Well then...
let URL1 = "https://www.something.com"
let URL2 = "https://www.something1.com"
let URL3 = "https://www.something2.com"
const promise1 = axios.get(URL1);
const promise2 = axios.get(URL2);
const promise3 = axios.get(URL3);
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
You might wonder how the response value of Promise.all() looks like. Well then, you could easily figure it out yourself by taking a quick look at this example:
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"]
For more information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
fetchData(URL) function makes a network request and returns promise object with pending status.
Promise.all will wait till all promises are resolved or any promise is rejected. It returns a promise and resolve with array of responses.
let URLs= ["https://jsonplaceholder.typicode.com/posts/1", "https://jsonplaceholder.typicode.com/posts/2", "https://jsonplaceholder.typicode.com/posts/3"]
function getAllData(URLs){
return Promise.all(URLs.map(fetchData));
}
function fetchData(URL) {
return axios
.get(URL)
.then(function(response) {
return {
success: true,
data: response.data
};
})
.catch(function(error) {
return { success: false };
});
}
getAllData(URLs).then(resp=>{console.log(resp)}).catch(e=>{console.log(e)})
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
You still can use promise.all with array of promises passed to it and then wait for all of them to be resolved or one of them gets rejected.
let URL1 = "https://www.something.com";
let URL2 = "https://www.something1.com";
let URL3 = "https://www.something2.com";
const fetchURL = (url) => axios.get(url);
const promiseArray = [URL1, URL2, URL3].map(fetchURL);
Promise.all(promiseArray)
.then((data) => {
data[0]; // first promise resolved
data[1];// second promise resolved
})
.catch((err) => {
});
Just to add to the approved answer axios also has its of Promise.all in the form axios.all it expects a list of promises and returns an array of responses.
let randomPromise = Promise.resolve(200);
axios.all([
axios.get('http://some_url'),
axios.get('http://another_url'),
randomPromise
])
.then((responses)=>{
console.log(responses)
})
Hope this may help
var axios = require('axios');
var url1 = axios.get('https://www.something.com').then(function(response){
console.log(response.data)
})
var url2 = axios.get('https://www.something2.com').then(function(response){
console.log(response.data)
})
var url3 = axios.get('https://www.something3.com').then(function(response){
console.log(response.data)
})
Promise.all([url1, url2, url3]).then(function(values){
return values
}).catch(function(err){
console.log(err);
})
Use Promise.allSettled which is almost same as Promise.all but it doesn't reject as a whole if any promise rejects.
Promise.allSettled just waits for all promises to settle, regardless of the result.
const [ first, second, third ] = await Promise.allSettled([
fetch('https://jsonplaceholder.typicode.com/todos/'),
fetch('https://jsonplaceholder.typicode.com/todos/'),
fetch('https://jsonplaceholder.typicodecomtodos')
])
// P.S: you can replace fetch with axios
The resulting array has:
{ status:"fulfilled", value:result } for successful responses
{ status:"rejected", reason:error } for errors.
Something like this should work:
const axios = require('axios');
function makeRequestsFromArray(arr) {
let index = 0;
function request() {
return axios.get('http://localhost:3000/api/' + index).then(() => {
index++;
if (index >= arr.length) {
return 'done'
}
return request();
});
}
return request();
}
makeRequestsFromArray([0, 1, 2]);
// using axios return
const promise0 = axios.get( url )
.then( function( response ) {
return response
} )
// using function new promise
function promise1() {
return new Promise( ( resolve, reject ) => {
const promise1 = axios.get( url )
.then( function( response ) {
return response
} )
resolve( promise1 )
) }
}
Promise.all( [promise0, promise1()] ).then( function(results) {
console.log(results[0]);
console.log(results[1]);
});
Related
I have an object with 5 items, each of them will send an http request 3 times.
I save that in a
var promise
var promise1
var promise2
In the end, I am resolving (trying) the promises using
Promise.all([promise, promise1, promise2]]
And then I send the data to a callback function.
I am using array.map() to do my task on that array, all the requests and Promise.all are happening inside that.
How can I wait until the whole batch of requests are made and the promises are solved before I send the data on the callback function?
async function requestJahrStatistic(jahreStatistic, callback){
Promise.all(
jahreStatistic.map(async (item) => {
var periods = getReportingPeriod(item.period);
connection.statistic_id = item.statistic_id;
connection.reporting_period = periods.prevYear;
var promise = new Promise(function(resolve, reject) {
sendHTTPRequest(item, function(result) {
resolve(result);
});
});
connection.reporting_period = periods.actualYear;
var promise1 = new Promise(function(resolve, reject) {
sendHTTPRequest(item, function(result) {
resolve(result);
});
});
connection.reporting_period = periods.nextYear;
var promise2 = new Promise(function(resolve, reject) {
sendHTTPRequest(item, function(result) {
resolve(result);
});
});
Promise.all([promise, promise1, promise2]).then(async resolved => {
var res = await resolved
return res
});
})
).then(async resolved =>{
var resp = await resolved;
callback(resp)
});
}
This was the last thing I tried before writing the question
There are several problems with that code:
requestJahrStatistic shouldn't be async if it reports its results by calling a callback
You use this pattern in a couple of places:
.then(async resolved => {
var res = await resolved
return res
});
That serves no purpose (unless... see #5) and can be completely removed.
There's no reason for the map callback to be async, as you're not using await within it.
You're repeating your logic wrapping sendHTTPRequest in a promise, and failing to handle errors in it. Don't repeat yourself, make a function for that.
It looks like connection.statistic_id and connection.reporting_period are used by the HTTP requests somehow. They shouldn't be, that's spooky action at a distance. :-) But if they are, then none of this can be in parallel since you have to wait for a request using a given statistic_id and reporting_period to complete before you can start the next.
You're not handling errors.
If I assume connection.reporting_period is used by the HTTP requests, that means they can't overlap, so none of this can be in parallel and you can't use Promise.all for it. You'd need something like:
If connection.reporting_period isn't used by the HTTP requests, this can all be parallel:
function sendHTTPRequestP(item) {
return new Promise((resolve, reject) => {
sendHTTPRequest(item, result => {
if (/*an error occurred*/) {
reject(new Error(/*...*/));
} else {
resolve(result);
}
});
})
}
// Completely serial because of spooky action at a distance with
// `connection.statistic_id` and `connection.reporting_period`
function requestJahrStatistic(jahreStatistic, callback) {
Promise.resolve(async () => {
const results = [];
for (const item of jahreStatistic) {
const periods = getReportingPeriod(item.period);
connection.statistic_id = item.statistic_id;
connection.reporting_period = periods.prevYear;
const result1 = await sendHTTPRequestP(item);
connection.reporting_period = periods.actualYear;
const result2 = await sendHTTPRequestP(item);
connection.reporting_period = periods.nextYear;
const result3 = await sendHTTPRequestP(item);
results.push([result1, result2, result3]);
}
return results;
})
.then(callback)
.catch(error => {
// Handle/report error, call `callback` with the appropriate error flag
});
}
Or something along those lines. Note that what callback will receive is an array of arrays. The outer array will have as many entries as jahreStatistic; each of those entries will be an array of the results of the three HTTP calls.
If you can change things so that each operation takes arguments rather than spooky action at a distance (I see that sendHTTPRequest already has the item so can presumably get statistic_id from it, so we just have to pass period), you can make things parallel:
function sendHTTPRequestP(item, reporting_period) {
return new Promise((resolve, reject) => {
sendHTTPRequest(item, reporting_period, result => {
if (/*an error occurred*/) {
reject(new Error(/*...*/));
} else {
resolve(result);
}
});
})
}
function requestJahrStatistic(jahreStatistic, callback){
Promise.all(
jahreStatistic.map((item) => {
const periods = getReportingPeriod(item.period);
return Promise.all([
sendHTTPRequestP(item, periods.prevYear),
sendHTTPRequestP(item, periods.actualYear),
sendHTTPRequestP(item, periods.nextYear)
]);
})
)
.then(callback)
.catch(error => {
// Handle/report error, call `callback` with the appropriate error flag
});
}
Or something along those lines.
I'm trying to use apolloFetch inside a Promise.all in my Node.js microservice but keep getting an error that the query is empty. The reason for using apolloFetch is to call another micro service and pass it an array of queries. Can someone give me some direction? My code is as follows:
const uri = "dsc.xxx.yyyy.com/abc/def/graphql";
const apolloFetch = CreateApolloFetch({uri});
const QryAllBooks = {
type: new GraphQLList(BookType),
args: {},
resolve() {
return new Promise((resolve, reject) => {
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if (err) {
reject(err);
}
resolve(results);
const str = JSON.stringify(results);
const json = JSON.parse(str);
const promises = [];
for (let p = 0; p < results.length; p++) {
const book_id = json[p].bookid;
const query = `mutation updateShipping
{updateShipping
(id: ${book_id}, input:{
status: "Shipped"
})
{ bookid
bookname }}`;
promises.push(query);
}
//Below is the Promise.all function with the
//apolloFetch that calls another graphql endpoint
//an array of queries
Promise.all(promises.map(p => apolloFetch({p}))).then((result) => {
//this is the problem code^^^^^^^^^^^^^^^^^^^^^
resolve();
console.log("success!");
}).catch((e) => {
FunctionLogError(29, "Error", e);
});
});
});
}
};
module.exports = {
QryAllBooks,
BookType
};
It looks like apolloFetch requires query - you are passing p
change
Promise.all( promises.map(p=>apolloFetch({p})) )
to
Promise.all( promises.map(query=>apolloFetch({query})) )
You also call resolve twice
To resolve all errors or success
const final_results = []
Promise.all(promises.map(query => apolloFetch({
query,
}))).then((result) => {
final_results.push(result)
}).catch((e) => {
final_results.push(e)
}).then(() => {
resolve(final_results)
});
You immediately resolve or rejects once the pool.query() callback starts:
if(err){ reject(err);}resolve(results);
So unless the query fails, you never resolve with the results from the apolloFetch calls, since the promise is already resolved with the pool.query() results. I guess you're missing an else block:
if( err ) {
reject();
}
else {
const promises = ...
}
PS: you can try using node.js' util.promisify() to turn pool.query() into a promise as well so you can just write something resembling: query(...).then(results=>results.map(apolloFetch) instead of ahving to mix callbacks and promises.
I have a function with an array of promises, that array can have from 1 to X promises.
Those promises enter into the array based on conditionals.
I want to be able to distinguish from which API comes each result, and I can't realise a clean way to do it
let promises = [];
if (false) {
let promise1 = request(toUrl);
promises.push(promise1);
}
if (true) {
let promise2 = request(toUrl);
promises.push(promise2);
}
if (false) {
let promise3 = request(toUrl);
promises.push(promise3);
}
if (true) {
let promise4 = request(toUrl);
promises.push(promise4);
}
try {
let result = await Promise.all(promises);
} catch (error) {
console.log(error);
}
So, if everything goes ok result will be an array of results. Not knowing which one of the conditionals was true, how do I know if result[0] is the result of promise1, promise2 or promise3?
You can just add to the response of your request(url) another information about the promise like
const promise1 = request(url).then(res => ({ res: res, promise: 'promise1' }))
and at the Promise.all() you will get values of the promises in the above form and can detect which promises were resolved.
Example
const promises = [];
if(true) {
const promise1 = fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => ({ res: res, promise: 'promise1' }));
promises.push(promise1);
}
if(false) {
const promise2 = fetch('https://jsonplaceholder.typicode.com/posts/2').then(res => ({ res: res, promise: 'promise2' }));
promises.push(promise2);
}
Promise.all(promises).then(res => console.log(res));
In my opinion we can simplify the complexity of the problem by using following code -
let promises = [];
let truthyValue = true,
falsyvalue = true;
let [promise1, promise2, promise3, promise4] = await Promise.all([
truthyValue ? request(toUrl) : Promise.resolve({}),
truthyValue ? request(toUrl) : Promise.resolve({}),
falsyValue ? request(toUrl) : Promise.resolve({}),
falsyValue ? request(toUrl) : Promise.resolve({})
]);
// promise1 will be called only when truthyValue is set
if (promise1) {
// do something
}
// promise2 will be called only when truthyValue is set
if (promise2) {
// do something
}
// promise3 will be called only when falsyValue is set
if (promise3) {
// do something
}
// promise4 will be called only when falsyValue is set
if (promise4) {
// do something
}
I used an object map of promises with a name key in order to identify which resolve corresponds to which promise.
const promises = {};
const mapResolveToPromise = res => Object.fromEntries(
Object.entries(promises).map(([key], index) => [key, res[index]])
);
promises.promise1 = fetch('https://jsonplaceholder.typicode.com/posts/1');
promises.promise2 = fetch('https://jsonplaceholder.typicode.com/posts/2');
Promise.all(Object.values(promises))
.then(mapResolveToPromise)
.then(res => {
console.log(res.promise1.url);
console.log(res.promise2.url);
});
I had a similar problem, always a different quantity of async functions to call.
What I didn't want was to start the promises work before promise.all().
So I collected function pointers in an array.
eg:
async function first() {
return new Promise((resolve)=> setTimeout(resolve,1000,99));
}
async function second() {
return new Promise((resolve)=> setTimeout(resolve,1500,100));
}
let x = [first, second];
// x is transformed into an array with then executed functions
await Promise.all(x.map(x=>x()))
Result is:
[
99,
100
]
hopefully this helps and I understood the mentioned problem ... :)
Why all the pushes, you can construct arrays inline.
doPromiseStuff = async ({ thing = true }) => {
const urls = ['', '', '', ''];
return await Promise.all([
thing ? request(urls[1]) : request(urls[2]),
thing ? request(urls[3]) : request(urls[4])
]);
}
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)
})
I have a function that makes several async calls which populate the same object with the returned data. I need to do something with the data once the objet is fully populated, and since there are multiple calls, this is not a basic callback/promise scenario.
Is it possible to create a promise in such case? Simplified code:
price_options = [] // when this is populated from all the async calls, I need to do stuff with it
sheet_columns = [3,5,7,89]
useServiceAccountAuth(credentials, function(error){ //google docs api
for (var i = 0; i < sheet_columns.length; i++) {
var params = {column_number: sheet_cols[i]}
do_async_call(params, function (e, data) {
data.forEach( function(item) {
price_options.push(item)
})
})
}
})
The other answers have so much misinformation to them.
What you should be doing is using Promise.all() to aggregate all of the Promises. Promise.all() takes an array of Promises, and returns a single Promise which resolves when all of the promises in the array have been resolved.
So now, you need to create a function that takes each params entry, and creates a Promise for data on it, and push it into a new array.
Since we're using Promises, let's get rid of all the other callbacks you have in your code:
// The "functionNameAsync" convention indicates that the function returns Promises.
// This convention was coined by Bluebird's promisifying functions.
// Takes credentials
// Returns a promise that rejects on error, or resolves with nothing on no error.
const useServiceAccountAuthAsync = credentials =>
new Promise((resolve, reject) =>
useServiceAccountAuth(credentials, err => err ? reject(err) : resolve()));
const doCallAsync = params =>
new Promise((resolve, reject) =>
do_async_call(params, (err, data) => err ? reject(err) : resolve(data)));
/* If you opt to use Bluebird, everything above this line can be replaced with:
const useServiceAccountAuthAsync = Promise.promisify(useServiceAcountAuth);
const doCallAsync = Promise.promisify(do_async_call);
it would even be faster than my version above. */
// Now time for the actual data flow:
const sheet_columns = [3,5,7,89]
useServiceAccountAsync()
.then(() => {
const arrayOfAsyncCallPromises = sheet_columns
.map(columnNumber => ({column_number: sheet_cols[columnNumber]}))
.map(doCallAsync);
//.map(param => doCallAsync(param)) equivalent to above
return Promise.all(arrayOfAsyncCallPromises);
})
.then(price_options => {
// use here
})
.catch(err => {
// handle errors here
});
You need to create array of promises.
here is where you can learn more about it.
http://bluebirdjs.com/docs/api/promise.all.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You can do this:
let results = sheet_columns.map(c => ({column_number: c}))
.map(params => new Promise((resolve, reject) => {
do_async_call(params, (e, data) => {
if(e) {
reject(e);
} else {
resolve(data);
}
})
}))
Promise.all(results).then(arr => Array.prototype.concat.apply([], arr)).then(price_options => doSomething(price_options))
Working jsbin here
If you want to use promise, wrap your do_async_call function in a promise returning function.
price_options = [];
sheet_columns = [3,5,7,89]
useServiceAccountAuth(credentials, function(error){ //google docs api
var promise_array = [];
for (var i = 0; i < sheet_columns.length; i++){
var params = {column_number: sheet_cols[i]}
var promise = do_async_promise(params);
promise_array.push(promise);
}
Q.all(promise_array).then(function(){
//do your operation with price_options here;
});
})
function do_async_promise(params){
var deferred = Q.defer();
do_async_call(params, function (e, data) {
data.forEach( function(item) {
price_options.push(item);
});
deferred.resolve();
})
return deferred.promise;
}
As other fellows have stated the use of Promise.all, I have written this snippet for you with Promise.all, have a look.
price_options = [];
sheet_columns = [3,5,7,89];
var promises = [];
useServiceAccountAuth(credentials, function(error){ //google docs api
for (var i = 0; i < sheet_columns.length; i++) {
var params = {column_number: sheet_cols[i]}
// create a new promise and push it to promises array
promises.push(new Promise(function(resolve, reject) {
do_async_call(params, function (e, data) {
resolve(data);
});
}));
}
// now use Promise.all
Promise.all(promises).then(function (args) {
args.forEach(function (data, i) {
data.forEach(function(item) {
price_options.push(item)
});
});
// here do your stuff which you want to do with price_options
});
})