Await nested forEach behaviour - javascript

Why does this snippet
const firstArray = ['toto', 'toto'];
const secondArray = ['titi', 'titi'];
firstArray.forEach(async (toto, i) =>
{
await secondArray.forEach(async titi =>
{
// async code
console.log(titi, i);
});
// async code
console.log(toto, i);
});
produce the following output:
Removing the await keyword produces the expected output
My guess is it resides in the await keyword's behaviour, as, without which, the produced output is the expected output.
EDIT: this is a purely trivial question. I want to understand why using await before a forEach provides this behaviour. There is no 'concrete code' behind this.
EDIT2: edited the snippet as the comments and answer reflected misunderstanding of my question

forEach works synchronously meaning it doesn't do anything with the return of each iteration (in this case, the promise) and it ignores it.
If you use instead:
for(let item of arrayItems)
or a normal for loop you should see the expected result working asynchronously.

I guess you want to await all promises before continuing. If should be the case, a combination of Array.prototype.reduce(...), Array.prototype.map(...) and Promise.all(...) will better suit:
const allPromises = firstArray.reduce((accumulatedPromises, toto) => {
const secondArrayPromises = secondArray.map((titi) => {
return // some promise;
});
const firstArrayPromise = // some promise;
return accumulatedPromises.concat(secondArrayPromises, firstArrayPromise);
}, []);
const finalPromise = Promise.all(allPromises);
Then, either await finalPromise if you're within an async function, or use .then(...).

Your outer forEach loop spits out two functions (note the async type you have; async functions automatically return a Promise) directly on the stack that then runs in sequence before finally outputting from the firstArray. So after both the functions are executed your output for the outer loop is printed. This is how the forEach loop works with async functions.
To use async/await with loops, you would want to have:
async function boot() {
for(let item of list) {
await getData(); // Here getData returns a promise
}
}
boot();
Another way could be to use bluebird for Promise:
let exec = [];
await Promise.map(list => getData());
// Here Promise.map iterates over list
// and returns a list of promises which are
// then resolved using await

The key to understanding is that await is asynchronous. So everything after will be executed no earlier than the current execution thread returns. Thus, the part
// async code
console.log(toto);
is put to a queue rather than being executed immediately.
Async/await essentially wraps your code like this:
const firstArray = ['toto', 'toto'];
const secondArray = ['titi', 'titi'];
firstArray.forEach((toto,i) => new Promise((resolve)=>
{
resolve((new Promise((resolve)=>resolve(secondArray.forEach(titi => new Promise((resolve)=>
{
resolve(console.log(titi, i));
}))))).then(()=>console.log(toto,i))
);}));
Now consider this code as well (setTimeout is pretty much a promise that is resolved after a given amount of time):
const firstArray = ['toto', 'toto'];
const secondArray = ['titi', 'titi'];
firstArray.forEach((toto, i) =>
{
secondArray.forEach(titi =>
{
console.log(titi, i);
});
setTimeout(()=>console.log(toto, i));
});

Related

JavaScript is not waiting for Async Await [duplicate]

