This question already has answers here:
Using async/await with a forEach loop
(33 answers)
How do I add a delay in a JavaScript loop?
(32 answers)
Closed 15 days ago.
I'm wondering what's the best method to loop over an array and use async/await on the elements as you perform a task with them. Being new to the language, I haven't found a definitive answer or I didn't understand the solution (using Promise.all with and map method ?)
This is the code. renderCountry is NOT an async function, but the function where this condition happens is an async. Is it correct how I've done it? If not, how should I implement it? I just wanna make sure the code is non-blocking as I loop over the array's elements,
Code
if (neighboursArr) {
neighboursArr.forEach(async (el, i) => {
await wait(i * 300);
const neighbourRes = await fetch(
`https://restcountries.com/v3.1/alpha/${el}`
);
const neighbourData = await neighbourRes.json();
renderCountry(neighbourData[0], "neighbour");
});
} else {
renderMessage("It is lonely island :)");
}
Well, disregarding your specific code, while focusing the main general question you posed in the first paragraph, I have devised a way for async iteration:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
const inc = async i => (await delay(500), ++i)
const foo = async () => {
for(let i = 1; i <= 5; i = await inc(i))
console.log(i) // prints 1, 2, 3, 4, 5 with a delay
}
foo()
Above code is from my Github gist
Related
This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed last month.
I need to loop through an array reading each element after some time, the only solution I've seen is to use setTimeOut() in Typescript.
The problem is that I need the function to finish to continue the process.
This is what I currently have:
async function testData() {
let arrT = ['data1','data2','data3']
let countReq = 0
let result = []
for await (let getA of arrT) {
countReq++
setTimeout(async() => {
result.push(
`test - ${getA}`
)
}, countReq * 1000)
}
console.log(result)
return result;
}
testData()
Now it returns the empty array because the process has not finished, I need to wait and return the array with the complete information
I don't know if using setTimeout() in this case is the best option, I don't know if there is a better way to do this.
You can use a Promise to resolve the task with await. You don't need to provide await after your for key.
function sleepTimeout(interval) {
return new Promise((resolve) => {
setTimeout(resolve, interval);
});
};
async function testData() {
let arrT = ['data1','data2','data3']
let countReq = 0
let result = []
for (let getA of arrT) {
countReq++
await sleepTimeout(1000);
result.push(`test - ${getA}`);
}
console.log(result)
return result;
}
testData()
This question already has answers here:
Is the Promise constructor callback executed asynchronously?
(2 answers)
When is the body of a Promise executed?
(4 answers)
Closed 2 months ago.
I read about event loop and callback queue and micro/macro task queue but maybe i am getting something the wrong way,
Can someone visualize it for me?, it will help me understand so mush concepts.
console.log(1)
const wait2sec = new Promise((resolve, reject) => {
console.log(2)
setTimeout(() => {
console.log(3)
resolve(true)
}, 2000)
console.log(4)
})
const X =async () => {
await wait2sec
console.log('ok')
}
X()
console.log(5)
I think it should log: 1,5,2,4,3,ok
but it logs: 1,2,4,5,3,ok
From MDN (emphasis mine):
The body of an async function can be thought of as being split by zero or more await expressions. Top-level code, up to and including the first await expression (if there is one), is run synchronously. In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously.
This is what happens here, the code of X and of wait2sec is run synchronously, hence before the console.log(5).
You expected concurrent behavior between Promise object and console.log and don't saw it?
You created and immediately called the Promise object (so you can't see 5 before 2,4, at least declare wait2sec as a function).
Promise object without await or timeout runs as a synchronous function (so you see 2,4 and after 3)
if you want to see concurrency between two processes see such code:
function RandomWork(prefix) {
return new Promise( resolve => {
let time = Math.round(Math.random() * 10);
setTimeout(function() {
console.log(prefix, 'made some work for:', time, 'ms')
resolve('foo');
}, time);
});
}
const Process = async(name) => {
const prefix = "process_"+name;
console.log(prefix, 'start');
for(let i=1; i<10; i++) {
await RandomWork(prefix);
}
console.log(prefix, 'stop');
}
Process(1);
Process(2);
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 11 months ago.
I am learning node.js and I am confused about how async works. My understanding is that Array.foreach is blocking but then the below piece of code does not work as expected.
testfunc.ts:
export const testfunc = async (numArray: number[]) => {
let count = 0;
numArray.forEach(async (elem) => {
if (elem % 2 === 0) {
await new Promise((r) => setTimeout(r, 2000));
console.log(elem);
count = count + 1;
}
});
console.log(`printed ${count}`);
};
sendmessage.ts:
import { testfunc } from "./testfunc";
const callTestFunc = async () => {
await testfunc([1, 2, 3, 4, 5, 6]);
};
callTestFunc();
I was expecting an out put of:
2
4
6
printed 3
But, I am getting
printed 0
2
4
6
Can someone explain this to me.
With forEach, you do your stuff in a callback function and this callback is taking some times. So it's asynchronous.
Consequently, forEach can't be used for synchronous tasks.
This is also the same for a for loop which is calling a function which is asynchronous. For example, if you execute a SQL INSERT query inside a for loop, you can not be sure that INSERT are executed in the loop order.
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 2 years ago.
I need to execute a chain of promises with a timer for external requests. As someone suggested in the similar thread I used async/await and rewrite it a bit:
class myClass{
constructor(symbols){
this.downloadCsv(symbols)
}
// async/await method executing requests with timer
static async downloadCsv(symbols){
const result = [];
symbols.forEach(symbol => {
await timer(1000)
result.push(await this.downloadOneCsv(symbol['symbol']) )
}
return result
}
downloadOneCsv(symbol){
//request goes here
}
}
While executing a code above I keep receving an error message suggesting that async/await is not valid:
await timer(1000)
^^^^^
SyntaxError: await is only valid in async function
How can I fix it?
First of all you are not able to use timer in a forEach because of its nature and calling callback functions as much as amount of your array elements in stack. That timer method after adding async keyword to the callback function will get fired just once and other times will get skipped. I suggest use for of loop on that array.
static async downloadCsv(symbols){
const result = [];
for (const symbol of symbols) {
await timer(1000)
result.push(await this.downloadOneCsv(symbol['symbol']) )
}
return result;
}
Old One:
const timer = ms => new Promise(resolve => setTimeout(resolve, ms));
const arr = [1, 2, 5, 6, 7];
arr.forEach(async item => {
console.log('Before Timer');
await timer(1000);
console.log(item);
});
Fixed One
const timer = ms => new Promise(resolve => setTimeout(resolve, ms));
const arr = [1, 2, 5, 6, 7];
(async () => {
for (const item of arr) {
await timer(1000);
console.log(item);
}
})();
This question already has answers here:
async/await inside arrow functions (Array#map/filter)
(6 answers)
Closed 3 years ago.
I am trying to query, that would get all of the earning and withdrawal amount and sum, but somehow after the forEach loop is done and exits, all of the updated values I placed in a variable return to their original values.
var withdrawbalance = 0;
var totalearning = 0;
userplans.forEach((up) => {
DepositEarning.findOne({deposit: up.deposit._id})
.then(depositearning => {
withdrawbalance += parseInt(depositearning.WithdrawableBalance, 10);
});
Earning.findOne({deposit: up.deposit._id})
.then(earnings => {
totalearning += parseInt(earnings.Earning, 10);
});
})
console.log(withdrawbalance);
console.log(totalearning);
You are running asynchronous code inside the forEach and forEach will not wait until the asynchronous code finish, to do so, you must wrap your async code with a waiting primitive, like this
await Promise.all(userplans.map(async (up) => {
await DepositEarning.findOne({deposit: up.deposit._id})
.then(depositearning => {
withdrawbalance += parseInt(depositearning.WithdrawableBalance, 10);
});
await Earning.findOne({deposit: up.deposit._id})
.then(earnings => {
totalearning += parseInt(earnings.Earning, 10);
});
}))
Here is a functional approach to it that removes the mutation side-effect inside the loop. You need an engine that supports Object Rest spread for this:
async function calculate(userplans) {
const add = (acc, n) => acc + n;
const flatten = (acc, e) => ({
depositEarnings: [...acc.depositEarnings, e.depositEarnings],
earnings: [...acc.earnings, e.earnings]
});
const {depositEarnings, earnings} = (await Promise.all(userplans
.map(async (up) => ({
depositEarnings: await DepositEarning.findOne({deposit: up.deposit._id}),
earnings: await Earning.findOne({deposit: up.deposit._id})
}))))
.reduce(flatten, {depositEarnings: [], earnings: []});
const withdrawalBalance = depositEarnings
.map(d => parseInt(d.WithdrawableBalance, 10))
.reduce(add, 0);
const totalEarning = earnings
.map(e => parseInt(e.Earning, 10))
.reduce(add, 0);
console.log(withdrawalBalance);
console.log(totalEarning);
}
Just to add my two cents,
As other answers have stated, your foreach loop is using asynchronous code. It is making use of some API which is not a part of Js.
The call is pushed to call stack and then it is removed from the call stack. Because the work will be handled by the API now. the call to
console.log(withdrawbalance);
console.log(totalearning);
is added to the call stack and what is the value right now? 0 0.
The API, when it is done with the work, will add the function in the task queue and from that, event loop will check if the call stack is empty. Since it is empty right now, will add the function in the call stack.
And it is then run. And then you will be having the real values of withdrawbalance and
totalearning values.
I think the reason could be that DepositEarning.findOne() and Earning.findOne() are both async functions. This means that they don't return immediately with a value, hence your variables are still the same after the loop.
forEach is asynchronous. So the console.log runs before your mutation loop. You can make it synchronous by using map, but even then - you have asynchronous calls inside it. You will need to flatten it further - probably by doing Promise.all on a map, then reducing the output of that.