Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/InternalBytecode/01-Promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,15 @@
};

core.prototype.finally = function (f) {
// Per ECMA-262 §27.2.5.3 (Promise.prototype.finally), when onFinally
// is not callable it must be passed straight through to then(), so
// the resulting Promise simply mirrors the receiver. Without this
// guard, calling f() below throws "undefined is not a function" for
// the no-arg / undefined / null cases that V8, JavaScriptCore and
// SpiderMonkey all accept as passthrough.
if (typeof f !== 'function') {
return this.then(f, f);
}
return this.then(function (value) {
return core.resolve(f()).then(function () {
return value;
Expand Down
49 changes: 49 additions & 0 deletions test/hermes/promise-finally.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: %hermes -Xes6-promise %s | %FileCheck --match-full-lines %s
// RUN: %hermes -Xmicrotask-queue %s | %FileCheck --match-full-lines %s
// RUN: %hermesc -O -emit-binary -out %t.hbc %s && %hermes -Xes6-promise %t.hbc | %FileCheck --match-full-lines %s

// Regression test for Promise.prototype.finally with a non-callable
// onFinally. Per ECMA-262 §27.2.5.3, when IsCallable(onFinally) is false
// the value/rejection of the receiver must pass straight through.

print('promise-finally');
// CHECK-LABEL: promise-finally

// 1) No-arg call: value passes through.
Promise.resolve('ok').finally().then(function (v) {
print('no-arg:', v);
});
// CHECK-NEXT: no-arg: ok

// 2) Explicit undefined: value passes through.
Promise.resolve('ok').finally(undefined).then(function (v) {
print('undefined:', v);
});
// CHECK-NEXT: undefined: ok

// 3) null: value passes through.
Promise.resolve('ok').finally(null).then(function (v) {
print('null:', v);
});
// CHECK-NEXT: null: ok

// 4) Rejection passes through unchanged when onFinally is not callable.
Promise.reject(new Error('x')).finally().then(function () {
print('rejection: unexpected resolve');
}, function (e) {
print('rejection:', e.message);
});
// CHECK-NEXT: rejection: x

// 5) Sanity: callable onFinally still works (no regression).
Promise.resolve('ok').finally(function () {}).then(function (v) {
print('callable:', v);
});
// CHECK-NEXT: callable: ok