There is quite some topics posted about how async/await behaves in javascript map function, but still, detail explanation in bellow two examples would be nice:
const resultsPromises = myArray.map(async number => {
return await getResult(number);
});
const resultsPromises = myArray.map(number => {
return getResult(number);
});
edited: this if of course a fictional case, so just opened for debate, why,how and when should map function wait for await keyword. solutions how to modify this example, calling Promise.all() is kind of not the aim of this question.
getResult is an async function
The other answers have pretty well covered the details of how your examples behave, but I wanted to try to state it more succinctly.
const resultsPromises = myArray.map(async number => {
return await getResult(number);
});
const resultsPromises = myArray.map(number => {
return getResult(number);
});
Array.prototype.map synchronously loops through an array and transforms each element to the return value of its callback.
Both examples return a Promise.
async functions always return a Promise.
getResult returns a Promise.
Therefore, if there are no errors you can think of them both in pseudocode as:
const resultsPromises = myArray.map(/* map each element to a Promise */);
As zero298 stated and alnitak demonstrated, this very quickly (synchronously) starts off each promise in order; however, since they're run in parallel each promise will resolve/reject as they see fit and will likely not settle (fulfill or reject) in order.
Either run the promises in parallel and collect the results with Promise.all or run them sequentially using a for * loop or Array.prototype.reduce.
Alternatively, you could use a third-party module for chainable asynchronous JavaScript methods I maintain to clean things up and--perhaps--make the code match your intuition of how an async map operation might work:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const getResult = async n => {
await delay(Math.random() * 1000);
console.log(n);
return n;
};
(async () => {
console.log('parallel:');
await AsyncAF([1, 2, 3]).map(getResult).then(console.log);
console.log('sequential:');
await AsyncAF([1, 2, 3]).series.map(getResult).then(console.log)
})();
<script src="https://unpkg.com/async-af#7.0.12/index.js"></script>
async/await is usefull when you want to flatten your code by removing the .then() callbacks or if you want to implicitly return a Promise:
const delay = n => new Promise(res => setTimeout(res, n));
async function test1() {
await delay(200);
// do something usefull here
console.log('hello 1');
}
async function test2() {
return 'hello 2'; // this returned value will be wrapped in a Promise
}
test1();
test2().then(console.log);
However, in your case, you are not using await to replace a .then(), nor are you using it to return an implicit Promise since your function already returns a Promise. So they are not necessary.
Parallel execution of all the Promises
If you want to run all Promises in parallel, I would suggest to simply return the result of getResult with map() and generate an array of Promises. The Promises will be started sequentially but will eventually run in parallel.
const resultsPromises = indicators.map(getResult);
Then you can await all promises and get the resolved results using Promise.all():
const data = [1, 2, 3];
const getResult = x => new Promise(res => {
return setTimeout(() => {
console.log(x);
res(x);
}, Math.random() * 1000)
});
Promise.all(data.map(getResult)).then(console.log);
Sequential execution of the Promises
However, if you want to run each Promise sequentially and wait for the previous Promise to resolve before running the next one, then you can use reduce() and async/await like this:
const data = [1, 2, 3];
const getResult = x => new Promise(res => {
return setTimeout(() => {
console.log(x);
res(x);
}, Math.random() * 1000)
});
data.reduce(async (previous, x) => {
const result = await previous;
return [...result, await getResult(x)];
}, Promise.resolve([])).then(console.log);
Array.prototype.map() is a function that transforms Arrays. It maps one Array to another Array. The most important part of its function signature is the callback. The callback is called on each item in the Array and what that callback returns is what is put into the new Array returned by map.
It does not do anything special with what gets returned. It does not call .then() on the items, it does not await anything. It synchronously transforms data.
That means that if the callback returns a Promise (which all async functions do), all the promises will be "hot" and running in parallel.
In your example, if getResult() returns a Promise or is itself async, there isn't really a difference between your implementations. resultsPromises will be populated by Promises that may or may not be resolved yet.
If you want to wait for everything to finish before moving on, you need to use Promise.all().
Additionally, if you only want 1 getResults() to be running at a time, use a regular for loop and await within the loop.
If the intent of the first code snippet was to have a .map call that waits for all of the Promises to be resolved before returning (and to have those callbacks run sequentially) I'm afraid it doesn't work like that. The .map function doesn't know how to do that with async functions.
This can be demonstrated with the following code:
const array = [ 1, 2, 3, 4, 5 ];
function getResult(n)
{
console.log('starting ' + n);
return new Promise(resolve => {
setTimeout(() => {
console.log('finished ' + n);
resolve(n);
}, 1000 * (Math.random(5) + 1));
});
}
let promises = array.map(async (n) => {
return await getResult(n);
});
console.log('map finished');
Promise.all(promises).then(console.log);
Where you'll see that the .map call finishes immediately before any of the asynchronous operations are completed.
If getResult always returns a promise and never throws an error then both will behave the same.
Some promise returning functions can throw errors before the promise is returned, in this case wrapping the call to getResult in an async function will turn that thrown error into a rejected promise, which can be useful.
As has been stated in many comments, you never need return await - it is equivalent to adding .then(result=>result) on the end of a promise chain - it is (mostly) harmless but unessesary. Just use return.

