Why does this loop repeat each iteration twice? - javascript

The function below prints each number twice. Could someone explain how it works? I tried debugging but all I can see is that the value of i only increases on every second iteration.
async function run(then) {
for (let i = 1; i <= 10; i++) {
console.log(i);
then = await { then };
}
}
run(run);
Concretely speaking, there are two things I don't understand.
Why does i not increase on every single iteration?
What does then = await { then }; exactly do? My first guess was that it would wait for a nested async call to run to finish before moving on to the next iteration, but this does not seem to be the case.

We can make this a bit clearer with minor re-write to include logging:
async function run(callback) {
let then = callback;
for (let i = 1; i <= 10; i++) {
console.log(callback === run ? "A" : "B", i);
then = await { then };
}
}
run(run);
.as-console-wrapper { max-height: 100% !important; }
This shows there are actually two loops started. For simplicity just called A and B. They log and await which means that their logs interleave and lead to A 1, B 1, A 2, B 2, etc.
This happens because of the first statement: run(run). Which passes the same function as a callback to itself. This does not call the callback but it is the first step to unravelling this.
The next step to understanding what is happening is await. You can await any value and in most cases if it's not a promise, it doesn't matter. If you have await 42; it just pretends the value was Promise.resolve(42) and continues the operation immediately with the next tick. That is true for most non-promises. The only exception is thenables - objects which have a .then() method.
When a thenable is awaited, its then() method is called:
const thenable = {
then() {
console.log("called");
}
};
(async () => {
await thenable;
})()
Which then explains the await { then } statement. This uses the shorthand for { then: then } where then is the callback passed to run. Thus it creates a thenable object which, when awaited, will execute the callback.
This means that the first time run() is executed and on the first iteration of the loop A the code is effectively await { then: run } which will execute run again which then starts loop B.
The value of then is overridden each time, hence why it only ever runs two loops in parallel, rather than more.
There is more to thenables that is relevant to fully grasp this code. I showed a simple one before which just shows that awaiting it calls the method. However, in reality await thenable will call .then() with two parameters - functions that can be called for success and failure. In the same way that the Promise constructor does it.
const badThenable = {
then() {
console.log("bad called");
}
};
(async () => {
await badThenable;
console.log("never reached");
})();
const goodThenable = {
then(resolve, reject) { //two callbacks
console.log("good called");
resolve(); //at least one needs to be called
}
};
(async () => {
await goodThenable;
console.log("correctly reached");
})();
This is relevant because run() expects a callback and when the await { then: run } executes it calls run(builtInResolveFunction) which then gets passed to the next await { then: builtInResolveFunction } which in turn resolves causes the a await to resolve.
With all this aside, the interleaved logging is just a factor of how tasks resolve:
(async () => {
for (let i = 1; i <= 10; i++){
console.log("A", i);
await Promise.resolve("just to force a minimal wait");
}
})();
(async () => {
for (let i = 1; i <= 10; i++) {
console.log("B", i);
await Promise.resolve("just to force a minimal wait");
}
})();
If there are two async functions running and there is nothing to really wait for:
One would run until it reaches an await and will then be suspended.
The other would run until it reaches an await and will then be suspended.
Repeat 1. and 2. until there are no more awaits.

Related

How to properly use async/await within function? [duplicate]

