I'm trying to resolve an array of promises using a timeout to avoid rate limit exceptions on API requests. However, I'm still hitting the rate limit, as the timeout doesn't seem to be working.
Not really sure how to make this work.
router.route("/").put(async (req, res) => {
const { apiKey: access_token, course, assignments } = req.body;
try {
const returnedIds = [];
const promises = await assignments.map((assignment) => {
return axios({
method: "PUT",
url: `https://url.to.api/api/v1/courses/${course}/assignments/${assignment.id}`,
params: {
access_token,
},
data: {
assignment: {
points_possible: assignment.points_possible,
},
},
});
});
const promiseResolution = function() {
Promise.all([...promises]).then((values) => {
values.forEach((_, index) => {
returnedIds.push(assignments[index].id);
});
res.status(201).json({
returnedIds,
});
});
};
setTimeout(promiseResolution, 5000);
} catch (e) {
res.status(401);
}
});
If you just want to put some time between API calls this should do.
router.route("/").put(async (req, res) => {
const { apiKey, course, assignments } = req.body;
try {
const returnedIds = [];
for (const assignment of assignments) {
returnedIds.push(await loadID(apiKey, course, assignment));
await wait(5000);
}
res.status(201).json({ returnedIds })
} catch (e) {
res.status(401);
}
});
function wait(duration) {
return new Promise((resolve) => setTimeout(resolve, duration));
}
function loadID(apiKey, course, assignment) {
// Makes the request, parses out the stuff you want from the response...
}
I would caution against using Promise.all since you probably want to check the result of each request before making the next one. For example, if the third request gets rate limited, you probably shouldn't bother making further requests.
It's because Promise.all will fire all promises at once, so your setTimeout is just set a timeout for all promises not individual promise as well.
You should try to make delay for each promise:
const promises = await assignments.map((assignment) => {
// some delay function
return axios({
method: "PUT",
url: `https://url.to.api/api/v1/courses/${course}/assignments/${assignment.id}`,
params: {
access_token,
},
data: {
assignment: {
points_possible: assignment.points_possible,
},
},
});
});
You can try this: (It's React but you should only focus on fetchData function) and see the logs:
https://codesandbox.io/s/zen-feynman-ql833?file=/src/App.js
Related
I have a function in a Quasar application that needs to call an Web API that I need to wait until I get the results back to call the next function. I've posted my code and I'm sure that I'm missing some await or async or have them in the wrong place.
Global ex='';
testFunction3(arg){
console.log(arg)
} ,
testFunction2(){
this.ex = update_Members_data_term("#musc.edu");
},
async testFunction(){
await this.testFunction2()
this.testFunction3(this.ex)
},
API call:
function update_Members_data_term(term) {
axios.get(
'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi', {
params: {
db: 'pubmed',
api_key: '',
retmode: 'json',
retmax: 200,
term: term
}
}
).then(async response => {
return response.data.esearchresult.idlist;
}).catch(error => {
console.log(error.response)
})
}
Thanks for the help.
testFunction2 Doesn't return a Promise so await does nothing. You are actually doing await null;
So the fix will be:
function update_Members_data_term(term) {
return new Promise((resolve,reject) => {
axios.get(
'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi', {
params: {
db: 'pubmed',
api_key: '',
retmode: 'json',
retmax: 200,
term: term
}
}
).then(async response => {
resolve(response.data.esearchresult.idlist);
}).catch(error => {
reject(error.response)
})
});
}
async testFunction2(){
this.ex = await update_Members_data_term("email_here");
},
Note that you might just await axios.get instead of returning a Promise, but I'm not sure what the syntax is.
I am using web workers to fetch information of websites including their subsites(nth number) recursively. When all of the calls are done I want to fire off a function that would format the data it receives (allSites array). So I thought it would be a great idea to use a Promise.all with an object that has all my resolved promises.
The problem is that it doesn't wait for all the resolved promises because it's waiting to hear messages posted from the worker. I can't define a length because it could be any number of websites + subsites.
Bonus: I have an object with resolved promises. Can I call a certain resolve like this?
keyName[index]()
It says it's not a function but shouldn't I be able to call it like that? Any help is greatly appreciated.
function getTreeData(cb) {
let allSites = [];
let baseUrl = "https://www.somewebsite.com/"
let resolver = {};
let rejecter = {};
let workerUrl =
"https://www.somewebsite.com/siteassets/worker.js";
let myWorker = new Worker(workerUrl);
function firstIteration() {
return new Promise((resolve, reject) => {
resolver[baseUrl] = resolve;
rejecter[baseUrl] = reject;
myWorker.postMessage({
requestDigest: document.getElementById("__REQUESTDIGEST").value,
qs1: "/_api/web/webinfos?$select=ServerRelativeUrl,Title",
qs2:
"/_api/Web/RoleAssignments?$expand=Member/Users,RoleDefinitionBindings",
url: baseUrl,
});
});
}
firstIteration();
//spawn a worker
myWorker.onmessage = function (e) {
allSites = allSites.concat([
{ pathname: e.data.url, groups: e.data.permissions },
]);
e.data.sites.forEach(function (props) {
return new Promise((resolve, reject) => {
myWorker.postMessage({
requestDigest: document.getElementById("__REQUESTDIGEST").value,
qs1: "/_api/web/webinfos?$select=ServerRelativeUrl,Title",
qs2:
"/_api/Web/RoleAssignments?$expand=Member/Users,RoleDefinitionBindings",
url: "www.somewebsite.com" + props.url,
});
resolver[props.url] = resolve;
rejecter[props.url] = reject;
});
});
resolver[e.data.url](); //it says that it is not a function
};
myWorker.onerror = function (e) {
rejecter[e.data.url]();
};
//After my first inital promises resovles resolve the rest (checks object of resolves)
resolver[baseUrl]().then(() => {
Promise.all(Object.values(resolver)).then(() => {
reduceData(cb, allSites);
});
});
}
Though it is working properly here's the code for the web worker. (worker.js)
function formatSites(props) {
return {
url: "www.someSite.com",
};
}
function formatSitesInfo(props) {
//get all info of the site or subsite
var accessArr = props.RoleDefinitionBindings.results
.reduce(function (r, a) {
return r.concat([a.Name]);
}, [])
.sort()
.join(", ");
return {
access: accessArr,
isGroup: props.Member.hasOwnProperty("AllowRequestToJoinLeave")
? true
: false,
name: props.Member.Title,
members: (props.Member.Users?.results || []).map(function (member) {
return {
access: accessArr,
email: member.Email,
groupName: props.Member.Title,
id: member.Id,
isAdmin: member.IsSiteAdmin,
title: member.Title,
};
}),
};
}
function _getRequest(props) {
return new Promise(function (resolve, reject) {
fetch(props.url + props.qs, {
method: "GET",
headers: {
Accept: "application/json; odata=verbose",
"Content-type": "application/json; odata=verbose",
"X-RequestDigest": props.requestDigest,
},
})
.then(function (resp) {
return resp.json();
})
.then(resolve)
.catch(reject);
});
}
self.addEventListener("message", function (e) {
if (!e.data.data) {
var promise1 = _getRequest(Object.assign(e.data, { qs: e.data.qs1 }));
var promise2 = _getRequest(Object.assign(e.data, { qs: e.data.qs2 }));
Promise.all([promise1, promise2]).then(function ([data1, data2]) {
self.postMessage({
info: data2.d.results.map(formatSitesInfo),
sites: data1.d.results.map(formatSites),
url: e.data.url,
});
});
}
});
I ended up making a timer to would be reset if there's a another call to the worker(another subsite was found to have childern). If the timer stops that means that we have reached the end of childern for the sites. It then goes to the next function. There will be a dash of latency, but it's something I can live with.
I've a problem with Axios and foreach loop. My Api provider support only 5 contemporary call so I want call one by one but when execute this code the each body not wait the the finish call function and receive the error code 429. How can resolve this? thanks.
async function call(url) {
var options = {
method: 'GET',
url: url,
auth: {
username: '*****',
password: '*****'
}
};
var response = await axios.request(options);
print(response.data["Id"])
}
app.get('/save', async (req, res) => {
var options = {
method: 'GET',
url: 'getListUser',
auth: {
username: '***',
password: '***'
}
};
var response = await axios.request(options);
response.data["users"].forEach( async (val) => {
console.log("ENTER");
var url = 'getDetailUser' + val["id"];
var res = await call(url); // <- How to wait finish this?
console.log("EXIT")
}, (err) => {
console.log(err)
})
res.status(200).send("ok").end();
});
FYI, Promise couldn't work with loop that involves callback ie forEach. Alternatively, you could use for of
try {
for (const val of response.data['users']) {
console.log("ENTER");
var url = 'getDetailUser' + val["id"];
var res = await call(url);
console.log("EXIT")
}
} catch (error) {
console.log(error)
}
I would say answer by #Ifaruki is correct with a minor change
await Promise.allSettled(response.data["users"].map(val => {
var url = 'getDetailUser' + val["id"];
return call(url);
}))
For details check the difference. In some cases Promise.all might work but if any of the Promise fails, the whole result of Promise.all will be a rejection.
The code 429 can be resolved by looking at 429 Too Many Requests
Promise.all() is the way
await Promise.all(response.data["users"].map(val => {
var url = 'getDetailUser' + val["id"];
return call(url);
}))
I'm working on load testing my API, but at some point I make a call to a different API.
Since I don't want to stress the second one, whenever I'm load testing I want to set a timeout and return an OK response like this:
function sendMessage(requestLib, blockApi, logger) {
if(!blockApi){
return (*my params*) => requestLib(`someURL`, {
headers: { Authorization: `Bearer ${token}` },
method: 'post',
data
});
}else{
logger.info("About to use the promise");
const response = returnOk.then(function() {
return new Response(200, {}, null, 'dummy.com');
});
return response;
}
}
returnOk is a Promise I defined earlier this way:
const returnOk = new Promise((resolve, reject) => {
setTimeout( function() {
resolve("Success!")
}, 2000)
});
And the function sendMessage is called inside a different function like this:
module.exports = ({ requestLib, Logger }) => async function(req, res) {
// Some unrelated code to decide if I'll call sendMessage
const response = await sendMessage(requestLib, blockApi, logger)(params);
// I log the response
res.end();
}
The regular flow works like a charm, but when I'm load testing and I get to returnOk.then()...
It throws
sendMessage(...) is not a function
If I remove the timeout and just return
return new Response(200, {}, null, 'dummy.com');
Things work just fine.
For some reason, your sendMessage(…) function returns another function:
return (*my params*) => …
You will need to do the same when mocking sendMessage:
function sendMessage(requestLib, blockApi, logger) {
if (!blockApi) {
return (*my params*) => requestLib(`someURL`, {
headers: { Authorization: `Bearer ${token}` },
method: 'post',
data
});
} else {
return (*my params*) => {
const returnOk = new Promise((resolve, reject) => {
setTimeout(resolve, 2000)
});
logger.info("About to use the promise");
return returnOk.then(function() {
return new Response(200, {}, null, 'dummy.com');
});
};
}
}
Btw, you really shouldn't have this boolean blockApi parameter to sendMessage. Write two distinct functions with the same signature, and use dependency injection in the code that is calling it.
I'm having some problems getting a response from a chained Promise.
I have my component where the chain starts
component
componentDidMount = async ()=> {
try{
const products = await post('payments/getProducts', {});
console.log(products);
} catch(e) {
console.log(e)
}
}
This component calls my API helper:
async function post(url, data) {
token = null;
if (firebase.auth().currentUser) {
token = await firebase.auth().currentUser.getIdToken();
}
try {
const response = axios({
method: 'POST',
headers: {
Authorization: `${token}`,
},
data,
url: `${API_URL}${url}`,
})
return response;
} catch(e){
Promise.reject(e);
}
}
and my API Helper then calls a Firebase Cloud Function which calls Stripe:
paymentRouter.post('/getProducts', (req, res) => {
return stripe.products.list()
.then(products => {
console.log(products.data)
return products.data;
})
.catch(e => {
throw new functions.https.HttpsError('unknown', err.message, e);
})
})
Calling the function is no problem, and my Cloud Function logs out the product data, but I can't get the response to log in my API Helper nor my component.
Promise.reject(e);
That is completely senseless as it creates a new rejected promise that is not used anywhere. You could await it so that it gets chained into the promise returned by the async function, or you just return the promise from axios:
async function post(url, data) {
let token = null; // always declare variables!
if (firebase.auth().currentUser) {
token = await firebase.auth().currentUser.getIdToken();
}
return axios({
method: 'POST',
headers: {
Authorization: `${token}`,
},
data,
url: `${API_URL}${url}`,
});
}
Now the errors don't go into nowhere anymore and you can probably debug the problem :)