Why for...of waits for Promise to resolve and .forEach() doesn't?

I am having hard time in understanding how for...of differ from .forEach() in case of handling Promises.
Using this snippet:
const allSettled = async arr => {
const data = []
for (const promise of arr) {
try {
const result = await promise;
data.push(result);
} catch (e) {
data.push(e)
}
}
return data
}
I go over each promise in array, wait for it to settle and push the result to data. It executes sequentially.
If I have this snippet:
const badAllSettled = arr => {
const data = []
arr.forEach(async promise => {
try {
const result = await promise;
data.push(result);
} catch (e) {
data.push(e)
}
})
return data
}
I get empty array (because forEach does not wait for Promise to settle).
AFAIK for...of works with iterables, hence it might suspend and wait await to return. But how this flow works step-by-step I don't understand.
Thanks!
.forEach() is implemented like this:
Array.prototype.forEach = function forEach(fn) {
for (let i = 0; i < this.length; i++)
fn(this[i], i, this);
}
As you can see, when .forEach() is called, it just calls the callback synchronously several times. However, if the callback is an async function, it doesn't get awaited.
By contrast, when an await keyword is found inside an async function, all the code execution withing that function is paused until the promise settles, which means that any control flow (including for ... of loops) is also paused.
The following code behaves like .forEach() because it calls the async function which awaits the promise without awaiting the promise the function returns:
const allSettled = async arr => {
const data = []
for (const promise of arr) {
(async () => {
try {
const result = await promise;
data.push(result);
} catch (e) {
data.push(e)
}
)();
}
return data
}
for...of doesn't wait. The await keyword does the waiting. You have one of those here: const result = await promise;
The internals of forEach don't await the promise returned by the function you pass to forEach(here). (The use of await inside the function that generates that promise is irrelevant).
for (const item of array) {
// do something
}
does "something" for each element of the array. If you await something inside, it will await it until proceeding to the rest of the code.
array.forEach(callback)
synchronously calls the callback for each element of the array. If your callback happens to be asynchronous, it will call it and then immediately call the next callback. .forEach doesn't wait for each callback to resolve to something before calling the next callback.
Note that if you want your promises to run in parallel, perhaps for perf gains, you might wanna use Promise.all
// runs every fetch sequentially
// if each of them takes 1s, this will take 3s to complete
const results = []
for (const item of [1,2,3]) {
results.push(await fetchSomething(item))
}
// runs every fetch in parrallel
// if each of them takes 1s, this will take 1s to complete
const promises = [1,2,3].map(item => fetchSomething(item))
const results = await Promise.all(promises)

Execute several promises in parallel, but only retrieve result from one of them

I currently have a program where I want to call several REST API:s in parallel, but I'm only interested in the result from one of them.
Currently I've solved it like this:
private async loadData () {
const all = [this.loadFirstData(), this.loadSecondData(), this.loadThirdData()];
const combine = Promise.all(all);
await combine;
// One of the promises just puts it's return value in this global variable, so that I can access it after it is done.
if (this.valueFromThirdAPI) {
// Do something with value
}
}
So what I do is just put the put the result from the promise I want a result from in a global variable that I can access after all of the promises have returned. This works, but I'm sure there must be a much better way of doing this.
Promises.all returns a array of the resolved values, but how do I distinguish them from each other if I'm only interested in the value from one of them? The other two doesn't need to return anything.
Thanks in advance!
First, it makes sense to await Promise.all,
You can access the result using the returned array's index.
Another option is using Array destructoring.
Below is an example..
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve('this one');
const promise4 = Promise.resolve(4);
async function test() {
//note the double `,` to ignore the first 2 promises.
const [,,three] = await Promise.all([promise1, promise2, promise3, promise4]);
console.log(three);
}
test();
You can iterate on the return values when they are all resolved.
Promise.all().then(values)
Take a look at https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Wait on the third and then await on all. So if the third finishes before first and second, you get to process it earlier.
private async loadData () {
const all = [this.loadFirstData(), this.loadSecondData(), this.loadThirdData()];
const third = await all[2];
if (third) {
// Do something with value
}
await Promise.all(all);
}
Promise.all returns an array with the same order of the given promises:
private async loadData () {
const all = [this.loadFirstData(), this.loadSecondData(), this.loadThirdData()];
const combine = Promise.all(all);
const values = await combine;
// One of the promises just puts it's return value in this global variable, so that I can access it after it is done.
if (values[2]) {
// Do something with value
}
}

`then` not a function error when invoking function returning Promise.all in await expression

I have a function with a loop that creates an array of promises that I pass to Promise.all, which I'm then attempting to return to an await expression in the main function.
When I take the returned object and attempt to invoke then on it (which I would have thought was a promise), it doesn't appear to be a promise anymore. I'm receiving the error
TypeError: respPromise.then is not a function
Here's a simplified version of what I'm doing
const run = async () => {
const fs = require('fs').promises
const createThreeFiles = () => {
const promiseArray = []
for (let i = 0; i < 3; i++) {
promiseArray.push(fs.writeFile(`file${i}.txt`, `This is the text for ${i}`))
}
return Promise.all(promiseArray)
}
const respPromise = await createThreeFiles()
respPromise
.then(fileInfos => {
console.dir(fileInfos)
})
.catch(error => {
console.error(error)
})
}
run()
If I change the createThreeFiles function and invoke then immediately off of Promise.all(promiseArray) there is no problem at all invoking then.
const createThreeFiles = () => {
const promiseArray = []
for (let i = 0; i < 3; i++) {
promiseArray.push(fs.writeFile(`file${i}.txt`, `This is the text for ${i}`))
}
return Promise.all(promiseArray)
.then(values => {...})...
// the above works without error.
}
So I think I must have something not quite right with the use of async/await, but for the life of me I'm not sure what. I've read the docs and looked for things to fix (and also I'm new to async/await) but so far, no luck. I've checked docs on Promise.all, await, and async as well as read a couple of articles. I've checked things like the following:
Make sure I'm invoking await in a function declared as async. Check.
Declare the createThreeFiles function as async. No change.
Do the immediate above and move Promise.all into an await expression. No change, and the linter complained that I'd already used await when I invoked createThreeFiles.
At this point, I'm just grasping at straws. I can move this logic back out of the function, but was trying to refactor and optimise my code, so tell me, how do I correctly return the promise of Promise.all when returned from a function invoked in an await expression?
If you await a promise inside an async function it reslves directly to the value and not to a promise.
So this
const respPromise = await createThreeFiles()
respPromise
.then(fileInfos => {
console.dir(fileInfos)
})
Should actually be this
const fileInfos = await createThreeFiles();
console.log(fileInfos);

map() function with async/await

There is quite some topics posted about how async/await behaves in javascript map function, but still, detail explanation in bellow two examples would be nice:
const resultsPromises = myArray.map(async number => {
return await getResult(number);
});
const resultsPromises = myArray.map(number => {
return getResult(number);
});
edited: this if of course a fictional case, so just opened for debate, why,how and when should map function wait for await keyword. solutions how to modify this example, calling Promise.all() is kind of not the aim of this question.
getResult is an async function
The other answers have pretty well covered the details of how your examples behave, but I wanted to try to state it more succinctly.
const resultsPromises = myArray.map(async number => {
return await getResult(number);
});
const resultsPromises = myArray.map(number => {
return getResult(number);
});
Array.prototype.map synchronously loops through an array and transforms each element to the return value of its callback.
Both examples return a Promise.
async functions always return a Promise.
getResult returns a Promise.
Therefore, if there are no errors you can think of them both in pseudocode as:
const resultsPromises = myArray.map(/* map each element to a Promise */);
As zero298 stated and alnitak demonstrated, this very quickly (synchronously) starts off each promise in order; however, since they're run in parallel each promise will resolve/reject as they see fit and will likely not settle (fulfill or reject) in order.
Either run the promises in parallel and collect the results with Promise.all or run them sequentially using a for * loop or Array.prototype.reduce.
Alternatively, you could use a third-party module for chainable asynchronous JavaScript methods I maintain to clean things up and--perhaps--make the code match your intuition of how an async map operation might work:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const getResult = async n => {
await delay(Math.random() * 1000);
console.log(n);
return n;
};
(async () => {
console.log('parallel:');
await AsyncAF([1, 2, 3]).map(getResult).then(console.log);
console.log('sequential:');
await AsyncAF([1, 2, 3]).series.map(getResult).then(console.log)
})();
<script src="https://unpkg.com/async-af#7.0.12/index.js"></script>
async/await is usefull when you want to flatten your code by removing the .then() callbacks or if you want to implicitly return a Promise:
const delay = n => new Promise(res => setTimeout(res, n));
async function test1() {
await delay(200);
// do something usefull here
console.log('hello 1');
}
async function test2() {
return 'hello 2'; // this returned value will be wrapped in a Promise
}
test1();
test2().then(console.log);
However, in your case, you are not using await to replace a .then(), nor are you using it to return an implicit Promise since your function already returns a Promise. So they are not necessary.
Parallel execution of all the Promises
If you want to run all Promises in parallel, I would suggest to simply return the result of getResult with map() and generate an array of Promises. The Promises will be started sequentially but will eventually run in parallel.
const resultsPromises = indicators.map(getResult);
Then you can await all promises and get the resolved results using Promise.all():
const data = [1, 2, 3];
const getResult = x => new Promise(res => {
return setTimeout(() => {
console.log(x);
res(x);
}, Math.random() * 1000)
});
Promise.all(data.map(getResult)).then(console.log);
Sequential execution of the Promises
However, if you want to run each Promise sequentially and wait for the previous Promise to resolve before running the next one, then you can use reduce() and async/await like this:
const data = [1, 2, 3];
const getResult = x => new Promise(res => {
return setTimeout(() => {
console.log(x);
res(x);
}, Math.random() * 1000)
});
data.reduce(async (previous, x) => {
const result = await previous;
return [...result, await getResult(x)];
}, Promise.resolve([])).then(console.log);
Array.prototype.map() is a function that transforms Arrays. It maps one Array to another Array. The most important part of its function signature is the callback. The callback is called on each item in the Array and what that callback returns is what is put into the new Array returned by map.
It does not do anything special with what gets returned. It does not call .then() on the items, it does not await anything. It synchronously transforms data.
That means that if the callback returns a Promise (which all async functions do), all the promises will be "hot" and running in parallel.
In your example, if getResult() returns a Promise or is itself async, there isn't really a difference between your implementations. resultsPromises will be populated by Promises that may or may not be resolved yet.
If you want to wait for everything to finish before moving on, you need to use Promise.all().
Additionally, if you only want 1 getResults() to be running at a time, use a regular for loop and await within the loop.
If the intent of the first code snippet was to have a .map call that waits for all of the Promises to be resolved before returning (and to have those callbacks run sequentially) I'm afraid it doesn't work like that. The .map function doesn't know how to do that with async functions.
This can be demonstrated with the following code:
const array = [ 1, 2, 3, 4, 5 ];
function getResult(n)
{
console.log('starting ' + n);
return new Promise(resolve => {
setTimeout(() => {
console.log('finished ' + n);
resolve(n);
}, 1000 * (Math.random(5) + 1));
});
}
let promises = array.map(async (n) => {
return await getResult(n);
});
console.log('map finished');
Promise.all(promises).then(console.log);
Where you'll see that the .map call finishes immediately before any of the asynchronous operations are completed.
If getResult always returns a promise and never throws an error then both will behave the same.
Some promise returning functions can throw errors before the promise is returned, in this case wrapping the call to getResult in an async function will turn that thrown error into a rejected promise, which can be useful.
As has been stated in many comments, you never need return await - it is equivalent to adding .then(result=>result) on the end of a promise chain - it is (mostly) harmless but unessesary. Just use return.

Categories

Resources