Given the code samples below, is there any difference in behavior, and, if so, what are those differences?
return await promise
async function delay1Second() {
return (await delay(1000));
}
return promise
async function delay1Second() {
return delay(1000);
}
As I understand it, the first would have error-handling within the async function, and errors would bubble out of the async function's Promise. However, the second would require one less tick. Is this correct?
This snippet is just a common function to return a Promise for reference.
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
Most of the time, there is no observable difference between return and return await. Both versions of delay1Second have the exact same observable behavior (but depending on the implementation, the return await version might use slightly more memory because an intermediate Promise object might be created).
However, as #PitaJ pointed out, there is one case where there is a difference: if the return or return await is nested in a try-catch block. Consider this example
async function rejectionWithReturnAwait () {
try {
return await Promise.reject(new Error())
} catch (e) {
return 'Saved!'
}
}
async function rejectionWithReturn () {
try {
return Promise.reject(new Error())
} catch (e) {
return 'Saved!'
}
}
In the first version, the async function awaits the rejected promise before returning its result, which causes the rejection to be turned into an exception and the catch clause to be reached; the function will thus return a promise resolving to the string "Saved!".
The second version of the function, however, does return the rejected promise directly without awaiting it within the async function, which means that the catch case is not called and the caller gets the rejection instead.
As other answers mentioned, there is likely a slight performance benefit when letting the promise bubble up by returning it directly — simply because you don’t have to await the result first and then wrap it with another promise again. However, no one has talked about tail call optimization yet.
Tail call optimization, or “proper tail calls”, is a technique that the interpreter uses to optimize the call stack. Currently, not many runtimes support it yet — even though it’s technically part of the ES6 Standard — but it’s possible support might be added in the future, so you can prepare for that by writing good code in the present.
In a nutshell, TCO (or PTC) optimizes the call stack by not opening a new frame for a function that is directly returned by another function. Instead, it reuses the same frame.
async function delay1Second() {
return delay(1000);
}
Since delay() is directly returned by delay1Second(), runtimes supporting PTC will first open a frame for delay1Second() (the outer function), but then instead of opening another frame for delay() (the inner function), it will just reuse the same frame that was opened for the outer function. This optimizes the stack because it can prevent a stack overflow (hehe) with very large recursive functions, e.g., fibonacci(5e+25). Essentially it becomes a loop, which is much faster.
PTC is only enabled when the inner function is directly returned. It’s not used when the result of the function is altered before it is returned, for example, if you had return (delay(1000) || null), or return await delay(1000).
But like I said, most runtimes and browsers don’t support PTC yet, so it probably doesn’t make a huge difference now, but it couldn’t hurt to future-proof your code.
Read more in this question: Node.js: Are there optimizations for tail calls in async functions?
Noticeable difference: Promise rejection gets handled at different places
return somePromise will pass somePromise to the call site, and await somePromise to settle at call site (if there is any). Therefore, if somePromise is rejected, it will not be handled by the local catch block, but the call site's catch block.
async function foo () {
try {
return Promise.reject();
} catch (e) {
console.log('IN');
}
}
(async function main () {
try {
let a = await foo();
} catch (e) {
console.log('OUT');
}
})();
// 'OUT'
return await somePromise will first await somePromise to settle locally. Therefore, the value or Exception will first be handled locally. => Local catch block will be executed if somePromise is rejected.
async function foo () {
try {
return await Promise.reject();
} catch (e) {
console.log('IN');
}
}
(async function main () {
try {
let a = await foo();
} catch (e) {
console.log('OUT');
}
})();
// 'IN'
Reason: return await Promise awaits both locally and outside, return Promise awaits only outside
Detailed Steps:
return Promise
async function delay1Second() {
return delay(1000);
}
call delay1Second();
const result = await delay1Second();
Inside delay1Second(), function delay(1000) returns a promise immediately with [[PromiseStatus]]: 'pending. Let's call it delayPromise.
async function delay1Second() {
return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
Async functions will wrap their return value inside Promise.resolve()(Source). Because delay1Second is an async function, we have:
const result = await Promise.resolve(delayPromise);
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
Promise.resolve(delayPromise) returns delayPromise without doing anything because the input is already a promise (see MDN Promise.resolve):
const result = await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
await waits until the delayPromise is settled.
IF delayPromise is fulfilled with PromiseValue=1:
const result = 1;
ELSE is delayPromise is rejected:
// jump to catch block if there is any
return await Promise
async function delay1Second() {
return await delay(1000);
}
call delay1Second();
const result = await delay1Second();
Inside delay1Second(), function delay(1000) returns a promise immediately with [[PromiseStatus]]: 'pending. Let's call it delayPromise.
async function delay1Second() {
return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
Local await will wait until delayPromise gets settled.
Case 1: delayPromise is fulfilled with PromiseValue=1:
async function delay1Second() {
return 1;
}
const result = await Promise.resolve(1); // let's call it "newPromise"
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: 1
const result = 1;
Case 2: delayPromise is rejected:
// jump to catch block inside `delay1Second` if there is any
// let's say a value -1 is returned in the end
const result = await Promise.resolve(-1); // call it newPromise
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: -1
const result = -1;
Glossary:
Settle: Promise.[[PromiseStatus]] changes from pending to resolved or rejected
This is a hard question to answer, because it depends in practice on how your transpiler (probably babel) actually renders async/await. The things that are clear regardless:
Both implementations should behave the same, though the first implementation may have one less Promise in the chain.
Especially if you drop the unnecessary await, the second version would not require any extra code from the transpiler, while the first one does.
So from a code performance and debugging perspective, the second version is preferable, though only very slightly so, while the first version has a slight legibility benefit, in that it clearly indicates that it returns a promise.
In our project, we decided to always use 'return await'.
The argument is that "the risk of forgetting to add the 'await' when later on a try-catch block is put around the return expression justifies having the redundant 'await' now."
Here is a typescript example that you can run and convince yourself that you need that "return await"
async function test() {
try {
return await throwErr(); // this is correct
// return throwErr(); // this will prevent inner catch to ever to be reached
}
catch (err) {
console.log("inner catch is reached")
return
}
}
const throwErr = async () => {
throw("Fake error")
}
void test().then(() => {
console.log("done")
}).catch(e => {
console.log("outer catch is reached")
});
here i leave some code practical for you can undertand it the diferrence
let x = async function () {
return new Promise((res, rej) => {
setTimeout(async function () {
console.log("finished 1");
return await new Promise((resolve, reject) => { // delete the return and you will see the difference
setTimeout(function () {
resolve("woo2");
console.log("finished 2");
}, 5000);
});
res("woo1");
}, 3000);
});
};
(async function () {
var counter = 0;
const a = setInterval(function () { // counter for every second, this is just to see the precision and understand the code
if (counter == 7) {
clearInterval(a);
}
console.log(counter);
counter = counter + 1;
}, 1000);
console.time("time1");
console.log("hello i starting first of all");
await x();
console.log("more code...");
console.timeEnd("time1");
})();
the function "x" just is a function async than it have other fucn
if will delete the return it print "more code..."
the variable x is just an asynchronous function that in turn has another asynchronous function, in the main of the code we invoke a wait to call the function of the variable x, when it completes it follows the sequence of the code, that would be normal for "async / await ", but inside the x function there is another asynchronous function, and this returns a promise or returns a" promise "it will stay inside the x function, forgetting the main code, that is, it will not print the" console.log ("more code .. "), on the other hand if we put" await "it will wait for every function that completes and finally follows the normal sequence of the main code.
below the "console.log (" finished 1 "delete the" return ", you will see the behavior.

Promise.all vs [await x, await y] - Is it really the same? [duplicate]

This question already has answers here:
Any difference between await Promise.all() and multiple await?
(6 answers)
Closed 1 year ago.
This is a basic question, but i couldn't find the answer to it anywhere.
We have two approaches:
// consider someFunction1() and someFunction2() as functions that returns Promises
Approach #1:
return [await someFunction1(), await someFunction2()]
Approach #2:
return await Promise.all([someFunction1(), someFunction2()])
My Team Leader said that both approaches ended up in the same solution (both functions executting in parallel). But, from my knowledge, the first approach would await someFunction1() to resolve and then would execute someFunction2.
So that's the question, is it really the same, or are there any performance improvements on second approach? Proofs are very welcome!
No, you should not accept that:
return [await someFunction1(), await someFunction2()];
Is the same as:
return await Promise.all([someFunction1(), someFunction2()]);
I should also note that await in the above return await is not needed. Check out this blog post to learn more.
They are different!
The first approach (sequential)
Let's determine the difference by inspecting how each of the two alternatives works.
[await someFunction1(), await someFunction2()];
Here, in an async context, we create an array literal. Note that someFunction1 is called (a function which probably returns a new promise each time it gets called).
So, when you call someFunction1, a new promise is returned, which then "locks" the async context because the preceding await.
In a nutshell, the await someFunction1() "blocks" the array initialization until the returned promise gets settled (by getting resolved or rejected).
The same process is repeated to someFunction2.
Note that, in this first approach, the two promises are awaited in sequence. There is, therefore, no similarity with the approach that uses Promise.all. Let's see why.
The second approach (non-sequential)
Promise.all([someFunction1(), someFunction2()])
When you apply Promise.all, it expects an iterable of promises. It waits for all the promises you give to resolve before returns a new array of resolved values, but don't wait each promise resolve until waiting another one. In essence, it awaits all the promises at the same time, so it is a kind of "non-sequential". As JavaScript is single-threaded, you cannot tell this "parallel", but is very similar in the behavior point of view.
So, when you pass this array:
[someFunction1(), someFunction2()]
You are actually passing an array of promises (which are returned from the functions). Something like:
[Promise<...>, Promise<...>]
Note that the promises are being created outside Promise.all.
So you are, in fact, passing an array of promises to Promise.all. When both of them gets resolved, the Promise.all returns the array of resolved values. I won't explain in all details how Promise.all works, for that, I suggest you checking out the documentation.
You can replicate this "non-sequential" approach by creating the promises before using the await. Like so:
const promise1 = someFunction1();
const promise2 = someFunction2();
return [await promise1, await promise2];
While promise1 is being waited, promise2 is already running (as it was created before the first await), so the behavior is similar to Promise.all's.
My Team Leader said that both approaches ended up in the same solution (both functions executting in parallel).
That is incorrect.
But, from my knowledge, the first approach would await someFunction1() to resolve and then would execute someFunction2.
That is correct.
Here is a demonstration
Approach 1:
const delay = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
async function Approach1() {
return [await someFunction1(), await someFunction2()];
}
async function someFunction1() {
const result = await delay(800, "hello");
console.log(result);
return result;
}
async function someFunction2() {
const result = await delay(400, "world");
console.log(result);
return result;
}
async function main() {
const start = new Date();
const result = await Approach1();
const totalTime = new Date() - start;
console.log(`result: ${result}
total time: ${totalTime}`);
}
main();
Result is:
hello
world
result: hello,world
total time: 1205
Which means that someFunction1 runs to completion first and then someFunction2 is executed. It is sequential
Approach 2:
const delay = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
async function Approach2() {
return await Promise.all([someFunction1(), someFunction2()]);
}
async function someFunction1() {
const result = await delay(800, "hello");
console.log(result);
return result;
}
async function someFunction2() {
const result = await delay(400, "world");
console.log(result);
return result;
}
async function main() {
const start = new Date();
const result = await Approach2();
const totalTime = new Date() - start;
console.log(`result: ${result}
total time: ${totalTime}`);
}
main();
Result is:
world
hello
result: hello,world
total time: 803
Which means that someFunction2 finishes before someFunction1. The two are parallel.
Easy to see the difference
function createTimer(ms, id) {
console.log(`id: ${id} started ${new Date()}`);
return new Promise((res, rej) => {
setTimeout( () => {
console.log(`id: ${id} finished ${new Date()}`);
res(id);
}, ms );
});
}
(async function() {
var result1 = [await createTimer(5000, '1'), await createTimer(5000, '2')];
var result2 = await Promise.all([createTimer(5000, '3'), createTimer(5000, '4')]);
console.log(result1);
console.log(result2);
})();
The first one starts 1 and when 1 finishes it starts 2.
The second one starts 3 and 4 at almost the very same moment.
If you start with a function which simulates doing some work which outputs at stages of that work but takes some time. eg
function someFunction1(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction1", i);
if(i == 5){
clearInterval(intervalId)
resolve(1);
}
}, 1000);
});
}
And then you duplicate that with a second, similar, method. You plug your 2 methods in and you see that the one using Promise.all does it in parallel but the one using 2 await calls does it in series.
Parallel
function someFunction1(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction1", i);
if(i == 5){
clearInterval(intervalId)
resolve(1);
}
}, 1000);
});
}
function someFunction2(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction2", i);
if(i == 5){
clearInterval(intervalId)
resolve(2);
}
}, 1000);
});
}
(async function(){
const result = await Promise.all([someFunction1(),someFunction2()]);
console.log("result",result);
})();
Series
function someFunction1(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction1", i);
if(i == 5){
clearInterval(intervalId)
resolve(1);
}
}, 1000);
});
}
function someFunction2(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction2", i);
if(i == 5){
clearInterval(intervalId)
resolve(2);
}
}, 1000);
});
}
(async function(){
const result = [await someFunction1(),await someFunction2()];
console.log("result",result);
})();
Both give the exact same result but getting there is very different.
MDN documentation for Promise.all() states that
This method can be useful for aggregating the results of multiple promises. It is typically used when there are multiple related asynchronous tasks that the overall code relies on to work successfully — all of whom we want to fulfill before the code execution continues.
While it isn't explicit, you can await Promise.all to track multiple promises. Only when all promises are resolved will the code execution continue.
The other approach you mention of capturing separate asynchronous tasks in an array is not the same due to how await operates.
An await splits execution flow, allowing the caller of the async function to resume execution. After the await defers the continuation of the async function, execution of subsequent statements ensues. If this await is the last expression executed by its function, execution continues by returning to the function's caller a pending Promise for completion of the await's function and resuming execution of that caller.
So, each await will pause execution before resuming. No need for a demonstration.

