Ilya Loskutov
Ilya Loskutov

Reputation: 2201

Misunderstanding of async/await evaluation details

I try to follow the ECMAScript spec to find out how async/await do its work and encountered some hurdle.

Let's suppose there is such code:

function boo() {
    return 1;
}

async function foo() {
    let result = await boo();
    return result;
}

foo();

So let's start to track step by step what must be done under the hood:

  1. Running [[Call]] internal method of foo(), that suspends the current execution context (callerContext), creates new one (calleeContext) and pushes it onto the execution context stack. We have the execution context stack of this look: callerContext < calleeContext -- running.

  2. The next step is a foo() evaluation (that is AsyncFunctionBody associated with it), that in effect is EvaluateAsyncFunctionBody. This routine, in turn, calls AsyncFunctionStart. It creates a copy of the running execution context - asyncContext - and pushes it onto the stack, which now is as follows: callerContext < calleeContext < asyncContext -- running. calleeContext is left anxious to receive the result of asyncContext evaluation later (step 10).

  3. As it was prescribed in AsyncFunctionStart, asyncContext evaluates FunctionBodyof foo(). The await expression is met along the way. It is defined to get the result of boo() firstly and subsequently call Await routine with 1 as an argument.

  4. Await creates a promise resolved with 1 and performs its then(), so that the promise can notify of its fulfilled value and this value would exposed as the result of the await expression. But for now asyncContext is removed from the execution context stack, which at the current time is callerContext < calleeContext -- running.

It is difficult to me to understand what is next due to happen. A promise takes the opportunity to notify its subscribers only when the execution context stack is empty because for the sake of it the Job queue is involved. In the other hand, caleeContext is waiting for the result of asyncContext evaluation (step 10 of AsyncFunctionStart) and I can't find the place where both execution contexts (calleeContext and callerContext) removed from the stack. It seems like a deadlock!

Upvotes: 3

Views: 155

Answers (1)

Bergi
Bergi

Reputation: 664538

caleeContext is waiting for the result of asyncContext evaluation

Not really "waiting". As you said, the await keyword removes the asyncContext from the call stack, and returns1 undefined in step 6 of AsyncFunctionStart2. After this is done, step 5 of EvaluateAsyncFunctionBody does return the promise result. With this result, step 7 of [[Call]] is done, and step 8 does "Remove calleeContext from the execution context stack and restore callerContext as the running execution context.". Now foo() has returned the promise.

The callerContext is finally removed from the stack in step 14 of ScriptEvaluation - it's the global scriptContext.

1: See also the NOTE (step 15) in Await: "This returns to the evaluation of the operation that had most previously resumed evaluation of asyncContext."
2: See also the assertions (step 7 and 8) in AsyncFunctionStart: "When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context. result is a normal completion with a value of undefined. [A] possible source of completion values [is] Await".

Upvotes: 1

Related Questions