Set timeout for external API in Promise resolve - javascript

I have an API that returns some values and depend on that values I want to create an array and pass that array in resolve. It works when set a timeout for resolve, for example, I set 5000 sec within 5 sec it fetch the data, push array and resolve. But I don't want to set a timeout. Please see the comments for bertter understand. its an nodejs function.
const calculateTotal = (arr) => {
let clientHotspotsTotal = [];
return new Promise((resolve, reject) => {
arr.forEach((data) => {
axios.get(`enpoint/data?.address`)
.then((response) => {
if (response?.data) {
console.log('API SUCCESS');
const val = (response?.data?.data?.total * data?.percentage) / 100;
clientHotspotsTotal.push({
total: val,
});
} else {
reject('API failed!');
}
})
.catch((error) => {
throw new Error(error);
});
});
resolve(clientHotspotsTotal); /// it return empty [], cause no time set for wait
setTimeout(() => {
resolve(clientHotspotsTotal) // it doesn't return empty because in that time api fetch data
an push into array
}, 5000)
});
};
So my question is how can I wait for that API response for multiple requests as I run a for loop for multiple users.

You can simplify this a lot by using async / await and Promise.all. We can create an array of promises from the axios calls, then get the response data using Promise.all.
We then iterate through the responses, returning the result.
const calculateTotal = async (arr) => {
const responses = await Promise.all(arr.map(data => axios.get(`enpoint/${data?.address}`)));
const clientHotspotsTotal = responses.map((response, i) => {
return({ total: (response?.data?.data?.total * arr[i]?.percentage) / 100});
});
return clientHotspotsTotal;
};
async function testIt() {
let result = await calculateTotal([
{ address: 'someaddress', percentage: 0.50 },
{ address: 'someaddress', percentage: 0.75 }
]);
console.log(result);
}
testIt()

Related

How to return a Promise with consequential axios calls?

I need to create a function, that will return a Promise and will call another function that will have an axios.get() call. Axios.get() calls an API that returns a data with the following structure:
{
count: 87, //total number of records
next: '[api_url]/&page=2'//indication if the API has a next page
previous: null, ////indication if the API has a previous page, if call URL above - it will return [api_url]/&page=1
results: [...] //10 objects for each call, or remaining for the last page
}
Since I know that only 10 results are being returned on every call, I need to check if the returned object has the next key, and if it does - make another call and so on, until no more next. I need to concatenate all the results and eventually resolve the Promise returned from the main function with all the data.
So I tried something like that:
const fetchResource = async({type, search, page}) {
const data = {count: 0, results: []}
const request = await performFetch({type, search, page}, data).then((data) => {
console.log('data?', data)
})
console.log('req', request)
}
const performFetch = async({type, search, page}, result) => {
const params = {
page
}
if (search) {
params.search = search
}
await axios.get(`${type}/`, {
params
}).then(async({data}) => {
result.results = [...result.results, ...data.results]
result.count = data.count
if (data.next) {
page += 1
await performFetch({type, search, page}, result)
} else {
console.log('result', result)
return result
}
})
.catch((err) => {
console.error(err)
})
}
Now I see that once I call fetchResourche all the requests are going out, and in console.log('result', result) I do see the concatenated data:
{
count: 87,
results: [/*all 87 objects*/]
}
But console.log('data?', data) and console.log('req', request) both print out undefined.
Where I return result, I tried to return Promise.resolve(result) - same result.
And I'm not sure how to return a Promise here, that will resolve once all the API calls are concluded and all the data is received. What am I missing? How do I make it work?
Couple of observations regarding your code:
There's no need to mix async-await syntax with promise chaining, i.e. then() and catch() method calls
Inside performFetch function, you need an explicit return statement. Currently, the function is implicitly returning a promise that fulfils with the value of undefined.
Key point here is that the performFetch function is returning before you get the result of http requests to the API.
It isn't waiting for the result of HTTP requests to be returned before returning.
Following is a simple demo that illustrates how you can make multiple requests and aggregate the data until API has returned all the data.
let counter = 0;
function fakeAPI() {
return new Promise(resolve => {
setTimeout(() => {
if (counter < 5) resolve({ counter: counter++ });
else resolve({ done: true });
}, 1000);
});
}
async function performFetch() {
const results = [];
// keep calling the `fakeAPI` function
// until it returns "{ done = true }"
while (true) {
const result = await fakeAPI();
if (result.done) break;
else results.push(result);
}
return results;
}
performFetch().then(console.log).catch(console.log);
<small>Wait for 5 seconds</small>
Your code can be rewritten as shown below:
const fetchResource = async ({ type, search, page }) => {
const data = { count: 0, results: [] };
const result = await performFetch({ type, search, page }, data);
console.log(result);
};
const performFetch = async ({ type, search, page }, result) => {
const params = { page };
if (search) params.search = search;
while (true) {
const { data } = await axios.get(`${type}/`, { params });
result.results = [...result.results, ...data.results];
result.count = data.count;
if (data.next) page += 1;
else return result;
}
};
Ideally, the code that calls the fetchResource function should do the error handling in case any of the HTTP request fails.