JS: Unsure of when to use Promise.resolve() vs returning a new Promise

I am unsure how the usage of returning a new Promise vs using a Promise.resolve() and want to make sure my understanding of these are correct.
Given these 3 functions below:
function testFunc() {
return Promise.resolve().then(() => {
//anything asynchronous in here has now become synchronous and
//execution of result of the function happens after this??
let i = 0;
while (i < 10000) {
console.log(i);
i++;
}
});
}
------
function testFunc2() {
return new Promise((resolve, reject) => {
//anything asynchronous in here is still asynchronous but the
//`resolve()` is then synchronous??
let i = 0;
while (i < 10000) {
if (i === 999) { resolve('I am a test func') };
i++;
}
})
}
------
//synchronous function
function logMe() {
let i = 0;
while (i < 10000) {
console.log("INSIDE LOG ME);
i++;
}
}
The way I understand it is that testFunc() immediately resolves the Promise and anything within there becomes synchronous. So if you were to execute:
testFunc();
logMe();
testFunc() would fully execute before logMe() was reached and executed.
For the testFunc2(), if it were executed in this order:
testFunc2();
logMe();
I understand it as the logic inside, in this case the while loop, would still execute synchronously and delay the execution of the following function, logMe(), but the resolve would be treated asynchronously.
It’s not that easy. You would have to use test Func.then(logMe()). Another option would be to run both functions in an asynchronous function:
async function run() {
await testFunc();
logMe();
}
The await is pretty simple - it runs the function and waits for it to finish, then runs the next lines. async is just there so that await can work (await does not work outside of async functions).
I prefer async/await, but many more prefer .then. It’s just what you want to use, both are very similar.
If I understand correctly what you're trying to achieve, you want to asynchronously execute operation inside the loop.
function testFunc2() {
const promises = []
for(let i = 0; i<1000; i++){
promises.push(new Promise((resolve) => {
console.log(i);
resolve()
}));
}
return Promise.all(promises);
}
function logMe() {
let i = 0;
while (i < 5) {
console.log("INSIDE LOG ME");
i++;
}
}
(async() => {
await testFunc2()
logMe();
})();

What is the control flow through this code?

The following code (yes I know it is not idiomatic) prints 1,2. But I expected it to print 2,1.
(async()=>{
let resolve;
new Promise((r)=>{
resolve = r
}).then(()=>console.log('1'))
await resolve();
console.log('2')
})()
1. (async()=>{
2. let resolve;
3. new Promise((r)=>{
4. resolve = r
5. }).then(()=>console.log('1'))
6. await resolve();
7. console.log('2')
8. })()
Expected control flow:
Line 1: instantiate an anonymous async function expression
Line 8: ...and immediately call it
Line 2: variable declaration
Line 3 & 4: Instantiate Promise, run the executor function, assign the variable
Line 5: configure the `then` with the callback
Line 6: Schedule evaluation of the `then` for the next microtask
Line 6 (contd.): `resolve()` synchronously returns `undefined`
Line 6 (contd.): `await` triggered with undefined (unsure of action)
Line 7: Print `2`
Line 8: Pop execution contexts from stack
<on next microtask>
Line 5: Push execution context for anonymous function onto stack, print `1`
Why is this wrong? Does the async keyword schedule the expression to the right to be on the next microtask, keeping the order 1,2?
The issue seems to be the usage of await. When the keyword is encountered, the JavaScript interpreter will immediately pause the rest of the function and only resume the rest of the execution after the Promise is resolved. If you await nonPromiseValue it's treated as if you've created it via Promise.resolve() and is essentially equivalent to
Promise.resolve(nonPromiseValue)
.then((resolvedValue) => {
resolvedValue;
})
Or even more generally this:
await <await expression>;
<rest of body>;
return <return expression>;
Works like this:
Promise.resolve(<await expression>)
.then(resolvedValue => {
resolvedValue();
<rest of body>;
return <return expression>;
});
Thus and then you'd still have the rest of the body of the function put on the microtask queue and executed at least the next time the event loop picks a task.
NOTE: I'm omitting some details because the engine would be pausing and unpausing the execution, so if the await expression is part of another statement, you might get slightly different behaviour than what you'd expect at a glance, yet still the reason is immediate pausing when await is encountered. Thus expressions before and after an await keyword would be evaluated at different times and thus might have a different result.
At any rate, here is an example:
async function foo() {
console.log("foo - start");
"any value without await";
console.log("foo - end")
}
async function bar() {
console.log("bar - start");
await "any value";
console.log("bar - end")
}
console.log('start');
foo();
bar();
console.log('end');
For foo() the execution is straight forward:
Call the function.
Execute every line in the body.
Finish.
Yes, it's async but it's still executed synchronously. The only difference here is that the result would be a Promise but since it's all synchronous and there is no return keyword, then it's just implicitly Promise.resolve(undefined).
However, for bar() the execution is different:
Call the function.
Execute every line until you encounter await.
Turn the rest of the function to a Promise.
Pause and wait until the Promise is resolved.
Since we've await-ed a non-Promise this will happen immediately the next event loop iteration.
Continue the execution.
So, in fact the body of the function is wrapped in a Promise behind the scenes, so we actually run something similar to this:
async function bar() {
console.log("bar - start");
Promise.resolve("any value") //<-- await "any value";
.then((resolvedValue) => {
resolvedValue; //<-- nothing is done with it but it's what we awaited
console.log("bar - end"); //<-- the rest of the body of `bar()`
})
}
console.log('start');
bar();
console.log('end');
This is a simplified view but it's to just help visualise the fact that await will always halt execution and resume later. It uses the same Promise mechanics as usual and would delay the rest of the body for a later iteration of the event loop via a microtask but it's automatically handled for you.
Were we actually await-ing a real Promise, then you'd still get the equivalent behaviour:
async function bazAwait() {
console.log("bazAwait - start");
const result = await new Promise(resolve => resolve("some Promise value"));
console.log("bazAwait - end", result); //<-- the rest of the body of `bazAwait()`
}
async function bazPromiseEquivalent() {
console.log("bazPromiseEquivalent - start");
new Promise(resolve => resolve("some Promise value"))//<-- the awaited Promise
.then((promiseVal) => { //<-- the value the Promise resolves with
const result = promiseVal; //<-- the binding for the resolved value
console.log("bazPromiseEquivalent - end", result); //<-- the rest of the body of `bazPromiseEquivalent()`
});
}
console.log('start');
bazAwait();
bazPromiseEquivalent();
console.log('end');
I heavily suspect this is done in order to keep the behaviour the same regardless of whether or not await a Promise. Otherwise if you had a line like await myValue you'd get different execution depending on what myValue holds and it will not be obvious until you actually check that.
The following code appears to demonstrate how code after an await is treated as a microtask, similar to a then.
A tight indirectly-recursive loop of microtasks is started, that prints the first five integers to the console.
An async function is invoked synchronously. A string inside foo 2 is printed to the console after an await.
I included a generator function to remind us that they are synchronous.
async iterator included for completeness.
Note the interleaving with the "then tasks."
function printNums() {
let counter = 0
function go() {
console.log(counter++)
if(counter < 5) Promise.resolve().then(go)
}
Promise.resolve().then(go)
}
printNums()
async function foo() {
console.log('inside foo 1')
await 1
console.log('inside foo 2')
}
requestAnimationFrame(() => console.log('raf complete')) // ~16ms
setTimeout(() => console.log('macrotask complete')) // ~4ms
console.log('synch')
const syncIterable = {
*[Symbol.iterator]() {
console.log('starting sync iterable')
yield 'a'
yield 'b'
}
}
async function printSyncIterable() {
for(let y of syncIterable) {
console.log(y)
}
}
printSyncIterable()
foo().then(() => console.log('done'))
const asyncIterable = {
async *[Symbol.asyncIterator]() {
console.log('starting async iterable')
yield '⛱'
yield '🍿'
}
}
async function printAsyncIterable() {
for await(let z of asyncIterable) {
console.log(z)
}
}
printAsyncIterable()

Is setTimeout required?

I have a question to async, await and setTimeout().
I thought, I use asynchron functions for slow processes. So I tried it with a large loop. On my Computer, it needs few seconds to run the following code:
function slowFunction() {
return new Promise(resolve => {
setTimeout(() => {
for (let i = 0; i < 4000000000; i++) {};
resolve('Ready at ' + new Date().toLocaleTimeString('de'));
}, 0);
});
};
console.log('Start: ' + new Date().toLocaleTimeString('de'));
(async () => {
console.log('Call slow function.');
console.log(await slowFunction());
})();
console.log('There is no need to wait for the slow function: ' + new Date().toLocaleTimeString('de'));
The output is:
Start: 16:39:20
Call slow function.
There is no need to wait for the slow function: 16:39:20
Ready at 16:39:23
And now the question: What is the difference to the next code:
function slowFunction() {
return new Promise(resolve => {
for (let i = 0; i < 4000000000; i++) {};
resolve('Ready at ' + new Date().toLocaleTimeString('de'));
});
};
console.log('Start: ' + new Date().toLocaleTimeString('de'));
(async () => {
console.log('Call slow function.');
console.log(await slowFunction());
})();
console.log('There is no need to wait for the slow function: ' + new Date().toLocaleTimeString('de'));
The output is:
Start: 16:39:20
Call slow function.
There is no need to wait for the slow function: 16:39:23
Ready at 16:39:23
By the first example, it looks like asynchron. By the second example, the function wait for the end of loop.
Do I have to use setTimeout or do I have an error in the code or am I getting it wrong? I both cases, the resolve statment is behind the large loop.
The most examples for async and await used setTimeout, but I think, it's just to simulate a break.
Thanks for your help in advance.
Best greets
Pascal
TL:DR
Promises and async functions don't offload your code to another thread. If you want to move that long-running process off the main thread, on browsers look at web workers, and on Node.js look at child processes.
Details
Promises and async functions (which are just a syntax for creating and consuming promises) don't move your processing to any other thread, it still happens on the same thread you start the process on. The only thing they do is ensure that then and catch callbacks are called asynchronously. They don't make your code asynchronous (other than that one thing, ensuring the callbacks happen asynchronously).
So your first block using setTimeout just sets a timeout, returns a promise, and then when the timeout expires it blocks the main thread while your slow-running process executes. This just changes when the blocking happens a little bit, it doesn't change the fact of the blocking.
You can see that effect here, notice how the counter pauses when the long-running process occurs:
function slowFunction() {
return new Promise(resolve => {
setTimeout(() => {
const stop = Date.now() + 2000;
while (Date.now() < stop) {
// busy wait (obviously, never really do this)
}
}, 1000);
});
};
console.log("before slowFunction");
slowFunction()
.then(() => {
console.log("then handler on slowFunction's promise");
})
.catch(console.error);
console.log("after slowFunction");
let counter = 0;
const timer = setInterval(() => {
console.log(++counter);
}, 100);
setTimeout(() => {
clearInterval(timer);
console.log("done");
}, 3000);
.as-console-wrapper {
max-height: 100% !important;
}
Your second block not using setTimeout just blocks right away, because the promise executor function (the function you pass new Promise) runs immediately and synchronously, and you're not doing anything to make it asynchronous.
You can see that here; the counter pauses right away, not later:
function slowFunction() {
return new Promise(resolve => {
const stop = Date.now() + 2000;
while (Date.now() < stop) {
// busy wait (obviously, never really do this)
}
});
};
console.log("before slowFunction");
slowFunction()
.then(() => {
console.log("then handler on slowFunction's promise");
})
.catch(console.error);
console.log("after slowFunction");
let counter = 0;
const timer = setInterval(() => {
console.log(++counter);
}, 100);
setTimeout(() => {
clearInterval(timer);
console.log("done");
}, 3000);
.as-console-wrapper {
max-height: 100% !important;
}
We don't even see the before slowFunction log appear until after the long-running code has finished, because the browser never got a chance to repaint, we had the thread hogged.
Regarding async functions: The code in an async function starts out synchronous, and is synchronous until the first await (or other construct, such as setTimeout, that schedules things to execute later). Only the code after that is asynchronous (because it had to wait).
Here's an example demonstrating that:
async function foo() {
console.log("before await");
await Promise.resolve();
console.log("after await");
}
console.log("before foo");
foo()
.then(() => {
console.log("then handler on foo's promise");
})
.catch(console.error);
console.log("after foo");
Here's the output of that:
before foo
before await
after foo
after await
then handler on foo's promise
Notice how before await occurs before after foo; it's synchronous with the call to foo. But then after await doesn't occur until later (because await Promise.resolve() has to make the code following it occur asynchronously; it's syntactic sugar for then, which promises not to call its handler synchronously even if the promise is already resolved).
The difference is that this is completely synchronous code:
return new Promise(resolve => {
for (let i = 0; i < 4000000000; i++) {};
resolve('Ready at ' + new Date().toLocaleTimeString('de'));
});
This statement will block the JavaScript thread and force it to wait until all of those 4 billion iterations have taken place. Then it will move on to the next statement. Since the console.log executes after this, it will not execute until that loop has finished.
That's why you are seeing a difference.

Categories

Resources