On top of @ef-dknittl-frank's answer. I would like to share my point of view.
I have difficulties understanding this question because I want to understand how exactly JS working behind the scene.
TL;DR, if you rewrite the run1 and run2 in .then, it would be more clear.
run1 can be rewritten as below with .then():
(async function run1() {
await Promise.all(
seconds.map(sec => {
return Promise.resolve(output1).then(temp => {
return sleep(sec).then(res => {
output1 = temp + res;
});
});
})
);
console.log({ output1 });
})();
run2 can be rewritten as below with .then():
(async function run2() {
await Promise.all(
seconds.map(sec => {
return sleep(sec).then(res => {
output2 = output2 + res;
});
})
);
console.log({ output2 });
})();
I post this answer just in case you have difficulties like me to understand this question.
The key is what execution context (local scope, scope chain and variables in closures from outer scopes) is when the codes are executing.
For the run1,
We rewrite the below codes:
output1 = output1 + (await sleep(sec));
to
a = b + c
The sequence of execution is:
1. store the value to b
2. store the value to c
3. sum b and c
4. assign the sum (b+c) to a
we analyze the sequence one by one.
1. store the value to b
When we execute line 1, the value of output1 is assigned to b. At this point, output1 is 0 from the scope chain(outer scope) and only sync codes are running. Therefore, all the value of b is 0 inside the map() loop
2. store the value to c
When we execute line 2, the value of await sleep(sec) is assigned to c. For instance, let say 1000(From await sleep) is assigned to c. At this point, you can already see the log start and fin because all sync code(outer scope) is finished before executing line2.
3. sum b and c
When we execute line 3, b is 0 and c is 1000(from the previous example), so the sum is 1000.
4. assign the sum (b+c) to a
When we execute line 4, the a is output1 and output1 is the variable identifier, used to provide the location in which to store a value and in this case, it stores the sum. To be more clear, the line4 is executing after all sync code(outer scope) is finished, so we access the output1 by Closures from outer scopes.
Why output1 always show 3000? It is because the line4 above is executed asynchronously and the last code executed is 3000ms. a(output1) is re-assigned by a new value from the asynchronous codes.
run1 can be rewritten as below with .then():
(async function run1() {
await Promise.all(
seconds.map(sec => {
return Promise.resolve(output1).then(temp => {
return sleep(sec).then(res => {
output1 = temp + res;
});
});
})
);
console.log({ output1 });
})();
For the run2,
The codes below
output2 = output2 + res;
Both output2 are referenced to the same thing and they are accessed also by Closures from outer scopes. To be more clear, this line is executing after all sync code(outer scope) is finished, so we access the output2 by Closures from outer scopes.
run2 can be rewritten as below:
(async function run2() {
await Promise.all(
seconds.map(sec => {
return sleep(sec).then(res => {
output2 = output2 + res;
});
})
);
console.log({ output2 });
})();
Extra point:
if we change the codes of run1 below
from:
output1 = output1 + (await sleep(sec));
to:
output1 = (await sleep(sec)) + output1;
It will output the same result as run2.
Let's rewrite it to a = b + c like the previous example.
The sequence of execution is:
1. await sleep() and store the value to b (it is running asynchronously)
2. store the value to c (c is equal to `output1` which is accessed by Closure)
3. sum b and c
4. assign the sum (b+c) to a (a is equal to `output1` which is accessed by Closure)
If we rewrite the above changes of run1 to .then():
(async function run1() {
await Promise.all(
seconds.map(sec => {
return sleep(sec).then(res => {
output1 = output1 + res;
});
})
);
console.log({ output1 });
})();
Did you notice? it is the same as run2().