Description
this issue is similar with #715, if we use chrome v8 async/await and compile angular with tsconfig target 'ES2017', then typescript will not generate __awaiter code and use native async/await.
and the following logic will fail
(click) = test();
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const test = async () => {
console.log(Zone.current.name) // will output 'angular'
await delay(100);
console.log(Zone.current.name) // will output 'root'
}
unlike typescript transpiler, native async/await will first yield from test, and then call promise.then
for continuation when await something. So Zone currentFrame will become root.
The sequence of above logic when call await delay(100)
will look like
- delay function return a ZoneAwarePromise, Zone.current is angular
- test function return native Promise which generate from chrome v8 by await, and test() execution is finished.
- So Zone.currentZone is transite from angular->root
- ZoneAwarePromise which generated from step1 was chained by called by
Promise.prototype.then
- delay timeout is executed, and ZoneAwarePromise resolved
- the chained Promise is resolved , but the Zone.current is root.
Based on the spec,
https://tc39.github.io/ecmascript-asyncawait/#abstract-ops-async-function-await
1. Let asyncContext be the running execution context.
2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
3. Let resolveResult be ! Call(promiseCapability.[[Resolve]], undefined, « value »).
4. Let onFulfilled be a new built-in function object as defined in AsyncFunction Awaited Fulfilled.
5. Let onRejected be a new built-in function object as defined in AsyncFunction Awaited Rejected.
6. Set onFulfilled and onRejected's [[AsyncContext]] internal slots to asyncContext.
7. Let throwawayCapability be NewPromiseCapability(%Promise%).
8. Perform ! PerformPromiseThen(promiseCapability.[[Promise]], onFulfilled, onRejected, throwawayCapability).
9. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
10. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion resumptionValue the following steps will be performed:
11. Return resumptionValue.
12. Return.
Step8 is not be executed immediately but in the microTask queue after the current function execution.
I checked Chrome v8 source,
https://chromium.googlesource.com/v8/v8/+/refs/heads/5.5.10/src/js/promise.js
function ResolvePromise(promise, resolution) {
if (resolution === promise) {
return RejectPromise(promise,
%make_type_error(kPromiseCyclic, resolution),
true);
}
if (IS_RECEIVER(resolution)) {
// 25.4.1.3.2 steps 8-12
try {
var then = resolution.then;
} catch (e) {
return RejectPromise(promise, e, true);
}
// Resolution is a native promise and if it's already resolved or
// rejected, shortcircuit the resolution procedure by directly
// reusing the value from the promise.
if (IsPromise(resolution) && then === PromiseThen) {
var thenableState = GET_PRIVATE(resolution, promiseStateSymbol);
if (thenableState === kFulfilled) {
// This goes inside the if-else to save one symbol lookup in
// the slow path.
var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol);
FulfillPromise(promise, kFulfilled, thenableValue,
promiseFulfillReactionsSymbol);
SET_PRIVATE(promise, promiseHasHandlerSymbol, true);
return;
} else if (thenableState === kRejected) {
var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol);
if (!HAS_DEFINED_PRIVATE(resolution, promiseHasHandlerSymbol)) {
// Promise has already been rejected, but had no handler.
// Revoke previously triggered reject event.
%PromiseRevokeReject(resolution);
}
// Don't cause a debug event as this case is forwarding a rejection
RejectPromise(promise, thenableValue, false);
SET_PRIVATE(resolution, promiseHasHandlerSymbol, true);
return;
}
}
if (IS_CALLABLE(then)) {
// PromiseResolveThenableJob
var id;
var name = "PromiseResolveThenableJob";
var instrumenting = DEBUG_IS_ACTIVE;
%EnqueueMicrotask(function() {
if (instrumenting) {
%DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
}
// These resolving functions simply forward the exception, so
// don't create a new debugEvent.
var callbacks = CreateResolvingFunctions(promise, false);
try {
%_Call(then, resolution, callbacks.resolve, callbacks.reject);
} catch (e) {
%_Call(callbacks.reject, UNDEFINED, e);
}
if (instrumenting) {
%DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
}
});
if (instrumenting) {
id = ++lastMicrotaskId;
%DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
}
return;
}
}
FulfillPromise(promise, kFulfilled, resolution, promiseFulfillReactionsSymbol);
}
ZoneAwarePromise is not treated as native one, so Chrome v8 enqueue a micro task to perform then
call. This maybe the reason.
And it seems the logic is totally changed in v8 6.0, so I will try the chromium 6.0 to see what happened.