How to pick which JSON file to use from multiple API calls

Hey I'm using promise to fetch 2 different API calls, that both return files with similar/matching data.
I'm trying to use one of the API calls to display a couple of numbers (balance of 2 wallets), which worked fine when there was only one API call, I was doing it like this:
const rewardWallet = data.result[0];
const rewardBalance = rewardWallet.balance;
So, data.result[0] was selecting result, and then the first array (0) and then the data from 'balance'. But now that there are 2 API calls, 'result' is under '0' and I can't figure out how to select the top level.
Link to results API returns
I added the second API call like this:
Promise.all([
fetch('https://api.bscscan.com/api?module=account&action=balancemulti&address=0xa3968Fba9D45D52b6b978f575934364ac2d5774c,0x121F0a592a25A52E8834C87005CbF4db000010Af&tag=latest&apikey=key'),
fetch('https://api.bscscan.com/api?module=account&action=tokenbalance&contractaddress=0x7b50c95f065dc48a0ecf8e5f992bf9bb9f4299c5&address=0x121F0a592a25A52E8834C87005CbF4db000010Af&tag=latest&apikey=key')
]).then(function (responses) {
return Promise.all(responses.map(function (response) {
return response.json();
}));
}).then(function (data) {
console.log(data);
}).catch(function (error) {
console.log(error);
});
Appreciate any help.
You can use Promise.all to ensure request are fired off and then work with the response after all promises have resolved. See below, I've added some mock functions that mock the API calls that will just return an object with an amount.
const fetchWalletOne = () => {
return new Promise((resolve) => {
resolve({ amount: 1 });
});
};
const fetchWalletTwo = () => {
return new Promise((resolve) => {
resolve({ amount: 5 });
});
};
async function getData() {
const [walletOne, walletTwo] = await Promise.all([
fetchWalletOne(),
fetchWalletTwo()
]);
console.log(walletOne, walletTwo);
console.log(walletOne.amount + walletTwo.amount);
}
getData();

How to combine multiple response , where function take different parameter, to give different response from a single function?

