Commit af4ff8c7 authored by Sathya Gunasekaran's avatar Sathya Gunasekaran Committed by Commit Bot

[ESNext] Update Promise.prototype.finally to match latest spec

The spec calls out to Promise.prototype.then and also passes around
the constructor of the receiver to Promise.prototype.finally.

Adds a new constructor slot to PromiseFinallyContext enum and this is
used to create a new promise in the thenFinally/catchFinally callbacks.

Created a new PromiseResolve TFS builtin refactored from
the existing PromiseResolve builtin. PromiseResolveWrapper
calls out to this TFS Builtin and is now exposed as Promise.resolve.
The thenFinally and catchFinally callbacks also call out to the
PromiseResolve TFS builtin.

Spec -- https://tc39.github.io/proposal-promise-finally/

Bug: v8:5967
Change-Id: I2ce89f14d3b149619d11e424b6e37062e466c4d5
Reviewed-on: https://chromium-review.googlesource.com/652026Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Commit-Queue: Sathya Gunasekaran <gsathya@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47898}
parent 52cdf06b
......@@ -2221,8 +2221,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
SimpleInstallFunction(promise_fun, "race", Builtins::kPromiseRace, 1, true);
SimpleInstallFunction(promise_fun, "resolve", Builtins::kPromiseResolve, 1,
true);
SimpleInstallFunction(promise_fun, "resolve",
Builtins::kPromiseResolveWrapper, 1, true);
SimpleInstallFunction(promise_fun, "reject", Builtins::kPromiseReject, 1,
true);
......
......@@ -754,7 +754,8 @@ namespace internal {
TFJ(PromiseHandle, 5, kValue, kHandler, kDeferredPromise, \
kDeferredOnResolve, kDeferredOnReject) \
/* ES #sec-promise.resolve */ \
TFJ(PromiseResolve, 1, kValue) \
TFJ(PromiseResolveWrapper, 1, kValue) \
TFS(PromiseResolve, kConstructor, kValue) \
/* ES #sec-promise.reject */ \
TFJ(PromiseReject, 1, kReason) \
TFJ(InternalPromiseReject, 3, kPromise, kReason, kDebugEvent) \
......
This diff is collapsed.
......@@ -57,11 +57,11 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
// This is used by the Promise.prototype.finally builtin to store
// onFinally callback and the Promise constructor.
// TODO(gsathya): Add extra slot for Promise constructor.
// TODO(gsathya): For native promises we can create a variant of
// this without extra space for the constructor to save memory.
enum PromiseFinallyContextSlot {
kOnFinallySlot = Context::MIN_CONTEXT_SLOTS,
kConstructorSlot,
kPromiseFinallyContextLength,
};
......@@ -156,6 +156,7 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
void InternalPromiseReject(Node* context, Node* promise, Node* value,
Node* debug_event);
std::pair<Node*, Node*> CreatePromiseFinallyFunctions(Node* on_finally,
Node* constructor,
Node* native_context);
Node* CreateValueThunkFunction(Node* value, Node* native_context);
......
......@@ -524,3 +524,84 @@ assertTrue(descriptor.configurable);
assertFalse(descriptor.enumerable);
assertEquals("finally", Promise.prototype.finally.name);
assertEquals(1, Promise.prototype.finally.length);
var count = 0;
class FooPromise extends Promise {
constructor(resolve, reject) {
count++;
return super(resolve, reject);
}
}
testAsync(assert => {
assert.plan(1);
count = 0;
new FooPromise(r => r()).finally(() => {}).then(() => {
assert.equals(6, count);
});
}, "finally/speciesconstructor");
testAsync(assert => {
assert.plan(1);
count = 0;
FooPromise.resolve().finally(() => {}).then(() => {
assert.equals(6, count);
})
}, "resolve/finally/speciesconstructor");
testAsync(assert => {
assert.plan(1);
count = 0;
FooPromise.reject().finally(() => {}).catch(() => {
assert.equals(6, count);
})
}, "reject/finally/speciesconstructor");
testAsync(assert => {
assert.plan(2);
class MyPromise extends Promise {
static get [Symbol.species]() { return Promise; }
}
var p = Promise
.resolve()
.finally(() => MyPromise.resolve());
assert.equals(true, p instanceof Promise);
assert.equals(false, p instanceof MyPromise);
}, "finally/Symbol.Species");
testAsync(assert => {
assert.plan(3);
let resolve;
let value = 0;
let p = new Promise(r => { resolve = r });
Promise.resolve()
.finally(() => {
return p;
})
.then(() => {
value = 1;
});
// This makes sure we take the fast path in PromiseResolve that just
// returns the promise it receives as value. If we had to create
// another wrapper promise, that would cause an additional tick in
// the microtask queue.
Promise.resolve()
// onFinally has run.
.then(() => { resolve(); })
// thenFinally has run.
.then(() => assert.equals(0, value))
// promise returned by .finally has been resolved.
.then(() => assert.equals(0, value))
// onFulfilled callback of .then() has run.
.then(() => assert.equals(1, value));
}, "PromiseResolve-ordering");
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