Having some issues when calling an external api to fetch information inside a loop in my node/express backend. I need information I get from the loop to get the correct data back from the endpoint. When I loop through the data and make the call I get this error.
{ message: 'Too Many Requests Limit 30. Reset time 1594218437315',
status: 429 }
I get the correct data back sometimes and sometimes only this message. There will be about 10 000 or so calls I need to make. I've tried a multiple of throttling libs and a lot of the code here on SO but it's pretty much always the same result which is the error message or it doesnt work at all.
I think I need a way send about 20-30 requests at a time and then wait a second or so and then continue. Or is there another better way? How would I achieve this?
const product = await NewProduct.find({});
product.map((item, index) => {
item.stores.map(async inner => {
inner.trackingUrl = await fetchRetry(inner.programId, inner.productUrl)
})
})
async function fetchRetry(id, urlToTrack) {
url1 = 'https://api.adtraction.com/v2/affiliate/tracking/link/?token=token';
const data = {
channelId,
programId: id,
shortLink: true,
url: urlToTrack
};
const options = {
method: 'POST',
headers: {
'Content-type': 'application/json',
Accept: 'application/json',
'Accept-Charset': 'utf-8',
},
body: JSON.stringify(data),
};
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-type': 'application/json',
Accept: 'application/json',
'Accept-Charset': 'utf-8',
},
body: JSON.stringify(data),
});
const json = await res.json();
console.log(json) // Error
return json.trackingUrl;
}
Well, I'll share with you something that I've done in one of my projects. For all of your data that you call with your api. Keep making requests and when you encounter any error or response with throttling then wait for 5 minutes (or required in your api) and move to previous index (previous data to call the api with)
async function callAPI(data) {
for (let index = 0; index < data.length; index++) {
try {
// api call with your data
yourApiCall(data[index]);
} catch (e) {
if (e.type == "RequestThrottled") {
let oneMinute = 60000;
// wait for the time your request will be availabl
await sleep(5 * oneMinute);
// because of error in this request get back to
//previous request (or data)
index--;
}
}
}
}
Function using setTimeout to wait synchronously.
async function sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
}
Also, you may want to run it on some background process as it might block your main event loop.
Related
So I have an axios request to a rapid API, my function looks like this...
//Initialize the lookup API that utalizes rapidAPI to get breach data
app.get("/lookup/:email/:function", (req, res) => {
var options = {
method: "GET",
url: "https://breachdirectory.p.rapidapi.com/",
params: { func: `${req.params.function}`, term: `${req.params.email}` },
headers: {
"x-rapidapi-host": "breachdirectory.p.rapidapi.com",
"x-rapidapi-key": `${config.RAPID_API_KEY}`,
},
};
axios
.request(options)
.then(function (response) {
res.json(response.data);
})
.catch(function (error) {
console.error(error);
});
}
});
The res.json(response.data); will show on the page a result like this:
{
"disclaimer": "This data is aggregated from BreachDirectory, HaveIBeenPwned, and Vigilante.pw.",
"info": "For full source info, request e.g. https://breachdirectory.tk/api/source?name=Animoto",
"sources": [
"123RF",
"500px",
"Adobe",
"AntiPublic",
"Apollo",
"Bitly",
"Dave",
"Disqus",
"Dropbox",
"ExploitIn",
"ShareThis",
"Straffic",
"Ticketfly",
"Tumblr",
"VerificationsIO"
]
}
I want to loop through everything in the "sources" array, and call upon the following:
https://haveibeenpwned.com/api/v3/breach/[ITEM]
So, the first one will call upon https://haveibeenpwned.com/api/v3/breach/123RF
So each result from that call will look like this:
{
"Name": "123RF",
"Title": "123RF",
"Domain": "123rf.com",
"BreachDate": "2020-03-22",
"AddedDate": "2020-11-15T00:59:50Z",
"ModifiedDate": "2020-11-15T01:07:10Z",
"PwnCount": 8661578,
"Description": "In March 2020, the stock photo site 123RF suffered a data breach which impacted over 8 million subscribers and was subsequently sold online. The breach included email, IP and physical addresses, names, phone numbers and passwords stored as MD5 hashes. The data was provided to HIBP by dehashed.com.",
"LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/123RF.png",
"DataClasses": [
"Email addresses",
"IP addresses",
"Names",
"Passwords",
"Phone numbers",
"Physical addresses",
"Usernames"
],
"IsVerified": true,
"IsFabricated": false,
"IsSensitive": false,
"IsRetired": false,
"IsSpamList": false
}
I want to make my res.json send over a JSON string that will have all the sources still there, along with the "Title","Description", and "LogoPath" from the API calls that it pulled for each one of the sources. So I will have a JSON string with the sources along with the title of each source, description of each source, and LogoPath of each source.
You have two options:
Create an array of promises and run with Promise.all
app.get('/lookup/:email/:function', async (req, res) => {
var options = {
method: 'GET',
url: 'https://breachdirectory.p.rapidapi.com/',
params: { func: `${req.params.function}`, term: `${req.params.email}` },
headers: {
'x-rapidapi-host': 'breachdirectory.p.rapidapi.com',
'x-rapidapi-key': `${config.RAPID_API_KEY}`,
},
};
axios.request(options)
.then((response) => {
const requestTasks = [];
for (let item of response.data.sources) {
const itemOption = {
method: 'GET',
url: `https://haveibeenpwned.com/api/v3/breach/${item}`,
headers: {
'content-type': 'application/json; charset=utf-8'
}
};
requestTasks.push(axios.request(itemOption));
}
return Promise.all(requestTasks);
})
.then((responseList) => {
for (let response of responseList) {
console.log(response.data);
}
})
.catch((error) => {
console.error(error);
});
});
Use async/await (promise) and for await for get data from for loop
app.get('/lookup/:email/:function', async (req, res) => {
try {
var options = {
method: 'GET',
url: 'https://breachdirectory.p.rapidapi.com/',
params: { func: `${req.params.function}`, term: `${req.params.email}` },
headers: {
'x-rapidapi-host': 'breachdirectory.p.rapidapi.com',
'x-rapidapi-key': `${config.RAPID_API_KEY}`,
},
};
const response = await axios.request(options);
for await (let item of response.data.sources) {
const itemOption = {
method: 'GET',
url: `https://haveibeenpwned.com/api/v3/breach/${item}`,
headers: {
'content-type': 'application/json; charset=utf-8'
}
};
const itemResponse = await axios.request(itemOption);
console.log(itemResponse.data);
}
} catch (error) {
console.error(error);
}
});
This how I managed to make it works.
first: I didn't had any APi key (and didn't want to register to get one) So i used a dummy Api.
although the logic stay the same as i have tested the result.
second i kept all your initial url just next to the one i used.so you can easily switch back to your original url.
finally i put comment to any critical part, and i named variable in a
way that they almost describe what they do.
so you can copy past test it to understand my logic then adapt it to your use case.
here the code
// make sure to replace /lookup by /lookup/:email/:function after testing my logic
app.get('/lookup', async (req, res) => {
try {
// in this options no change just switch back to your url
var options = {
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/albums',
// url: "https://breachdirectory.p.rapidapi.com/",
// params: { func: `${req.params.function}`, term: `${req.params.email}` },
// headers: {
// 'x-rapidapi-host': 'breachdirectory.p.rapidapi.com',
// 'x-rapidapi-key': `${config.RAPID_API_KEY}`,
// },
};
// here you get all your sources list (in my case it an array of object check picture 1 bellow)
const allSources = await axios.request(options)
console.log(allSources.data);
// because my dummy api response is a huge array i slice to limited number
const reduceAllsource = allSources.data.slice(0,5);
console.log(reduceAllsource);
// note here you need to replace reduceAllsource.map by allSources.data.map
// because you don't need a sliced array
const allSourcesWithDetails = reduceAllsource.map(async (_1sourceEachtime)=>{
// here you can switch back to your original url
// make sure to replace [ITEM] by ${_1sourceEachtime}
const itemOption = await axios({
method: 'GET',
url: `https://jsonplaceholder.typicode.com/albums/${_1sourceEachtime.id}/photos`,
// url:`https://haveibeenpwned.com/api/v3/breach/[ITEM]`
headers: {
'content-type': 'application/json; charset=utf-8'
}
});
// this the place you can mix the 2 result.
const mixRes1AndRes2 ={
sources:_1sourceEachtime.title,
details:itemOption.data.slice(0,1)
}
return mixRes1AndRes2;
})
// final result look like the picture 2 below
finalRes= await Promise.all(allSourcesWithDetails);
return res.status(200).json({response: finalRes});
}
catch (error) {
console.log(error);
}
});
Picture 1
Picture 2
I'm trying to do a remote call in my node.js application to an external URL, then parse that request and perform an action based on the return. I'm using express and mysql.
I was able to get the remote URL content, however, I'm having some kind of race condition where my output is always changing and is not reliable. I tried to use async/await but wasn't able to.
This is the function called to run the app:
function lista(servidores) {
return new Promise(function(resolve, reject) {
var sql = ' SELECT sv.id as svid, sv.ip as svip'+
' FROM servidores sv'
dbconfig.conexao.query(sql, function (err, result, fields) {
Promise.all(
result.map(row => {
var ipsv = row.svip;
var urlprobe = 'http://201.182.96.14:8000/PING/' + ipsv;
fetch(urlprobe, {
method: 'get',
headers: {
'Accept': 'application/json, application/xml, text/plain, text/html, *.*',
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
},})
.then(
response => response.json(),
error => console.log('Ocorreu um erro', error)
)
.then(
json => console.log(json),
)
})
).then(result => result);
return resolve();
});
})
}
When all these functions are called, everything works ok until the monitora() function. The output is random based on which fetch answers faster, and therefore the result is not reliable. The ideal is that
monitora() performs each fetch separetely and then process the if's based on each one of the results.
#edit: I edited the code and made the fetch directly on the main function, however I'm still receiving inconsistent results, like if there was some sort of caching somewhere.
I'm not sure if I understand correctly but you can try using Promise.all() function to wait for all fetch requests to finish, before taking an action.
Promise.all(
result.map(row => {
var urlMon = 'http://' + row.pip + ':8000/PING/' + ipsv;
monitora(urlMon, idsv);
})
).then(result => result /*Do something */);
I have to make get requests to a slow API and keep track of the response status. I can only fetch the data (with another get request and a different url) once the initial dispatch status is 'Done'. Some search queries are fetched faster than others, so I have to keep that in mind as well
I used the javascript setTimeout function and waited 20 seconds for all the search queries to finish. This is a hit or miss approach as some queries are fetched faster than 20 seconds and some later
async function get_dispatch_state(sid) {
let dispatchState = "";
let json = await axios.get(
`https://digitals.devfg.test.com:8089/services/search/jobs/${sid}?output_mode=json`,
{
method: "get",
auth: auth,
headers: options
}
);
dispatchState = json.data.entry[0]["content"].dispatchState;
return dispatchState;
}
function get__data() {
axios({
method: "get",
url: `https://digitalsp.devfg.test.com:8089/services/search/jobs/test_search_1/results?output_mode=json`,
auth: auth,
headers: options
})
.then(datax => {
fraud_line_1d = datax.data;
console.log("***Fraud line 1****" + JSON.stringify(fraud_line_1d));
})
.catch(error => {
console.log("second error is " + error);
});
// repeat other get requests
}
setTimeout(function() {
get_data();
}, 20000);
All data is eventually fetched but at different intervals depending on how large the search query is. I need some advice on the best way to fetch the data once the dispatch status is Done.
You can use Promise.all() method returns a single Promise that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises. It rejects with the reason of the first promise that rejects.
function get_dispatch_state(sid) {
return axios.get(
`https://digitals.devfg.test.com:8089/services/search/jobs/${sid}?output_mode=json`,
{
method: 'get',
auth: auth,
headers: options
}
)
.then(json => {
return json.data.entry[0]['content'].dispatchState;
});
}
function get__data() {
axios({
method: 'get',
url: `https://digitalsp.devfg.test.com:8089/services/search/jobs/test_search_1/results?output_mode=json`,
auth: auth,
headers: options
})
.then(datax => {
fraud_line_1d = datax.data;
return JSON.stringify(fraud_line_1d);
});
// repeat other get requests
}
Promise.all([get_dispatch_state() , get__data()])
.then(data => {
// Array with the response of both request at the same time
})
Small sample of how we should implement it.
More information on the subject here.
I'm building a front-end application that makes HTTP requests to 2 separate API's.
http://greetings_api:3000/getGreeting
data {'language': 'es'}
Response: 'Hola'
http://users_api:3000/getUser
data {'userid': 1}
Response: 'Jose Smith'
I have a single route that makes a request to these API's and then returns those responses:
var http = require('http');
var request = require('request-promise');
var greetingOptions = {
uri: 'http://greetings_api:3000/getGreeting',
hostname: 'greetings_api',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'language': 'es'
}
};
var greeting = {
getGreeting: function() {
return request(greetingOptions);
}
}
function myGreeting() {
return greeting.getGreeting();
};
var userOptions = {
uri: 'http://users_api:3000/getUser',
hostname: 'users_api',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'id': 1
}
};
var user = {
getUser: function() {
return request(userOptions);
}
}
function myUser() {
return user.getUser();
};
function getWelcome(req, res) {
// How do you store this....
myUser().then(function(result) {
console.log('result')
return result;
})
// ...and this...
myGreeting().then(function(result) {
console.log('Greet ' + result);
return result;
});
/// ...and then send them with this?
res.send(greeting + ' ' + user);
}
module.exports = { getWelcome };
So with the current code I get the correct output in the console. The problem is that I need to be able to send the response from the route with the combination of both API responses. What is the simplest way to accomplish this?
You are sending the response before the promises resolving.
with async/await we can write asynchronous code that looks and behaves like synchronous.
async function getWelcome(req, res) {
// we can wrap our operation in a try/catch block to handle
// both asynchronous and synchronous errors
try {
// with the await keyword we can wait for all promises to resolve
// before we continue with our code
/* if one of the promises inside Promise.all rejects we move to the catch block */
const [user, greeting] = await Promise.all([
myUser(),
myGreeting()
]);
// send the response if no errors
res.send(greeting + ' ' + user);
catch(e) {
res.status(404).send();
}
}
You need to make 2 parallel requests and send response after completing both. Here Promise.all function can help you.
Promise.all([myUser(), myGreeting()]).then(function(result) {
// result[0] - user
// result[1] - greeting
res.send(result[0] + ' ' + result[1]);
});
I'm working with Asana and Zapier, and am working to GET a Section within a project, and optionally create one if it doesn't exist. This action doesn't exist natively in Zapier.
I used Zapier Code to do the GET, and that code successfully returns the sectionId as either 0, or the actual id from Asana.
In the next action, I'm doing Zapier Code again, and I'm trying to do the POST to create the Section, but only if the sectionId returned by the GET action is 0.
I think the issue I'm having has to do with a lack of understanding of asynchronous programming. But, I was more or less copied the code from the successful GET action into the new action, and changed the HTTP request a bit to be a POST.
If I remove the if statement, then the POST works as expected.
Where I'm getting stuck is wrapping the fetch in an if statement. I've tried to simply wrap it in an if statement (if 0, do the fetch, else, return the ID that's not 0), and create it as a function that I put within the if statement. I've tried a few different configurations of each method, but either way, the error I'm receiving is:
Bargle. We hit an error creating a run javascript. :-( Error:
ReferenceError: output is not defined
Here's my code:
var sId = input.sId;
function doFetch(aPId) {
var settings = {
'method': 'POST',
'headers': {
'Authorization': 'Bearer <ACCESS TOKEN>',
'Content-Type': 'application/x-www-form-urlencoded'
},
'body': "name=Emails%3A"
};
var url = 'https://app.asana.com/api/1.0/projects/' + aPId + '/sections';
fetch(url,settings)
.then(function(res) {
return res.json();
})
.then(function(json) {
callback(null, json);
})
.catch(callback);
}
if(sId === 0){
return doFetch(input.asanaProjectId);
}
output = [{test: 123}];
Any thoughts?
Ok, I fixed it. I can't explain it fully, but this worked. I believe the if statement itself was the issue.
var sId = inputData.sId;
function createSection(aPId) {
var settings = {
'method': 'POST',
'headers': {
'Authorization': 'Bearer <ACCESS TOKEN>',
'Content-Type': 'application/x-www-form-urlencoded'
},
'body': "name=Emails%3A"
};
var url = 'https://app.asana.com/api/1.0/projects/' + aPId + '/sections';
console.log('Settings:',settings);
console.log('URL:',url);
fetch(url,settings)
.then(function(res) {
var response = res;
//console.log(response);
return response.json();
})
.then(function(json) {
//console.log(json);
var sId = json['data']['id'];
callback(null,{sectionId: sId});
})
.catch(callback);
}
if(sId == 0){
var sId = createSection(input.asanaProjectId);
console.log('If test ran!');
} else {
output = [{sectionId: sId}];
}