how do I combine multiple response, where function takes a different parameter, to give different response from a single function, I want all response should be combined in a single array.
const data = [
{id:1,name:"currency"},
{id:1,name:"merchant"},
{id:1,name:"broker"},
]
data.map(i=>{
getData(i.name)
})
getData(name){
const response = getResponsedata(name);
if (response?.data && response?.status === 200) {
            setData(response.data);
}
}
getResponsedata(name){
try{
const response = await axios.get(`/api/data/${name}`)
return response
}
here it gives me a different array, I want all in one
Promise.all. Take an array of promises and wait for them to resolve (or not).
const data = [
{ id:1, name:'currency' },
{ id:1, name:'merchant' },
{ id:1, name:'broker' },
];
// Return the name after two seconds
function mockFetch(name) {
return new Promise((res, rej) => {
setTimeout(() => res(name), 2000);
});
};
// Create an array of promises
const promises = data.map(obj => mockFetch(obj.name));
// Wait for all the data to resolve - an array is returned
Promise.all(promises).then(data => console.log(data));
You can also use async/await:
async getData() {
const data = await Promise.all(promises);
console.log(data);
}
getData();

Promise.allSettled - set time delay to avoid API Abuse Rate Limits

I'm working with Promise.allSettled to do multiple fetches to the Github API for a few hundred requests. I'm getting blocked as they're all getting called at once, so I want to add a delay of 5ms between each. Is this possible with Promise.allSettled?
allSettled will neither help nor hinder you; it has no logic at all with regard to timing, other than to wait for the promises to settle. It will be up to you to create your promises so that they have the delays you want.
For example:
const delay = (milliseconds) => new Promise(resolve => setTimeout(resolve, milliseconds);
const urls = ['www.foo.com/whatever', /* etc for a few hundred urls */];
const promises = urls.map((url, index) => {
return delay(index * 5)
.then(() => fetch(url))
})
Promise.allSettled(promises)
.then(results => {
// do whatever with the results
});
Probably you need a concurrency limit, not a delay.
Fetch with concurrency limit:
Plain JS demo
const urls = [
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo1",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo2",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo3",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo4",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo5",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo6",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo7"
];
function fetchBottleneck(concurrency = 1) {
const queue = [];
let pending = 0;
return async (...args) => {
if (pending === concurrency) {
await new Promise((resolve) => queue.push(resolve));
}
pending++;
return fetch(...args).then((value) => {
pending--;
queue.length && queue.shift()();
return value;
});
};
}
// Use fetch queue
(async () => {
const fetchInQueue = fetchBottleneck(3);
//No matter how many times you call the 'fetchInQueue' method, you get a maximum of 3 pending requests at the same time.
const results = await Promise.all(
urls.map(async (url) => {
try {
const response = await fetchInQueue(url);
console.log(`Got response for ${url}`);
return { status: "fulfilled", value: await response.json() };
} catch (reason) {
return { status: "rejected", reason };
}
})
);
console.log("Done:", JSON.stringify(results));
})();
Better solution is to use concurrency limitation provided by several third-party libs (e.g. http://bluebirdjs.com/docs/api/promise.map.html)
Live browser example
import CPromise from "c-promise2";
CPromise.allSettled(
[
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo1",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=4s&foo2",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=2s&foo3",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo4",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=4s&foo5",
"https://fake.url.to.test/",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo6",
"https://run.mocky.io/v3/40594989-00c2-4798-94da-c4a9ab251558?mocky-delay=3s&foo7"
],
{
mapper: (url) => {
console.log(`request [${url}]`);
return fetch(url).then((response) => response.json());
},
concurrency: 2
}
).then((results) => {
console.log(`Done: `, JSON.stringify(results));
});

javascript node.js async-await electron

I can't seem to find a relevant solution to this issue.
I am trying to use a foreach loop to call out for data. I have tried multiple variations of packaging this in promises but cannot seem to halt this function from proceeding before finishing the loop. Hoping someone may have some insight.
async function getData(){
temparray [] = { data to iterate over };
await getAgents();
console.log('done');
function getAgents(){
return new Promise(resolve => {
temparray.forEach((deal) => {
pipedrive.Persons.get(deal.agent, function(err, person) {
if (err) throw err;
console.log("trying to get agent " + deal.agent)
console.log(person.name);
});
});
}); // end of promise
};
};
So what I'm trying to do here is create a promise that resolves once all of the async calls are completed, but the process marches on before this has completed. Hence 'done' logs to the console before the getAgents() function has completed and logged the names. I suspect it has to do with the callbacks, but I've tried converting it to a promise, I've tried util.promiseify, and I've looked at building an array of promises and then using promise.all but haven't tested it.
Here's how you should do with another example
exec = async () => {
let tmp = [1,2,3,4,5,6];
let value = await Promise.all(tmp.map(id => getIdAfterTimeout(id)));
console.log(value);
}
getIdAfterTimeout = (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(id);
}, 2000);
})
}
exec();
You could covert a function with callback style to a promise.
My example for you case:
async function getData() {
temparray[] = {data to iterate over};
let persons = await getAgents(temparray);
console.log(persons);
console.log('done');
};
async function getAgents(temparray) {
await Promise.all(
temparray.map((deal) => {
return new Promise(resolve => {
pipedrive.Persons.get(deal.agent, function(err, person) {
if (err) throw err;
console.log("trying to get agent " + deal.agent)
console.log(person.name);
resolve(person);
});
})
})
);
};
You can use Promise.all or old school for loop (fori), I think you need read more about Promise async/await.
async function getData(){
// this or you can do await
Promise.all(temparray.map(temp => getAgents(temp)))
.then(data => {
console.log('done');
})
.catch(e => {
console.log('error' );
})
// if you do await .. do like this
const {error, data } = await Promise.all(promises);
console.log(data);
}
function getAgents(id){
return new Promise((resolve, reject) => {
pipedrive.Persons.get(deal.agent, function(err, person){
if (err)
return resolve({error: err, data: null});
return resolve({error : null, data: person});
});
}); // end of promise
}
I even was stuck in similar problem yesterday. I applied following logic to solve this
Create a counter that tracks how many async requests are completed
Increment the counter in the callback function of async request
Add check if counter is equal to length of array
Resolve the promise once the counter is equal to the length of input array
const fs = require('fs');
async function getData() {
const array = [1, 2, 3, 4, 5, 6];
// Create a counter that tracks how many async requests are completed
let counter = 0;
await getAgents();
console.log('done');
async function getAgents() {
return new Promise(resolve => {
array.forEach((item, i) => {
//do async stuff here
fs.writeFile(`${i}.file.txt`, `line in file ${i}`, err => {
if (err) throw err;
// Increment the counter in the callback function of async request
// Add check if counter is equal to length of array
if (++counter == array.length) {
// Resolve the promise once the counter is equal to the length of input array
resolve(counter);
}
});
});
});
}
}
getData();

Categories

Resources