This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
I have an item that I got from an Async function as follows:
items = await statusCheck(items.split('\n'))
The items is an array which I need to add to localStorage. The issue is, although it has elements inside of it, they are not accessible.
Therefore, I have to do this:
items = await statusCheck(items.split('\n'))
setTimeout(() => {
localStorage.setItem(localKey, items.join('\n'))
}, 1000)
Is there a more elegant way of doing this without having to resort to a setTimeout function?
Is there more information I can provide to help resolve this issue? The FULL code is as follows:
async function clearHistory() {
var localKey = "uploadcare_" + UPLOADCARE_PUBLIC_KEY
var items = localStorage.getItem(localKey);
items = await statusCheck(items.split('\n'))
setTimeout(() => {
localStorage.setItem(localKey, items.join("\n"));
}, 1000)
}
async function statusCheck (items) {
let newItems = []
items.forEach(async (item) => {
let url = "https://somelink" + item.split(' ')[0] + "/54x54/"
let status = await fetchResponse(url)
if (status === 200) {
newItems.push(item)
}
})
return newItems
}
async function fetchResponse(url) {
try {
let response = await fetch(url)
let status = response.status
return status
} catch (e) {
console.log(e)
}
}
clearHistory()
You shouldn't be doing a forEach with async. You should use map and get each promise out. Then do Promise.all. Something like this:
async function statusCheck (items) {
let promises = items.map(item => {
let url = "https://somelink" + item.split(' ')[0] + "/54x54/";
return fetchResponse(url);
});
await Promise.all(promises);
// figure out which ones to return.
}
Async functions need to return a Promise. Your code runs synchronously. I am not sure why the browser won't tell you. So your statusCheck() function needs to return a Promise object to be awaitable. This also applies to the the forEach loop in the statusCheck. And I think that you cant do the foreach async because the native function wont wait for those callbacks.
See this reference for async function: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/async_function
There is no reason to use setTimeout. the main problem is that you are using await inside the function you are passing to forEach, and there is no reason for javascript to wait until all of the requests resolve and then returning the array. it keeps going and returns possibly an empty array first. and when you are using the new created array it, some of the requests might resolve and they results will be pushed to that array. you can use this instead:
async function statusCheck (items) {
return Promise.all(items.map(async (item) => {
let url = "https://somelink" + item.split(' ')[0] + "/54x54/"
let status = await fetchResponse(URL)
if(status !== 200) {
return;
}
return item;
}).filter(Boolean);
}
I used Boolean just to remove items that checking them by API might result in a status other than 200. in this case the function return;s or in the other words, returns undefined. as a matter of fact, I've filtered those results by filter(Boolean)
Related
This question already has answers here:
How do I convert an existing callback API to promises?
(24 answers)
Closed 2 years ago.
I'm building a trading bot that needs to get stock names from separate files. But even I have used async function and await in my code, that doesn't work.
My index file init method.
const init = async () => {
const symbols = await getDownTrendingStock();
console.log("All stocks that are down: " + symbols);
const doOrder = async () => {
//do stuff
}
doOrder();
}
my getDownTrendeingStock file
const downStocks = []
function getDownTrendingStock () {
for(i = 0; i < data.USDTPairs.length; i++){
const USDTPair = data.USDTPairs[i] + "USDT";
binance.prevDay(USDTPair, (error, prevDay, symbol) => {
if(prevDay.priceChangePercent < -2){
downStocks.push(symbol)
}
});
}
return downStocks;
}
I have tried to also use async in for loop because the getDownTrendinStock function returns an empty array before for loop is finished. I didn't find the right way to do that because I was confused with all async, promise and callback stuff. What is the right statement to use in this situation?
Output:
All stocks that are down:
Wanted output:
All stocks that are down: [BTCUSDT, TRXUSDT, ATOMUSDT...]
I think the main issue in the code you posted is that you are mixing callbacks and promises.
This is what's happening in your getDownTrendingStock function:
You start iterating over the data.USDTPairs array, picking the first element
You call binance.prevDay. This does nothing yet, because its an asynchronous function that takes a bit of time. Notably, no data is added to downStocks yet.
You continue doing 1-2, still, no data is added
You return downStocks, which is still empty.
Your function is complete, you print the empty array
Now, at some point, the nodejs event loop continues and starts working on those asynchronous tasks you created earlier by calling binance.prevDay. Internally, it probably calls an API, which takes time; once that call is completed, it calls the function you provided, which pushes data to the downStocks array.
In summary, you didn't wait for the async code to complete. You can achieve this in multiple ways.
One is to wrap this in a promise and then await that promise:
const result= await new Promise((resolve, reject) => {
binance.prevDay(USDTPair, (error, prevDay, symbol) => {
if (error) {
reject(error);
} else {
resolve({prevDay, symbol});
}
});
});
if(result.prevDay.priceChangePercent < -2){
downStocks.push(result.symbol)
}
Note that you can probably also use promisify for this. Also, this means that you will wait for one request to finish before starting the next, which may slow down your code considerably, depending on how many calls you need; you may want to look into Promise.all as well.
Generally speaking, I use two technics:
const asyncFunc = () => {smthAsync};
const arrayToProcess = [];
// 1
const result = await arrayToProcess.reduce((acc, value) => acc.then(() => asyncFunc(value)), Promise.resolve(someInitialValue));
// 2
// here will be eslint errors
for(let i = 0 i < arrayToProcess.length; i+=1) {
const processResult = await asyncFunc(value);
// do with processResult what you want
};
This question already has answers here:
Why is my asynchronous function returning Promise { <pending> } instead of a value?
(9 answers)
Closed 3 years ago.
I'm trying to make a bunch of request an await to all of them to be completed with a Promise.all() function, but instead of doing manually all the fetchs like this:
var data = await Promise.all([
fetch('https://jsonplaceholder.typicode.com/posts').then((response) => response.json()),
fetch('https://jsonplaceholder.typicode.com/albums').then((response) => response.json()),
fetch('https://jsonplaceholder.typicode.com/users').then((response) => response.json())
]);
i want to make it dynamic, to make N fetch requests like this:
let promiseList = [];
try {
for (let url of requestUrls) {
promiseList.push(fetch(url).then((response) => response.json()));
}
var data = await Promise.all(promiseList);
But i get this error Uncaught SyntaxError: await is only valid in async function in the await Promise.all() line, if i delete the await, i get a PromiseĀ {<pending>} and
(index):79 error:TypeError: data is not iterable
This is my full code: https://jsfiddle.net/ham7g82e/1/
What i'm missing to get the data from those fetchs?
Don't use await, instead use Promise.then
Promise.all(promiseList).then(data => {
document.getElementById("result").innerHTML = data;
console.log(data);
for (var i of data) {
console.log(`RESPONSE ITEM \n`);
for (var obj of i) {
console.log(obj);
}
}
});
To use await, it needs to be part of an async function.
async function functionName() {
//You can use await in here, because you used the async keyword
}
If the function where are executing this code is not async, you could just use the .then() to get the values from the promises. There is not need to use await.
Check this documentation: Promise.all()
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed last year.
In node.js I need to use a function procesMultipleCandidates () which contains Array.foreach which process insert every element into db. but the entire function should return response after completing all insertion operation
JavaScript Code
async function procesMultipleCandidates (data) {
let generatedResponse = []
await data.forEach(async (elem) => {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
//and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
})
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}
And as described above last return statement not waiting for the foreach execution to complete first.
Use Array.prototype.map and Promise.all:
async function procesMultipleCandidates (data) {
let generatedResponse = []
await Promise.all(data.map(async (elem) => {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
// and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
}))
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}
Or use a for/of loop if you don't want the loop run concurrently:
async function procesMultipleCandidates (data) {
let generatedResponse = []
for(let elem of data) {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
// and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
}
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}
Array.prototype.forEach() tries to execute sequentially and you may not always get the expected result.
Array.prototype.map() is the widely used for creating Promises using data and then resolved using await Promise.all([])
using .map() function, all the promises are resolved in parallel and if you happen to call an API you may end up getting error 429: Too many Requests
To execute all the promises sequentially, you can use for ...of.
const array1 = ['a', 'b', 'c'];
for (const element of array1) {
console.log(element);
}
MDN Reference
As far as I'm concern, for of works better with asynchrony than forEach. Anyway, in this case, if you only care for the aggregated result, I think this could be a solution.
async function procesMultipleCandidates(data) {
const promises = data.map(insertionInCandidate);
return await Promise.all(promises);
}
Use Promise.all instead, which will resolve once all Promises in the array passed to it have resolved. The syntax will probably be simplified if you use and return a plain Promise chain rather than async/await:
const procesMultipleCandidates = data => Promise.all(
data.map(insertionInCandidate)
)
.catch(err => {
console.log('err: ' + err);
});
Note that if there's an error, procesMultipleCandidates will currently return a resolved Promise rather than rejecting - it might be a better idea to have the consumer of procesMultipleCandidates handle errors, so that it can handle them appropriately (rather than simply console.logging it).
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 4 years ago.
First of all I did read through similar questions, and still cannot see where I'm making my mistake.
Here's my code:
async function validateWebsites(website) {
var result = url.parse(`http://${website}`);
console.log(result.hostname);
return await fetch(`http://www.${result.hostname}`)
.then(() => console.log(true))
.catch(() => console.log(false));
}
var wrongWebsites = [];
var wrongWebsites = [];
var i = 0;
websites.forEach(website => {
i++;
if (validateWebsites(website) === false
) {
wrongWebsites.push(i);
}
});
console.log(wrongWebsites);
How it works:
The user passes an array of websites, and I want to validate if they're valid websites, not to waste resources and block other errors. Now to the console:
digitlead.com
google.com
georgiancollege.ca
youtube.com
[]
true
true
true
true
So as you see, it prints out first the websites array, and then the response. So it's still async. How do I make it wait? I changed the loop from a for to forEach as suggested by many posts, I used the await and I am returning a promise. So what else do I have to do?
Edit:
I tried to do this:
async function validateWebsites(website) {
var result = url.parse(`http://${website}`); // TODO figure out if filtering all the subpages is a good idea.
console.log(result.hostname);
return await fetch(`http://www.${result.hostname}`)
.then(()=>console.log(true))
.catch(()=>console.log(false));
}
But it doesn't change anything
I found a function called readFileSync. That's more or less what I'm looking for, but with the ability to call a different website.
Here is how you can get all valid websites.
Problem with your validateWebsites function is that it returns promise wchih is resolving to undefined thanks to promise chaining and your loging
Also using forEach to filter array is unnesesery.
But if you wanted you could do something like this
websites.forEach(async website => {
i++;
if (await validateWebsites(website) === false) { // now value is Boolean instead of Promise
wrongWebsites.push(i);
}
});
Also note that if you use global i with asyncronous functions to keep track of index this can lead to many errors.
However I think this soultion should satisfy you
async function validateWebsites(website) {
var result = url.parse(`http://${website}`)
return fetch(`http://www.${result.hostname}`)
.then(() => true) // async function returns promise
.catch(() => false)
}
const websites = ['digitlead.com',
'google.com',
'georgiancollege.ca',
'youtube.com',
'111.1',
'foobarbaz']
async function filter(array, func) {
const tmp = await Promise.all( // waits for all promises to resolve
array.map(func) // evecutes async function and stores it result in new array then returns array of promises
)
return array.filter((_, i) => tmp[i]) // removes invalid websites
}
const validWebsites = filter(websites, validateWebsites)
validWebsites.then(console.log)
Get the indexes of the non-valid sites
async function filter(array, func) {
const tmp = await Promise.all(array.map(func))
return tmp
.map((x, i) => !x && i) // flip true to false and asign index when x is false
.filter(x => x !== false) // return indexes
}
destoryeris saying you should do something like this:
websites.forEach(async website => {
i++;
if (await validateWebsites(website) === false
) {
wrongWebsites.push(i);
}
});
But that alone is problematic because you have wrap your async functions in a try/catch to handle their errors. So something more like this:
websites.forEach(async website => {
i++;
try {
const validSites = await validateWebsites(website);
if (validSites === false) {
wrongWebsites.push(i);
}
} catch(e) {
// handle e
}
})
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 4 years ago.
when I create my api in nodejs and trying to pushing mongoose return count to new created array, it's not wait for the forEach and execute json.res() and giving null response. when I use
setTimeout() then it's giving proper result.
let newcategories = [];
let service = 0;
const categories = await Category.find({}, '_id name');
categories.forEach(async (category) => {
service = await Service.count({category: category});
newcategories.push({ count:service });
console.log('newcategories is -- ', newcategories);
}); /* while executing this forEach it's not wait and execute res.json..*/
console.log('result --- ',result);
console.log('out newcategories is -- ', newcategories);
res.json({status: 200, data: newcategories});
You need to use map instead of forEach, to collect the awaits and wait for them to complete. Edit: Or you can use for..of which is pretty neat (thanks other ppl)!
const categories = ['a', 'b', 'c'];
function getNextCategory(oldCategory) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(String.fromCharCode(oldCategory.charCodeAt(0)+1));
}, 1000);
});
}
async function blah() {
const categoryPromises = categories.map(getNextCategory);
const nextCategories = await Promise.all(categoryPromises);
console.log(nextCategories);
}
blah();
async function blah2() {
const nextCategories = [];
for (const category of categories) {
nextCategories.push(await getNextCategory(category));
};
console.log(nextCategories);
}
blah2();
So the problem you have is that async marked functions will return a promise per default, but that the Array.prototype.forEach method doesn't care about the result type of your callback function, it is just executing an action.
Inside your async function, it will properly await your responses and fill up your new categories, but the forEach loop on categories will be long gone.
You could either choose to convert your statements into a for .. of loop, or you could use map and then await Promise.all( mapped )
The for..of loop would be like this
for (let category of categories) {
service = await Service.count({category: category});
newcategories.push({ count:service });
console.log('newcategories is -- ', newcategories);
}
the map version would look like this
await Promise.all( categories.map(async (category) => {
service = await Service.count({category: category});
newcategories.push({ count:service });
console.log('newcategories is -- ', newcategories);
}));
The second version simply works because Promise.all will only resolve once all promises have completed, and the map will return a potentially unresolved promise for each category