I am reading "Eloquent JavaScript" and at the end of Asynchronous chapter there is a section about execution gap in asynchronous operations and it shows an example. The example code below from the book is not runable as it uses some made up scenario.
function anyStorage(nest, source, name) {
if (source == nest.name) return storage(nest, name); else return routeRequest(nest, source, "storage", name);
}
async function chicks(nest, year) {
let list = "";
await Promise.all(network(nest).map(async name => {
list = `${name}: ${
await anyStorage(nest, name, `chicks in ${year}`)
}\n`; }));
return list;
}
It says the problem with this code, as I understand, is each asynchronous call e.g. anyStorage
will actually concatenate an empty list
and can't see the mutation from other asynchronous calls.
I tried to replicate this problem as below:
function delay(n) {
return Promise.resolve(setTimeout(()=>{}, n*1000));
}
async function asyncSum(a) {
let sum = 0;
await Promise.all(a.map(async i => sum = await delay(i)));
return sum;
}
asyncSum([1,2,3,4]).then(value => console.log(value));
But this works as expected. It prints the sum correctly. So did I misunderstand the book or there is something wrong with my example?
CodePudding user response:
The problem in the original code is that here:
list = `${name}: ${ await ...
The list =
is like doing list = list
- but the second list:
list = list
^^^^
refers to the list
at that moment - before any of the asynchronous calls have completed. Eventually one of them will complete and assigns to list
. But once the next one resolves, because the list
it has refers to the original one, not the newly updated one, the prior results will be lost.
Here's a demo:
const getMultipliedNum = num => Promise.resolve(num * 3);
(async() => {
let list = "";
await Promise.all([1, 2, 3].map(async num => {
list = `${num}: ${ await getMultipliedNum(num)}\n`;
}));
console.log(list);
})();
Your example has the same issue, though the delay
function is broken - the result is 4, which is only the result of the final setTimeout
call (because setTimeout
returns the timeout ID). Fixing it to demonstrate the problem:
function delay(n) {
return Promise.resolve(n);
}
async function asyncSum(a) {
let sum = 0;
await Promise.all(a.map(async i => sum = await delay(i)));
return sum;
}
asyncSum([1,2,3,4]).then(value => console.log(value));
The result is 4, rather than 10 - this demonstrates that sum = await somethingElse
inside a parallel loop doesn't work.
Another way of looking at it:
sum = await somethingElse
is like doing
const currentValueOfSum = sum;
sum = currentValueOfSum await somethingElse;