From 899be7cedbef6678280d56a4725f2697f808bbb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Tue, 12 Nov 2019 17:02:16 +0100 Subject: Issue #1283 - Implement Promise.prototype.finally() This resolves #1283. --- js/src/builtin/Promise.cpp | 8 ++++ js/src/builtin/Promise.h | 8 ++++ js/src/builtin/Promise.js | 69 +++++++++++++++++++++++++++++ js/src/vm/SelfHosting.cpp | 19 ++++++++ js/xpconnect/tests/chrome/test_xrayToJS.xul | 2 +- 5 files changed, 105 insertions(+), 1 deletion(-) (limited to 'js') diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index ec7845e89..1d068f8c6 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -2006,6 +2006,13 @@ CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue ar return promise; } +MOZ_MUST_USE JSObject* +js::PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value) +{ + RootedValue C(cx, ObjectValue(*constructor)); + return CommonStaticResolveRejectImpl(cx, C, value, ResolveMode); +} + /** * ES2016, 25.4.4.4, Promise.reject. */ @@ -2739,6 +2746,7 @@ CreatePromisePrototype(JSContext* cx, JSProtoKey key) static const JSFunctionSpec promise_methods[] = { JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0), JS_FN("then", Promise_then, 2, 0), + JS_SELF_HOSTED_FN("finally", "Promise_finally", 1, 0), JS_FS_END }; diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index c76dc358c..6a6453e46 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -128,6 +128,14 @@ OriginalPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled, HandleValue onRejected, MutableHandleObject dependent, bool createDependent); +/** + * PromiseResolve ( C, x ) + * + * The abstract operation PromiseResolve, given a constructor and a value, + * returns a new promise resolved with that value. + */ +MOZ_MUST_USE JSObject* +PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value); MOZ_MUST_USE PromiseObject* CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal); diff --git a/js/src/builtin/Promise.js b/js/src/builtin/Promise.js index 704cbab0b..91a1e1f56 100644 --- a/js/src/builtin/Promise.js +++ b/js/src/builtin/Promise.js @@ -14,3 +14,72 @@ function Promise_catch(onRejected) { // Steps 1-2. return callContentFunction(this.then, this, undefined, onRejected); } + +// Promise.prototype.finally(onFinally) +// See https://tc39.es/proposal-promise-finally/ +function Promise_finally(onFinally) { + // Step 1. + var promise = this; + + // Step 2. + if (!IsObject(promise)) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Promise", "finally", "value"); + + // Step 3. + var C = SpeciesConstructor(promise, GetBuiltinConstructor("Promise")); + + // Step 4. + assert(IsConstructor(C), "SpeciesConstructor returns a constructor function"); + + // Steps 5-6. + var thenFinally, catchFinally; + if (!IsCallable(onFinally)) { + thenFinally = onFinally; + catchFinally = onFinally; + } else { + // ThenFinally Function. + // The parentheses prevent the inferring of a function name. + (thenFinally) = function(value) { + // Steps 1-2 (implicit). + + // Step 3. + var result = onFinally(); + + // Steps 4-5 (implicit). + + // Step 6. + var promise = PromiseResolve(C, result); + + // Step 7. + // FIXME: spec issue - "be equivalent to a function that" is not a defined spec term. + // https://github.com/tc39/ecma262/issues/933 + + // Step 8. + return callContentFunction(promise.then, promise, function() { return value; }); + }; + + // CatchFinally Function. + // The parentheses prevent the inferring of a function name. + (catchFinally) = function(reason) { + // Steps 1-2 (implicit). + + // Step 3. + var result = onFinally(); + + // Steps 4-5 (implicit). + + // Step 6. + var promise = PromiseResolve(C, result); + + // Step 7. + // FIXME: spec issue - "be equivalent to a function that" is not a defined spec term. + // https://github.com/tc39/ecma262/issues/933 + + // Step 8. + return callContentFunction(promise.then, promise, function() { throw reason; }); + }; + } + + // Step 7. + return callContentFunction(promise.then, promise, thenFinally, catchFinally); +} diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 82d2cde64..833410465 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2102,6 +2102,21 @@ intrinsic_ModuleNamespaceExports(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +intrinsic_PromiseResolve(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + + RootedObject constructor(cx, &args[0].toObject()); + JSObject* promise = js::PromiseResolve(cx, constructor, args[1]); + if (!promise) + return false; + + args.rval().setObject(*promise); + return true; +} + // The self-hosting global isn't initialized with the normal set of builtins. // Instead, individual C++-implemented functions that're required by // self-hosted code are defined as global functions. Accessing these @@ -2498,6 +2513,10 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0), JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0), + JS_FN("IsPromiseObject", intrinsic_IsInstanceOfBuiltin, 1, 0), + JS_FN("CallPromiseMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), + JS_FN("PromiseResolve", intrinsic_PromiseResolve, 2, 0), + JS_FS_END }; diff --git a/js/xpconnect/tests/chrome/test_xrayToJS.xul b/js/xpconnect/tests/chrome/test_xrayToJS.xul index 495b99607..38f3f447d 100644 --- a/js/xpconnect/tests/chrome/test_xrayToJS.xul +++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul @@ -243,7 +243,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681 "$`", "$'", Symbol.species]) gPrototypeProperties['Promise'] = - ["constructor", "catch", "then", Symbol.toStringTag]; + ["constructor", "catch", "then", "finally", Symbol.toStringTag]; gConstructorProperties['Promise'] = constructorProps(["resolve", "reject", "all", "race", Symbol.species]); -- cgit v1.2.3