Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async function with +=

let x = 0;  async function test() {     x += await 5;     console.log('x :', x); }  test(); x += 1; console.log('x :', x);

The values of x logged are 1 and 5. My question is: why is the value of x 5 on second log?

If the test is executed after x += 1 (since it is an async function) then the value of x is 1 by the time test is executed, so x += await 5 should make the value of x 6.

like image 240
Aldrin Avatar asked Jan 18 '20 16:01

Aldrin


People also ask

How do you call async function?

Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.

Can async function have parameters?

An async function (or AyncFunction) in the context of Async is an asynchronous function with a variable number of parameters where the final parameter is a callback.

Why is it that using async functions with event handlers is problematic?

Using async functions with event handlers is problematic, because it can lead to an unhandled rejection in case of a thrown exception: const ee = new EventEmitter(); ee. on('something', async (value) => { throw new Error('kaboom'); });

What is async function in node JS?

The async function helps to write promise-based code asynchronously via the event-loop. Async functions will always return a value. Await function can be used inside the asynchronous function to wait for the promise. This forces the code to wait until the promise returns a result.


2 Answers

TL;DR: Because += reads x before, but writes it after it has changed, due to the await keyword in its second operand (right-hand side).


async functions run synchronously when they are called until the first await statement.

So, if you remove await, it behaves like a normal function (with the exception that it still returns a Promise).

In that case, you get 5 (from the function) and 6 (from the main script) in the console:

let x = 0;  async function test() {   x += 5;   console.log('x :', x); }  test(); x += 1; console.log('x :', x);

The first await stops synchronous running, even if its argument is an already resolved promise (or as in here, not a promise at all - these will be converted to resolved promises by await), so the following will return 1 (from the main script) and 6 (from the function), as you expected:

let x = 0;  async function test() {   // Enter asynchrony   await 0;    x += 5;   console.log('x :', x); }  test(); x += 1; console.log('x :', x);

However, your case is a bit more complicated.

You've put await inside an expression, that uses +=.

You probably know, that in JS x += y is identical to x = (x + y) (unless x is an expression with side-effects, which isn't the case here). I'll use the latter form for better understanding:

let x = 0;  async function test() {   x = (x + await 5);   console.log('x :', x); }  test(); x += 1; console.log('x :', x);

When the interpreter reaches this line...

x = (x + await 5); 

...it starts evaluating it, substitutes x, so it turns to...

x = (0 + await 5); 

...then, it evaluates the expression inside await (5), turns it into a resolved promise, and starts waiting for it.

The code after the function call starts to run, and modifies the value of x (from 0 to 1), then logs it.

x is now 1.

Then, after the main script finishes, the interpreter goes back to the paused test function, and continues evaluating the line, which, with the await out of the way, looks like this:

x = (0 + 5); 

And, since the value of x is already substituted, it remains 0.

Finally, the interpreter does the addition, stores 5 to x, and logs it.

You can check this behaviour by logging inside an object property getter/setter (in this example, y.z, which reflects the value of x:

let x = 0; const y = {   get z() {     console.log('get x :', x);     console.log(new Error().stack.replace('Error', 'Stacktrace')); //Log stacktrace using an Error object     return x;   },   set z(value) {     console.log('set x =', value);     console.log(new Error().stack.replace('Error', 'Stacktrace')); //Log stacktrace using an Error object     x = value;   } };  async function test() {   console.log('inside async function');   y.z += await 5;   console.log('x :', x); }  test(); console.log('main script'); y.z += 1; console.log('x :', x); console.log('end of main script')  /* Output:  inside async function get x : 0 <-------------- async fn reads Stacktrace     at Object.get z [as z] (https://stacksnippets.net/js:19:17)     at test (https://stacksnippets.net/js:31:3) <-- async fn is synchronous here     at https://stacksnippets.net/js:35:1 <--------- (main script is still in the stack)  main script get x : 0 Stacktrace     at Object.get z [as z] (https://stacksnippets.net/js:19:17)     at https://stacksnippets.net/js:37:1 set x = 1 Stacktrace     at Object.set z [as z] (https://stacksnippets.net/js:24:17)     at https://stacksnippets.net/js:37:5 x : 1 end of main script  set x = 5 <-------------- async fn writes Stacktrace     at Object.set z [as z] (https://stacksnippets.net/js:24:17)     at test (https://stacksnippets.net/js:31:7) <-- async fn is asynchronous (main script is no longer in the stack) x : 5 <------------------ async fn logs  */
/* Just to make console fill the available space */ .as-console-wrapper {   max-height: 100% !important; }
like image 124
FZs Avatar answered Sep 21 '22 20:09

FZs


Your statement x += await 5 desugars to

const _temp = x; const _gain = await 5; x = _temp + _gain; 

The _temporary value is 0, and if you change x during the await (which your code does) it doesn't matter, it gets assigned 5 afterwards.

like image 35
Bergi Avatar answered Sep 20 '22 20:09

Bergi