Commit 0272aa50 authored by caitp's avatar caitp Committed by Commit bot

[promise] separate PerformPromiseThen from PromiseThen

The `PerformPromiseThen` spec-internal operation is used by the async functions
proposal, in order to ensure that AwaitExpressions are not observable via
usual mechanisms/hooks, such as Symbol.species.

BUG=v8:5253
R=littledan@chromium.org, adamk@chromium.org, gsathya@chromium.org, yangguo@chromium.org

Review-Url: https://codereview.chromium.org/2209433003
Cr-Commit-Position: refs/heads/master@{#38353}
parent e1a58dc4
......@@ -13,29 +13,44 @@
var AsyncFunctionNext;
var AsyncFunctionThrow;
var PromiseReject;
var PromiseResolve;
var PromiseThen;
var IsPromise;
var GlobalPromise;
var NewPromiseCapability;
var PerformPromiseThen;
utils.Import(function(from) {
AsyncFunctionNext = from.AsyncFunctionNext;
AsyncFunctionThrow = from.AsyncFunctionThrow;
PromiseReject = from.PromiseCreateRejected;
PromiseResolve = from.PromiseCreateResolved;
PromiseThen = from.PromiseThen;
IsPromise = from.IsPromise;
GlobalPromise = from.GlobalPromise;
NewPromiseCapability = from.NewPromiseCapability;
PerformPromiseThen = from.PerformPromiseThen;
});
// -------------------------------------------------------------------
function AsyncFunctionAwait(generator, value) {
return %_Call(
PromiseThen, PromiseResolve(value),
function(sentValue) {
return %_Call(AsyncFunctionNext, generator, sentValue);
},
function(sentError) {
return %_Call(AsyncFunctionThrow, generator, sentError);
});
// Promise.resolve(value).then(
// value => AsyncFunctionNext(value),
// error => AsyncFunctionThrow(error)
// );
var promise;
if (IsPromise(value)) {
promise = value;
} else {
var promiseCapability = NewPromiseCapability(GlobalPromise);
%_Call(promiseCapability.resolve, UNDEFINED, value);
promise = promiseCapability.promise;
}
var onFulfilled =
(sentValue) => %_Call(AsyncFunctionNext, generator, sentValue);
var onRejected =
(sentError) => %_Call(AsyncFunctionThrow, generator, sentError);
var throwawayCapability = NewPromiseCapability(GlobalPromise);
return PerformPromiseThen(promise, onFulfilled, onRejected,
throwawayCapability);
}
%InstallToContext([ "async_function_await", AsyncFunctionAwait ]);
......
......@@ -187,9 +187,11 @@ function PostNatives(utils) {
"AsyncFunctionThrow",
"GetIterator",
"GetMethod",
"GlobalPromise",
"IntlParseDate",
"IntlParseNumber",
"IsNaN",
"IsPromise",
"MakeError",
"MakeRangeError",
"MakeTypeError",
......@@ -198,7 +200,9 @@ function PostNatives(utils) {
"MapIteratorNext",
"MaxSimple",
"MinSimple",
"NewPromiseCapability",
"NumberIsInteger",
"PerformPromiseThen",
"PromiseChain",
"PromiseDefer",
"PromiseAccept",
......
......@@ -390,40 +390,47 @@ function PromiseCreateResolved(x) {
return %_Call(PromiseResolve, GlobalPromise, x);
}
// ES#sec-promise.prototype.then
// Promise.prototype.then ( onFulfilled, onRejected )
// Multi-unwrapped chaining with thenable coercion.
function PromiseThen(onResolve, onReject) {
var status = GET_PRIVATE(this, promiseStateSymbol);
if (IS_UNDEFINED(status)) {
throw MakeTypeError(kNotAPromise, this);
}
function PerformPromiseThen(promise, onResolve, onReject, resultCapability) {
if (!IS_CALLABLE(onResolve)) onResolve = PromiseIdResolveHandler;
if (!IS_CALLABLE(onReject)) onReject = PromiseIdRejectHandler;
var constructor = SpeciesConstructor(this, GlobalPromise);
onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler;
onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler;
var deferred = NewPromiseCapability(constructor);
var status = GET_PRIVATE(promise, promiseStateSymbol);
switch (status) {
case kPending:
PromiseAttachCallbacks(this, deferred, onResolve, onReject);
PromiseAttachCallbacks(promise, resultCapability, onResolve, onReject);
break;
case kFulfilled:
PromiseEnqueue(GET_PRIVATE(this, promiseResultSymbol),
onResolve, deferred, kFulfilled);
PromiseEnqueue(GET_PRIVATE(promise, promiseResultSymbol),
onResolve, resultCapability, kFulfilled);
break;
case kRejected:
if (!HAS_DEFINED_PRIVATE(this, promiseHasHandlerSymbol)) {
if (!HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) {
// Promise has already been rejected, but had no handler.
// Revoke previously triggered reject event.
%PromiseRevokeReject(this);
%PromiseRevokeReject(promise);
}
PromiseEnqueue(GET_PRIVATE(this, promiseResultSymbol),
onReject, deferred, kRejected);
PromiseEnqueue(GET_PRIVATE(promise, promiseResultSymbol),
onReject, resultCapability, kRejected);
break;
}
// Mark this promise as having handler.
SET_PRIVATE(this, promiseHasHandlerSymbol, true);
return deferred.promise;
SET_PRIVATE(promise, promiseHasHandlerSymbol, true);
return resultCapability.promise;
}
// ES#sec-promise.prototype.then
// Promise.prototype.then ( onFulfilled, onRejected )
// Multi-unwrapped chaining with thenable coercion.
function PromiseThen(onResolve, onReject) {
var status = GET_PRIVATE(this, promiseStateSymbol);
if (IS_UNDEFINED(status)) {
throw MakeTypeError(kNotAPromise, this);
}
var constructor = SpeciesConstructor(this, GlobalPromise);
var resultCapability = NewPromiseCapability(constructor);
return PerformPromiseThen(this, onResolve, onReject, resultCapability);
}
// Unspecified V8-specific legacy function
......@@ -622,6 +629,8 @@ utils.InstallFunctions(extrasUtils, 0, [
fn => %FunctionRemovePrototype(fn));
utils.Export(function(to) {
to.IsPromise = IsPromise;
to.PromiseChain = PromiseChain;
to.PromiseDefer = PromiseDefer;
to.PromiseAccept = PromiseAccept;
......@@ -629,6 +638,10 @@ utils.Export(function(to) {
to.PromiseCreateRejected = PromiseCreateRejected;
to.PromiseCreateResolved = PromiseCreateResolved;
to.PromiseThen = PromiseThen;
to.GlobalPromise = GlobalPromise;
to.NewPromiseCapability = NewPromiseCapability;
to.PerformPromiseThen = PerformPromiseThen;
});
})
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-async-await --allow-natives-syntax
function assertEqualsAsync(expected, run, msg) {
var actual;
var hadValue = false;
var hadError = false;
var promise = run();
if (typeof promise !== "object" || typeof promise.then !== "function") {
throw new MjsUnitAssertionError(
"Expected " + run.toString() +
" to return a Promise, but it returned " + PrettyPrint(promise));
}
promise.then(function(value) { hadValue = true; actual = value; },
function(error) { hadError = true; actual = error; });
assertFalse(hadValue || hadError);
%RunMicrotasks();
if (hadError) throw actual;
assertTrue(
hadValue, "Expected '" + run.toString() + "' to produce a value");
assertEquals(expected, actual, msg);
};
// Rename a function so that it can help omit things from stack trace.
function test(fn) {
return Object.defineProperty(fn, "name", {
enumerable: false,
configurable: true,
value: "@" + fn.name,
writable: false
});
}
function getStack(error) {
var stack = error.stack.split('\n').
filter(function(line) {
return /^\s*at @?[a-zA-Z0-9_]/.test(line);
}).
map(line => line.replace(/^\s*at (@?[a-zA-Z0-9_\.\[\]]+)(.*)/, "$1"));
// remove `Promise.then()` invocation by assertEqualsAsync()
if (stack[2] === "assertEqualsAsync") return [];
return stack.reverse();
}
var log = [];
class FakePromise extends Promise {
constructor(executor) {
var stack = getStack(new Error("Getting Callstack"));
if (stack.length) {
var first = -1;
for (var i = 0; i < stack.length; ++i) {
if (stack[i][0] === '@') {
first = i;
break;
}
}
while (first > 0) stack.shift(), --first;
if (stack.length) {
log.push("@@Species: [" + stack.join(" > ") + "]");
}
}
return new Promise(executor);
}
};
Object.defineProperty(Promise, Symbol.species, {
value: FakePromise,
configurable: true,
enumerable: false,
writable: false
});
// Internal `AsyncFunctionAwait` only --- no @@species invocations.
async function asyncFn() { return await "foo"; }
assertEqualsAsync("foo", test(function testInternalOnly() { return asyncFn(); },
"should not call Promise[@@Species]"));
assertEquals([], log);
log.length = 0;
assertEqualsAsync(
"foo",
test(function testThenOnReturnedPromise() {
return asyncFn().then(x => (log.push("Then: " + x), x));
}),
"should call Promise[@@Species] after non-internal Then");
assertEquals([
"@@Species: [@testThenOnReturnedPromise > Promise.then > FakePromise]",
"Then: foo"
], log);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment