JavaScript, React - sending multiple simultaneous ajax calls struggling with promises. Basically I want to chain the calls, if one server call completes then only do next call, and collect the successful response of calls from endpoint /pqr made inside makeServerCalls.
import Promise from 'bluebird';
import request from 'superagent';
// sends a post request to server
const servercall2 = (args, response) => {
const promise = new Promise((resolve, reject) => {
const req = request
.post(`${baseUrl}/def`)
.send(args, response)
.setAuthHeaders();
req.endAsync()
.then((res) => resolve(res))
.catch((err) => reject(err));
});
return promise;
};
// sends a post request to server
const servercall1 = (args) => {
const promise = new Promise((resolve, reject) => {
const req = request
.post(`${baseUrl}/abc`)
.send(args)
.setAuthHeaders();
req.endAsync()
.then((res) => resolve({res}))
.catch((err) => reject(err));
});
return promise;
};
// function to send request to cgi server to execute actions from ui
async function makeServerCalls(args, length) {
// convert args to two dimensional array, chunks of given length [[1,2,3], [4,5,6,], [7,8]]
const batchedArgs = args.reduce((rows, key, index) => (index % length === 0 ? rows.push([key])
: rows[rows.length - 1].push(key)) && rows, []);
const responses = [];
for (const batchArgs of batchedArgs) {
responses.push(
// wait for a chunk to complete, before firing the next chunk of calls
await Promise.all(
***// Error, expected to return a value in arrow function???***
batchArgs.map((args) => {
const req = request
.get(`${baseUrl}/pqr`)
.query(args)
// I want to collect response from above req at the end of all calls.
req.endAsync()
.then((response) =>servercall2(args,response))
.then((res) => res);
})
)
);
}
// wait for all calls to finish
return Promise.all(responses);
}
export function execute(args) {
return (dispatch) => {
servercall1(args)
.then(makeServerCalls(args, 3))
.then((responses) => {
const serverresponses = [].concat(...responses);
console.log(serverresponses);
});
};
}
Error: expected to return a value in arrow function. What am I doing wrong here?
Is this a right chaining or it can be optimized?
What happens if some call fails in between?
You can use Async library for this. No need to re-invent the wheel.
There is a waterfall function that takes a list of functions that execute in series. You can pass result of function 1 to function 2 to function 3 and so on. Once complete waterfall executes, you get the result in callback. You can read more about it in the docs in the link above.
Related
I'm trying to limit the number of requests I send to an API.
I'm using Limiter and it's working just like I need, the only issue is that I can't find a way to use it with await (I need all the responses before rendering my page)
Can someone give me a hand with it?
Btw the Log returns a boolean.
const RateLimiter = require('limiter').RateLimiter;
const limiter = new RateLimiter(50, 5000)
for (let i = 0; i < arrayOfOrders.length; i++) {
const response = limiter.removeTokens(1, async (err, remainingRequests) => {
console.log('request')
return await CoreServices.load('updateOrder', {
"OrderNumber": arrayOfOrders[i],
"WorkFlowID": status
})
})
console.log('response', response)
}
console.log('needs to log after all the request');
this is loggin:
response true
response true
response false
needs to log after all the request
request
request
request
...
Promisifying .removeTokens will help, see if this code works
const RateLimiter = require('limiter').RateLimiter;
const limiter = new RateLimiter(50, 5000);
const tokenPromise = n => new Promise((resolve, reject) => {
limiter.removeTokens(n, (err, remainingRequests) => {
if (err) {
reject(err);
} else {
resolve(remainingRequests);
}
});
});
(async() => { // this line required only if this code is top level, otherwise use in an `async function`
const results = await Promise.all(arrayOfOrders.map(async (order) => {
await tokenPromise(1);
console.log('request');
return CoreServices.load('updateOrder', {
"OrderNumber": order,
"WorkFlowID": status
});
}));
console.log('needs to log after all the request');
})(); // this line required only if this code is top level, otherwise use in an `async function`
explanation
Firstly:
const tokenPromise = n => new Promise((resolve, reject) => {
limiter.removeTokens(n, (err, remainingRequests) => {
if (err) {
reject(err);
} else {
resolve(remainingRequests);
}
});
});
promisifies the limiter.removeTokens to use in async/await - in nodejs you could use the built in promisifier, however lately I've had too many instances where that fails - so a manual promisification (I'm making up a lot of words here!) works just as well
Now the code is easy - you can use arrayOfOrders.map rather than a for loop to create an array of promises that all run parallel as much as the rate limiting allows, (the rate limiting is done inside the callback)
await Promise.all(... will wait until all the CoreServices.load have completed (or one has failed - you could use await Promise.allSettled(... instead if you want)
The code in the map callback is tagged async so:
await tokenPromise(1);
will wait until the removeTokens callback is called - and then the request
return CoreServices.load
is made
Note, this was originally return await CoreServices.load but the await is redundant, as return await somepromise in an async function is just the same as return somepromise - so, adjust your code too
I am dynamically executing API callouts using promise all , but I want to control the order of execution of the callouts.
So in the code below, I want callout2 to wait until callout1 is complete and 3 to wait for 2.
dataMap = [callout1, callout2, callout3]
const newMap = dataMap((x) =>{
req = axios.post(url, {
"query": x
})
return req;
});
Promise.all(promiseArray)
.then( (val) => {
console.log(val[0]);
console.log(val[1]);
console.log(val[2]);
})
.catch( (error) => console.log('error' + error))
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 get an array of args as an argument, and then I make lots of server calls based on the algo below.
Post to endpoint /abc with args array as data.
Iterate over args array,
a. Pull 3 at a time and send 3 Get calls to endpoint /pqr
b. Once 3 calls in step '2.a' succeeds send 3 Post calls to endpoint /def
c. Collect responses from step '2.a' server call and push it in an array.
d. Repeat step a,b,c till args length.
Code Snippet for the entire process is given below, execution starts at function execute(args).
import Promise from 'bluebird';
import request from 'superagent';
// sends a post request to server
const servercall2 = (args, response) => {
const req = request
.post(`${baseUrl}/def`)
.send(args, response)
.setAuthHeaders();
return req.endAsync();
};
// sends a post request to server
const servercall1 = (args) => {
const req = request
.post(`${baseUrl}/abc`)
.send(args)
.setAuthHeaders();
return req.endAsync()
.then((res) => resolve({res}))
.catch((err) => reject(err));
};
async function makeServerCalls(args, length) {
// convert args to two dimensional array, chunks of given length [[1,2,3], [4,5,6,], [7,8]]
const batchedArgs = args.reduce((rows, key, index) => (index % length === 0 ? rows.push([key])
: rows[rows.length - 1].push(key)) && rows, []);
const responses = [];
for (const batchArgs of batchedArgs) {
responses.push(
// wait for a chunk to complete, before firing the next chunk of calls
await Promise.all(
***// Error, expected to return a value in arrow function???***
batchArgs.map((args) => {
const req = request
.get(`${baseUrl}/pqr`)
.query(args)
// I want to collect response from above req at the end of all calls.
return req.endAsync()
.then((response) =>servercall2(args,response));
})
)
);
}
// wait for all calls to finish
return Promise.all(responses);
}
export function execute(args) {
return (dispatch) => {
servercall1(args)
.then(makeServerCalls(args, 3))
.then((responses) => {
const serverresponses = [].concat(...responses);
console.log(serverresponses);
});
};
}
I am facing couple of issues
2.c seems not to be working fine "Collect responses from step '2.a' server call and push it in an array.". Error: expected to return a value in arrow function. What am I doing wrong here? Please note that at the end I care about the response from step 2.a only.
Is this a right chaining or it can be optimized, based on the requirements mentioned above?
Is there any other failure handling I have to do?
You have a brick wall of text so its becoming a little hard to decipher what you're actually trying to achieve, but I will give my two cents on the code given.
//Both server calls can be simplified.. no need to
//wrap in another promise if one is being returned
const servercall2 = (args, response) => {
const req = request
.post(`${baseUrl}/def`)
.send(args, response)
.setAuthHeaders();
return req.endAsync();
};
//Here... you return no value in the function passed to map, thus an
//error is being thrown. You need to return a Promise from here so that
//it can be passed into Promise.all
const allFinished = await Promise.all(
batchArgs.map((args) => {
const req = request
.get(`${baseUrl}/pqr`)
.query(args)
// I want to collect response from above req at the end of all calls.
return req.endAsync()
})
);
allFinished.then(function(results){
});
It might be this -- each item in batchArgs.map should be a Promise I think? Then Promise.all will wait for each to finish:
batchArgs.map((args) => {
const req = request
.get(`${baseUrl}/pqr`)
.query(args)
// Return promise here
return req.endAsync()
.then((response) =>servercall2(args,response))
.then((res) => res);
})
I am trying to create an async queue for an array of get requests to an api, i am just unsure how to combine and use the responses. Maybe my implementation is wrong since i am using async.queue inside a promise then function ?
Ultimately i would like to get results from first promise ->
use results of that first promise to create an array of get requests for the async.queue ->
then combine the results of all the get responses. I need to throttle the amount of requests that go out at a time due to API rate limit.
const rp = require("request-promise");
app.get("/", (req,res) => {
let arr = []
rp.get(url)
.then((response) => {
let arrayID = response
let q = async.queue((task, callback) => {
request({
method: "GET",
url: url,
qs: {
id: task.id
}
}, (error, response, body) => {
arr.push(body)
console.log(arr.length)
// successfully gives me the response i want. im trying to push into an array with all of my responses,
// but when i go to next then chain it is gone or if i try to return arr i get an empty []
})
callback()
}, 3)
for(var i = 0; i < arrayID.length; i++){
q.push({ id : arrayID[i]} );
}
q.drain = function() {
console.log('all items have been processed');
}
return arr
})
.then((responseArray) => {
//empty array even though the length inside the queue said other wise, i know its a problem with async and sync actions but is there a way to make the promise chain and async queue play nice?
res.json(responseArray)
})
})
Figured it out, ended up having to wrap it in a promise and resolve the final array in q.drain()
const rp = require("request-promise");
app.get("/", (req,res) => {
rp.get(url)
.then((response) => {
let arrayID = response
return new Promise((resolve, reject) => {
var q = async.queue((task, callback) => {
request({
method: "GET",
url: url,
qs: {
id:task.id,
},
}, (error, response, body) => {
arr.push(body)
callback();
})
}, 2);
q.drain = () => resolve(arr);
q.push(arrayID);
})
})
.then((response) => res.json(response))
.catch((error) => res.json(error))
}
To launch multiple async calls in parallel you can use Promise.all()
To launch multiple async calls sequentially (i.e they depend on each other) you can return each promise and use its result inside a then() function
Code below:
app.get("/", (req,res)
.then(function(firstResult)) {
//You can use result of first promise here
return Promise.all([
//Create array of get request here
//To also return firstResult just add it in the Promise.All array
]);
})
.then(function(allResults){
//You can use results of all the get requests created in the previous then()
})
.catch(function(error){
//Deal with any error that happened
});