From 18ad0f13afeaabff4e035fddd9edc3d319152160 Mon Sep 17 00:00:00 2001 From: gsathya Date: Fri, 17 Feb 2017 14:10:28 -0800 Subject: [PATCH] [ESnext] Implement Promise.prototype.finally Adds five new TF builtins for the spec defined functions/closures. This follows mechanism similar to promise resolving functions approach where we store the closure variables in a custom context. Adds a new --harmony-promise-finally flag. BUG=v8:5967 Review-Url: https://codereview.chromium.org/2695753002 Cr-Commit-Position: refs/heads/master@{#43294} --- src/bootstrapper.cc | 62 ++ src/builtins/builtins-promise.cc | 217 +++++- src/builtins/builtins-promise.h | 21 + src/builtins/builtins.h | 5 + src/contexts.h | 8 + src/flag-definitions.h | 3 +- src/objects-printer.cc | 1 + .../harmony/promise-prototype-finally.js | 661 ++++++++++++++++++ 8 files changed, 976 insertions(+), 2 deletions(-) create mode 100644 test/mjsunit/harmony/promise-prototype-finally.js diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index e89427988e..4963e85c6b 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -3619,6 +3619,67 @@ void Genesis::InitializeGlobal_harmony_async_iteration() { factory()->async_iterator_symbol()); } +void Genesis::InitializeGlobal_harmony_promise_finally() { + if (!FLAG_harmony_promise_finally) return; + + Handle constructor(native_context()->promise_function()); + Handle prototype(JSObject::cast(constructor->instance_prototype())); + SimpleInstallFunction(prototype, "finally", Builtins::kPromiseFinally, 1, + true, DONT_ENUM); + + // The promise prototype map has changed because we added a property + // to prototype, so we update the saved map. + Handle prototype_map(prototype->map()); + Map::SetShouldBeFastPrototypeMap(prototype_map, true, isolate()); + native_context()->set_promise_prototype_map(*prototype_map); + + { + Handle code = + handle(isolate()->builtins()->builtin(Builtins::kPromiseThenFinally), + isolate()); + Handle info = factory()->NewSharedFunctionInfo( + factory()->empty_string(), code, false); + info->set_internal_formal_parameter_count(1); + info->set_length(1); + info->set_native(true); + native_context()->set_promise_then_finally_shared_fun(*info); + } + + { + Handle code = + handle(isolate()->builtins()->builtin(Builtins::kPromiseCatchFinally), + isolate()); + Handle info = factory()->NewSharedFunctionInfo( + factory()->empty_string(), code, false); + info->set_internal_formal_parameter_count(1); + info->set_length(1); + info->set_native(true); + native_context()->set_promise_catch_finally_shared_fun(*info); + } + + { + Handle code = handle( + isolate()->builtins()->builtin(Builtins::kPromiseValueThunkFinally), + isolate()); + Handle info = factory()->NewSharedFunctionInfo( + factory()->empty_string(), code, false); + info->set_internal_formal_parameter_count(0); + info->set_length(0); + native_context()->set_promise_value_thunk_finally_shared_fun(*info); + } + + { + Handle code = + handle(isolate()->builtins()->builtin(Builtins::kPromiseThrowerFinally), + isolate()); + Handle info = factory()->NewSharedFunctionInfo( + factory()->empty_string(), code, false); + info->set_internal_formal_parameter_count(0); + info->set_length(0); + native_context()->set_promise_thrower_finally_shared_fun(*info); + } +} + #ifdef V8_I18N_SUPPORT void Genesis::InitializeGlobal_datetime_format_to_parts() { if (!FLAG_datetime_format_to_parts) return; @@ -4153,6 +4214,7 @@ bool Genesis::InstallExperimentalNatives() { static const char* harmony_object_rest_spread_natives[] = {nullptr}; static const char* harmony_async_iteration_natives[] = {nullptr}; static const char* harmony_dynamic_import_natives[] = {nullptr}; + static const char* harmony_promise_finally_natives[] = {nullptr}; for (int i = ExperimentalNatives::GetDebuggerCount(); i < ExperimentalNatives::GetBuiltinsCount(); i++) { diff --git a/src/builtins/builtins-promise.cc b/src/builtins/builtins-promise.cc index 0f89d304e5..aac79f1e3f 100644 --- a/src/builtins/builtins-promise.cc +++ b/src/builtins/builtins-promise.cc @@ -438,7 +438,6 @@ Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen( Bind(&if_onresolvenotcallable); { - Isolate* isolate = this->isolate(); Node* const default_resolve_handler_symbol = HeapConstant( isolate->factory()->promise_default_resolve_handler_symbol()); var_on_resolve.Bind(default_resolve_handler_symbol); @@ -1563,5 +1562,221 @@ TF_BUILTIN(InternalPromiseReject, PromiseBuiltinsAssembler) { Return(UndefinedConstant()); } +Node* PromiseBuiltinsAssembler::CreatePromiseFinallyContext( + Node* on_finally, Node* native_context) { + Node* const context = + CreatePromiseContext(native_context, kOnFinallyContextLength); + StoreContextElementNoWriteBarrier(context, kOnFinallySlot, on_finally); + return context; +} + +std::pair PromiseBuiltinsAssembler::CreatePromiseFinallyFunctions( + Node* on_finally, Node* native_context) { + Node* const promise_context = + CreatePromiseFinallyContext(on_finally, native_context); + Node* const map = LoadContextElement( + native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); + Node* const then_finally_info = LoadContextElement( + native_context, Context::PROMISE_THEN_FINALLY_SHARED_FUN); + Node* const then_finally = AllocateFunctionWithMapAndContext( + map, then_finally_info, promise_context); + Node* const catch_finally_info = LoadContextElement( + native_context, Context::PROMISE_CATCH_FINALLY_SHARED_FUN); + Node* const catch_finally = AllocateFunctionWithMapAndContext( + map, catch_finally_info, promise_context); + return std::make_pair(then_finally, catch_finally); +} + +TF_BUILTIN(PromiseValueThunkFinally, PromiseBuiltinsAssembler) { + Node* const context = Parameter(3); + + Node* const value = LoadContextElement(context, kOnFinallySlot); + Return(value); +} + +Node* PromiseBuiltinsAssembler::CreateValueThunkFunctionContext( + Node* value, Node* native_context) { + Node* const context = + CreatePromiseContext(native_context, kOnFinallyContextLength); + StoreContextElementNoWriteBarrier(context, kOnFinallySlot, value); + return context; +} + +Node* PromiseBuiltinsAssembler::CreateValueThunkFunction(Node* value, + Node* native_context) { + Node* const value_thunk_context = + CreateValueThunkFunctionContext(value, native_context); + Node* const map = LoadContextElement( + native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); + Node* const value_thunk_info = LoadContextElement( + native_context, Context::PROMISE_VALUE_THUNK_FINALLY_SHARED_FUN); + Node* const value_thunk = AllocateFunctionWithMapAndContext( + map, value_thunk_info, value_thunk_context); + return value_thunk; +} + +TF_BUILTIN(PromiseThenFinally, PromiseBuiltinsAssembler) { + CSA_ASSERT_JS_ARGC_EQ(this, 1); + + Node* const value = Parameter(1); + Node* const context = Parameter(4); + + Node* const on_finally = LoadContextElement(context, kOnFinallySlot); + + // 2.a Let result be ? Call(onFinally, undefined). + Callable call_callable = CodeFactory::Call(isolate()); + Node* result = + CallJS(call_callable, context, on_finally, UndefinedConstant()); + + // 2.b Let promise be ! PromiseResolve( %Promise%, result). + Node* const promise = AllocateAndInitJSPromise(context); + InternalResolvePromise(context, promise, result); + + // 2.c Let valueThunk be equivalent to a function that returns value. + Node* native_context = LoadNativeContext(context); + Node* const value_thunk = CreateValueThunkFunction(value, native_context); + + // 2.d Let promiseCapability be ! NewPromiseCapability( %Promise%). + Node* const promise_capability = AllocateAndInitJSPromise(context, promise); + + // 2.e Return PerformPromiseThen(promise, valueThunk, undefined, + // promiseCapability). + InternalPerformPromiseThen(context, promise, value_thunk, UndefinedConstant(), + promise_capability, UndefinedConstant(), + UndefinedConstant()); + Return(promise_capability); +} + +TF_BUILTIN(PromiseThrowerFinally, PromiseBuiltinsAssembler) { + Node* const context = Parameter(3); + + Node* const reason = LoadContextElement(context, kOnFinallySlot); + CallRuntime(Runtime::kThrow, context, reason); + Return(UndefinedConstant()); +} + +Node* PromiseBuiltinsAssembler::CreateThrowerFunctionContext( + Node* reason, Node* native_context) { + Node* const context = + CreatePromiseContext(native_context, kOnFinallyContextLength); + StoreContextElementNoWriteBarrier(context, kOnFinallySlot, reason); + return context; +} + +Node* PromiseBuiltinsAssembler::CreateThrowerFunction(Node* reason, + Node* native_context) { + Node* const thrower_context = + CreateThrowerFunctionContext(reason, native_context); + Node* const map = LoadContextElement( + native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); + Node* const thrower_info = LoadContextElement( + native_context, Context::PROMISE_THROWER_FINALLY_SHARED_FUN); + Node* const thrower = + AllocateFunctionWithMapAndContext(map, thrower_info, thrower_context); + return thrower; +} + +TF_BUILTIN(PromiseCatchFinally, PromiseBuiltinsAssembler) { + CSA_ASSERT_JS_ARGC_EQ(this, 1); + + Node* const reason = Parameter(1); + Node* const context = Parameter(4); + + Node* const on_finally = LoadContextElement(context, kOnFinallySlot); + + // 2.a Let result be ? Call(onFinally, undefined). + Callable call_callable = CodeFactory::Call(isolate()); + Node* result = + CallJS(call_callable, context, on_finally, UndefinedConstant()); + + // 2.b Let promise be ! PromiseResolve( %Promise%, result). + Node* const promise = AllocateAndInitJSPromise(context); + InternalResolvePromise(context, promise, result); + + // 2.c Let thrower be equivalent to a function that throws reason. + Node* native_context = LoadNativeContext(context); + Node* const thrower = CreateThrowerFunction(reason, native_context); + + // 2.d Let promiseCapability be ! NewPromiseCapability( %Promise%). + Node* const promise_capability = AllocateAndInitJSPromise(context, promise); + + // 2.e Return PerformPromiseThen(promise, thrower, undefined, + // promiseCapability). + InternalPerformPromiseThen(context, promise, thrower, UndefinedConstant(), + promise_capability, UndefinedConstant(), + UndefinedConstant()); + Return(promise_capability); +} + +TF_BUILTIN(PromiseFinally, PromiseBuiltinsAssembler) { + CSA_ASSERT_JS_ARGC_EQ(this, 1); + + // 1. Let promise be the this value. + Node* const promise = Parameter(0); + Node* const on_finally = Parameter(1); + Node* const context = Parameter(4); + + // 2. If IsPromise(promise) is false, throw a TypeError exception. + ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE, + "Promise.prototype.finally"); + + Variable var_then_finally(this, MachineRepresentation::kTagged), + var_catch_finally(this, MachineRepresentation::kTagged); + + Label if_notcallable(this, Label::kDeferred), perform_finally(this); + + // 3. Let thenFinally be ! CreateThenFinally(onFinally). + // 4. Let catchFinally be ! CreateCatchFinally(onFinally). + GotoIf(TaggedIsSmi(on_finally), &if_notcallable); + Node* const on_finally_map = LoadMap(on_finally); + GotoIfNot(IsCallableMap(on_finally_map), &if_notcallable); + + Node* const native_context = LoadNativeContext(context); + Node* then_finally = nullptr; + Node* catch_finally = nullptr; + std::tie(then_finally, catch_finally) = + CreatePromiseFinallyFunctions(on_finally, native_context); + var_then_finally.Bind(then_finally); + var_catch_finally.Bind(catch_finally); + Goto(&perform_finally); + + Bind(&if_notcallable); + { + var_then_finally.Bind(on_finally); + var_catch_finally.Bind(on_finally); + Goto(&perform_finally); + } + + // 5. Return PerformPromiseThen(promise, valueThunk, undefined, + // promiseCapability). + Bind(&perform_finally); + Label if_nativepromise(this), if_custompromise(this, Label::kDeferred); + BranchIfFastPath(context, promise, &if_nativepromise, &if_custompromise); + + Bind(&if_nativepromise); + { + Node* deferred_promise = AllocateAndInitJSPromise(context, promise); + InternalPerformPromiseThen(context, promise, var_then_finally.value(), + var_catch_finally.value(), deferred_promise, + UndefinedConstant(), UndefinedConstant()); + Return(deferred_promise); + } + + Bind(&if_custompromise); + { + Isolate* isolate = this->isolate(); + Node* const then_str = HeapConstant(isolate->factory()->then_string()); + Callable getproperty_callable = CodeFactory::GetProperty(isolate); + Node* const then = + CallStub(getproperty_callable, context, promise, then_str); + Callable call_callable = CodeFactory::Call(isolate); + // 5. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). + Node* const result = + CallJS(call_callable, context, then, promise, var_then_finally.value(), + var_catch_finally.value()); + Return(result); + } +} + } // namespace internal } // namespace v8 diff --git a/src/builtins/builtins-promise.h b/src/builtins/builtins-promise.h index 9f7f456473..df011822ff 100644 --- a/src/builtins/builtins-promise.h +++ b/src/builtins/builtins-promise.h @@ -36,6 +36,18 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { kCapabilitiesContextLength, }; + // This is used by the PromiseThenFinally and PromiseCatchFinally + // builtins to store the onFinally in the onFinallySlot. + // + // This is also used by the PromiseValueThunkFinally to store the + // value in the onFinallySlot and PromiseThrowerFinally to store the + // reason in the onFinallySlot. + enum PromiseFinallyContextSlot { + kOnFinallySlot = Context::MIN_CONTEXT_SLOTS, + + kOnFinallyContextLength, + }; + explicit PromiseBuiltinsAssembler(CodeAssemblerState* state) : CodeStubAssembler(state) {} // These allocate and initialize a promise with pending state and @@ -115,6 +127,15 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { bool debug_event); void InternalPromiseReject(Node* context, Node* promise, Node* value, Node* debug_event); + std::pair CreatePromiseFinallyFunctions(Node* on_finally, + Node* native_context); + Node* CreatePromiseFinallyContext(Node* on_finally, Node* native_context); + + Node* CreateValueThunkFunction(Node* value, Node* native_context); + Node* CreateValueThunkFunctionContext(Node* value, Node* native_context); + + Node* CreateThrowerFunctionContext(Node* reason, Node* native_context); + Node* CreateThrowerFunction(Node* reason, Node* native_context); private: Node* AllocateJSPromise(Node* context); diff --git a/src/builtins/builtins.h b/src/builtins/builtins.h index f23bfd13cd..49a9f41bde 100644 --- a/src/builtins/builtins.h +++ b/src/builtins/builtins.h @@ -666,6 +666,11 @@ class Isolate; TFJ(PromiseResolve, 1) \ TFJ(PromiseReject, 1) \ TFJ(InternalPromiseReject, 3) \ + TFJ(PromiseFinally, 1) \ + TFJ(PromiseThenFinally, 1) \ + TFJ(PromiseCatchFinally, 1) \ + TFJ(PromiseValueThunkFinally, 0) \ + TFJ(PromiseThrowerFinally, 0) \ \ /* Proxy */ \ CPP(ProxyConstructor) \ diff --git a/src/contexts.h b/src/contexts.h index 530e29ac7c..5c88aa5108 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -290,6 +290,14 @@ enum ContextLookupFlags { V(PROMISE_RESOLVE_SHARED_FUN, SharedFunctionInfo, \ promise_resolve_shared_fun) \ V(PROMISE_REJECT_SHARED_FUN, SharedFunctionInfo, promise_reject_shared_fun) \ + V(PROMISE_THEN_FINALLY_SHARED_FUN, SharedFunctionInfo, \ + promise_then_finally_shared_fun) \ + V(PROMISE_CATCH_FINALLY_SHARED_FUN, SharedFunctionInfo, \ + promise_catch_finally_shared_fun) \ + V(PROMISE_VALUE_THUNK_FINALLY_SHARED_FUN, SharedFunctionInfo, \ + promise_value_thunk_finally_shared_fun) \ + V(PROMISE_THROWER_FINALLY_SHARED_FUN, SharedFunctionInfo, \ + promise_thrower_finally_shared_fun) \ V(PROMISE_PROTOTYPE_MAP_INDEX, Map, promise_prototype_map) \ V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \ V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \ diff --git a/src/flag-definitions.h b/src/flag-definitions.h index a5a1122cd4..ee5fc3d664 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -205,7 +205,8 @@ DEFINE_IMPLICATION(es_staging, move_object_start) V(harmony_function_tostring, "harmony Function.prototype.toString") \ V(harmony_class_fields, "harmony public fields in class literals") \ V(harmony_async_iteration, "harmony async iteration") \ - V(harmony_dynamic_import, "harmony dynamic import") + V(harmony_dynamic_import, "harmony dynamic import") \ + V(harmony_promise_finally, "harmony Promise.prototype.finally") // Features that are complete (but still behind --harmony/es-staging flag). #define HARMONY_STAGED(V) \ diff --git a/src/objects-printer.cc b/src/objects-printer.cc index dfeb3cb8ef..53c35e9c63 100644 --- a/src/objects-printer.cc +++ b/src/objects-printer.cc @@ -500,6 +500,7 @@ void JSPromise::JSPromisePrint(std::ostream& os) { // NOLINT os << "\n - fulfill_reactions = " << Brief(fulfill_reactions()); os << "\n - reject_reactions = " << Brief(reject_reactions()); os << "\n - has_handler = " << has_handler(); + os << "\n "; } void JSRegExp::JSRegExpPrint(std::ostream& os) { // NOLINT diff --git a/test/mjsunit/harmony/promise-prototype-finally.js b/test/mjsunit/harmony/promise-prototype-finally.js new file mode 100644 index 0000000000..eefce4b6ba --- /dev/null +++ b/test/mjsunit/harmony/promise-prototype-finally.js @@ -0,0 +1,661 @@ +// 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-promise-finally --allow-natives-syntax + +var asyncAssertsExpected = 0; + +function assertUnreachable() { + %AbortJS("Unreachable: failure"); +} + +function assertAsyncRan() { + ++asyncAssertsExpected; +} + +function assertAsync(b, s) { + if (b) { + print(s, "succeeded"); + } else { + %AbortJS(s + " FAILED!"); + } + --asyncAssertsExpected; +} + +function assertEqualsAsync(b, s) { + if (b === s) { + print(b, "===", s, "succeeded"); + } else { + %AbortJS(b + "===" + s + " FAILED!"); + } + --asyncAssertsExpected; +} + +function assertAsyncDone(iteration) { + var iteration = iteration || 0; + %EnqueueMicrotask(function() { + if (asyncAssertsExpected === 0) + assertAsync(true, "all"); + else if ( + iteration > 10 // Shouldn't take more. + ) + assertAsync(false, "all... " + asyncAssertsExpected); + else + assertAsyncDone(iteration + 1); + }); +} + +(function() { + assertThrows( + function() { + Promise.prototype.finally.call(5); + }, + TypeError + ); +})(); + +// resolve/finally/then +(function() { + Promise.resolve(3).finally().then( + x => { + assertEqualsAsync(3, x); + }, + assertUnreachable + ); + assertAsyncRan(); +})(); + +// reject/finally/then +(function() { + Promise.reject(3).finally().then(assertUnreachable, x => { + assertEqualsAsync(3, x); + }); + assertAsyncRan(); +})(); + +// resolve/finally-return-notcallable/then +(function() { + Promise.resolve(3).finally(2).then( + x => { + assertEqualsAsync(3, x); + }, + assertUnreachable + ); + assertAsyncRan(); +})(); + +// reject/finally-return-notcallable/then +(function() { + Promise.reject(3).finally(2).then( + assertUnreachable, e => { + assertEqualsAsync(3, e); + }); + assertAsyncRan(); +})(); + +// reject/finally/catch +(function() { + Promise.reject(3).finally().catch(reason => { + assertEqualsAsync(3, reason); + }); + assertAsyncRan(); +})(); + +// reject/finally/then/catch +(function() { + Promise.reject(3).finally().then(assertUnreachable).catch(reason => { + assertEqualsAsync(3, reason); + }); + assertAsyncRan(); +})(); + +// resolve/then/finally/then +(function() { + Promise.resolve(3) + .then(x => { + assertEqualsAsync(3, x); + return x; + }) + .finally() + .then( + x => { + assertEqualsAsync(3, x); + }, + assertUnreachable + ); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/catch/finally/then +(function() { + Promise.reject(3) + .catch(x => { + assertEqualsAsync(3, x); + return x; + }) + .finally() + .then( + x => { + assertEqualsAsync(3, x); + }, + assertUnreachable + ); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally-throw/then +(function() { + Promise.resolve(3) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + throw 1; + }) + .then(assertUnreachable, function onRejected(reason) { + assertEqualsAsync(1, reason); + }); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-throw/then +(function() { + Promise.reject(3) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + throw 1; + }) + .then(assertUnreachable, function onRejected(reason) { + assertEqualsAsync(1, reason); + }); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally-return/then +(function() { + Promise.resolve(3) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return 4; + }) + .then( + x => { + assertEqualsAsync(x, 3); + }, + assertUnreachable + ); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-return/then +(function() { + Promise.reject(3) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return 4; + }) + .then(assertUnreachable, x => { + assertEqualsAsync(x, 3); + }); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/catch-throw/finally-throw/then +(function() { + Promise.reject(3) + .catch(e => { + assertEqualsAsync(3, e); + throw e; + }) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + throw 4; + }) + .then(assertUnreachable, function onRejected(e) { + assertEqualsAsync(4, e); + }); + assertAsyncRan(); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/then-throw/finally-throw/then +(function() { + Promise.resolve(3) + .then(e => { + assertEqualsAsync(3, e); + throw e; + }) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + throw 4; + }) + .then(assertUnreachable, function onRejected(e) { + assertEqualsAsync(4, e); + }); + assertAsyncRan(); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally-return-rejected-promise/then +(function() { + Promise.resolve(3) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return Promise.reject(4); + }) + .then(assertUnreachable, e => { + assertEqualsAsync(4, e); + }); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-return-rejected-promise/then +(function() { + Promise.reject(3) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return Promise.reject(4); + }) + .then(assertUnreachable, e => { + assertEqualsAsync(4, e); + }); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally-return-resolved-promise/then +(function() { + Promise.resolve(3) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return Promise.resolve(4); + }) + .then( + x => { + assertEqualsAsync(3, x); + }, + assertUnreachable + ); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-return-resolved-promise/then +(function() { + Promise.reject(3) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return Promise.resolve(4); + }) + .then(assertUnreachable, e => { + assertEqualsAsync(3, e); + }); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-return-resolved-promise/then +(function() { + Promise.reject(3) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return Promise.resolve(4); + }) + .then(assertUnreachable, e => { + assertEqualsAsync(3, e); + }); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally-thenable-resolve/then +(function() { + var thenable = { + then: function(onResolve, onReject) { + onResolve(5); + } + }; + + Promise.resolve(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return thenable; + }) + .then( + x => { + assertEqualsAsync(5, x); + }, + assertUnreachable + ); + + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-thenable-resolve/then +(function() { + var thenable = { + then: function(onResolve, onReject) { + onResolve(1); + } + }; + + Promise.reject(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return thenable; + }) + .then(assertUnreachable, e => { + assertEqualsAsync(5, e); + }); + + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-thenable-reject/then +(function() { + var thenable = { + then: function(onResolve, onReject) { + onReject(1); + } + }; + + Promise.reject(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return thenable; + }) + .then(assertUnreachable, e => { + assertEqualsAsync(1, e); + }); + + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally-thenable-reject/then +(function() { + var thenable = { + then: function(onResolve, onReject) { + onReject(1); + } + }; + + Promise.resolve(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return thenable; + }) + .then(assertUnreachable, e => { + assertEqualsAsync(1, e); + }); + + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally/finally/then +(function() { + Promise.resolve(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .then( + x => { + assertEqualsAsync(5, x); + }, + assertUnreachable + ); + + assertAsyncRan(); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally-throw/finally/then +(function() { + Promise.resolve(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + throw 1; + }) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .then(assertUnreachable, e => { + assertEqualsAsync(1, e); + }); + + assertAsyncRan(); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally-return-rejected-promise/finally/then +(function() { + Promise.resolve(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return Promise.reject(1); + }) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .then(assertUnreachable, e => { + assertEqualsAsync(1, e); + }); + + assertAsyncRan(); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally/finally/then +(function() { + Promise.reject(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .then(assertUnreachable, e => { + assertEqualsAsync(5, e); + }); + + assertAsyncRan(); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-throw/finally/then +(function() { + Promise.reject(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + throw 1; + }) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .then(assertUnreachable, e => { + assertEqualsAsync(1, e); + }); + + assertAsyncRan(); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-return-rejected-promise/finally/then +(function() { + Promise.reject(5) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return Promise.reject(1); + }) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .then(assertUnreachable, e => { + assertEqualsAsync(1, e); + }); + + assertAsyncRan(); + assertAsyncRan(); + assertAsyncRan(); +})(); + +// resolve/finally-deferred-resolve/then +(function() { + var resolve, reject; + var deferred = new Promise((x, y) => { + resolve = x; + reject = y; + }); + Promise.resolve(1) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return deferred; + }) + .then( + x => { + assertEqualsAsync(1, x); + }, + assertUnreachable + ); + + assertAsyncRan(); + assertAsyncRan(); + + resolve(5); +})(); + +// resolve/finally-deferred-reject/then +(function() { + var resolve, reject; + var deferred = new Promise((x, y) => { + resolve = x; + reject = y; + }); + Promise.resolve(1) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + return deferred; + }) + .then(assertUnreachable, e => { + assertEqualsAsync(5, e); + }); + + assertAsyncRan(); + assertAsyncRan(); + + reject(5); +})(); + +// all/finally/then +(function() { + var resolve, reject; + var deferred = new Promise((x, y) => { + resolve = x; + reject = y; + }); + + Promise.all([deferred]) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .then( + ([x]) => { + assertEqualsAsync(1, x); + }, + assertUnreachable + ); + + assertAsyncRan(); + assertAsyncRan(); + + resolve(1); +})(); + +// race/finally/then +(function() { + var resolve, reject; + var d1 = new Promise((x, y) => { + resolve = x; + reject = y; + }); + var d2 = new Promise((x, y) => { + resolve = x; + reject = y; + }); + + Promise.race([d1, d2]) + .finally(function onFinally() { + assertEqualsAsync(0, arguments.length); + }) + .then( + x => { + assertEqualsAsync(1, x); + }, + assertUnreachable + ); + + assertAsyncRan(); + assertAsyncRan(); + + resolve(1); +})(); + +// resolve/finally-customthen/then +(function() { + class MyPromise extends Promise { + then(onFulfilled, onRejected) { + assertEqualsAsync(5, onFulfilled); + assertEqualsAsync(5, onRejected); + return super.then(onFulfilled, onRejected); + } + } + + MyPromise.resolve(3).finally(5); + + assertAsyncRan(); + assertAsyncRan(); +})(); + +// reject/finally-customthen/then +(function() { + class MyPromise extends Promise { + then(onFulfilled, onRejected) { + assertEqualsAsync(5, onFulfilled); + assertEqualsAsync(5, onRejected); + return super.then(onFulfilled, onRejected); + } + } + + MyPromise.reject(3).finally(5); + + assertAsyncRan(); + assertAsyncRan(); +})(); + +var descriptor = Object.getOwnPropertyDescriptor(Promise.prototype, 'finally'); +assertTrue(descriptor.writable); +assertTrue(descriptor.configurable); +assertFalse(descriptor.enumerable); +assertEquals("finally", Promise.prototype.finally.name); +assertEquals(1, Promise.prototype.finally.length); + +assertAsyncDone();