From 4b3162b6bbaabbb02a186dad05f0e9e7718ab10a Mon Sep 17 00:00:00 2001 From: pilaoda <793493083@qq.com> Date: Thu, 28 May 2026 18:35:45 +0800 Subject: [PATCH 1/2] (lualib): update Promise.prototype.finally to correctly handle onFinally errors and rejected --- src/lualib/Promise.ts | 21 +++++++--------- test/unit/builtins/promise.spec.ts | 40 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/lualib/Promise.ts b/src/lualib/Promise.ts index 187e5380d..e8121f1b3 100644 --- a/src/lualib/Promise.ts +++ b/src/lualib/Promise.ts @@ -125,20 +125,17 @@ export class __TS__Promise implements Promise { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally // Delegates to .then() so that a new Promise is returned (per ES spec ยง27.2.5.3) // and the original fulfillment value / rejection reason is preserved. + // reference: https://github.com/tc39/proposal-promise-finally/blob/fd934c0b42d59bf8d9446e737ba14d50a9067216/polyfill.js#L34-L41 public finally(onFinally?: () => void): Promise { + if (typeof onFinally !== "function") { + return this.then(onFinally, onFinally); + } return this.then( - onFinally - ? (value: T): T => { - onFinally(); - return value; - } - : undefined, - onFinally - ? (reason: any): never => { - onFinally(); - throw reason; - } - : undefined + x => new __TS__Promise(resolve => resolve(onFinally())).then(() => x), + e => + new __TS__Promise(resolve => resolve(onFinally())).then(() => { + throw e; + }) ); } diff --git a/test/unit/builtins/promise.spec.ts b/test/unit/builtins/promise.spec.ts index f323b030f..63272c8ab 100644 --- a/test/unit/builtins/promise.spec.ts +++ b/test/unit/builtins/promise.spec.ts @@ -1370,4 +1370,44 @@ describe("Promise.finally", () => { return result.value; `.expectToEqual(99); }); + + test("finally throw overrides fulfillment value", () => { + util.testModule` + let result: any; + Promise.resolve("ok") + .finally(() => { throw "finally-error"; }) + .then(v => { result = v; }, e => { result = e; }); + export const output = result; + `.expectToEqual({ output: "finally-error" }); + }); + + test("finally throw overrides rejection reason", () => { + util.testModule` + let result: any; + Promise.reject("original") + .finally(() => { throw "finally-error"; }) + .then(v => { result = v; }, e => { result = e; }); + export const output = result; + `.expectToEqual({ output: "finally-error" }); + }); + + test("finally returning rejected promise overrides fulfillment", () => { + util.testModule` + let result: any; + Promise.resolve("ok") + .finally(() => Promise.reject("finally-rejected") as any) + .then(v => { result = v; }, e => { result = e; }); + export const output = result; + `.expectToEqual({ output: "finally-rejected" }); + }); + + test("finally returning rejected promise overrides rejection", () => { + util.testModule` + let result: any; + Promise.reject("original") + .finally(() => Promise.reject("finally-rejected") as any) + .then(v => { result = v; }, e => { result = e; }); + export const output = result; + `.expectToEqual({ output: "finally-rejected" }); + }); }); From 71fc6cc669a455490fbf8da215c78df9d1a60c90 Mon Sep 17 00:00:00 2001 From: pilaoda <793493083@qq.com> Date: Thu, 28 May 2026 18:46:38 +0800 Subject: [PATCH 2/2] (test): add tests for async/await try-catch-finally re-throw cases --- test/unit/builtins/async-await.spec.ts | 98 ++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/test/unit/builtins/async-await.spec.ts b/test/unit/builtins/async-await.spec.ts index 2929832eb..1c5d23018 100644 --- a/test/unit/builtins/async-await.spec.ts +++ b/test/unit/builtins/async-await.spec.ts @@ -1126,6 +1126,104 @@ describe("try/catch in async function", () => { .setTsHeader(promiseTestLib) .expectToEqual(["finally", "ok"]); }); + + test.each([0, 1, 2])("async re-throw (%p)", i => { + util.testModule` + const i: number = ${i}; + async function foo() { + try { + try { + if (i === 0) { throw "z"; } + } catch (e) { + throw "a"; + } finally { + if (i === 1) { throw "b"; } + } + } catch (e) { + throw (e as string).toUpperCase(); + } finally { + throw "C"; + } + } + export let result: string = "x"; + async function run() { + try { + await foo(); + } catch (e) { + result = (e as string)[(e as string).length - 1]; + } + } + run(); + `.expectToEqual({ result: "C" }); + }); + + test("async: catch re-throws, finally still runs", () => { + util.testModule` + const foo = async () => { + throw "original"; + }; + + let finallyCalled = false; + let caughtError: any = false; + + const run = async () => { + try { + await foo(); + } catch (e) { + throw "re-thrown: " + e; + } finally { + finallyCalled = true; + } + }; + + run().catch(e => { caughtError = e; }); + + export const result = { finallyCalled, caughtError }; + `.expectToEqual({ + result: { + finallyCalled: true, + caughtError: "re-thrown: original", + }, + }); + }); + + test("async: finally throw overrides catch throw", () => { + util.testModule` + const run = async () => { + try { + throw "try-error"; + } catch (e) { + throw "catch-error"; + } finally { + throw "finally-error"; + } + }; + + let caughtError: any = false; + run().catch(e => { caughtError = e; }); + + export const result = caughtError; + `.expectToEqual({ result: "finally-error" }); + }); + + test("async: finally return overrides catch throw", () => { + util.testFunction` + async function run() { + try { + throw "try-error"; + } catch (e) { + throw "catch-error"; + } finally { + return "finally-return"; + } + } + + let result: any; + run().then(v => { result = v; }).catch(e => { result = "rejected: " + e; }); + + return result; + `.expectToEqual("finally-return"); + }); }); describe("async generators are unsupported", () => {