From b34a8a38c649275e47845fd7415477c539c47506 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 6 Apr 2025 23:36:40 +1200 Subject: [PATCH] LibJS: Do not generate implicit await for async functions Despite the comment in our source, as far as I can tell from my testing, other engines do not appear to do this. For example, the result of the following test now matches node and LibJS by logging `0` calls. ``` async function myFunction() { let calls = 0; function makeConstructorObservable(promise) { Object.defineProperty(promise, "constructor", { get() { calls++; return Promise; }, }); return promise; } async function test() { try { return makeConstructorObservable(Promise.reject(1)); } catch { return 2; } } test().catch(() => { }); console.log(calls); }; myFunction() ``` We regress in one test262 Array.fromAsync test, but as far as I can tell this is due to some unrelated issue in the engine. Fixes #3251 --- Libraries/LibJS/Bytecode/ASTCodegen.cpp | 7 +------ Libraries/LibJS/Tests/syntax/async-await.js | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 85d9f61f2e2..ae15e368a68 100644 --- a/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -1852,12 +1852,7 @@ Bytecode::CodeGenerationErrorOr> ReturnStatement::genera return_value = TRY(m_argument->generate_bytecode(generator)).value(); // 3. If GetGeneratorKind() is async, set exprValue to ? Await(exprValue). - // Spec Issue?: The spec doesn't seem to do implicit await on explicit return for async functions, but does for - // async generators. However, the major engines do so, and this is observable via constructor lookups - // on Promise objects and custom thenables. - // See: https://tc39.es/ecma262/#sec-asyncblockstart - // c. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done. - if (generator.is_in_async_function()) { + if (generator.is_in_async_generator_function()) { auto received_completion = generator.allocate_register(); auto received_completion_type = generator.allocate_register(); auto received_completion_value = generator.allocate_register(); diff --git a/Libraries/LibJS/Tests/syntax/async-await.js b/Libraries/LibJS/Tests/syntax/async-await.js index 1d5c4bcb486..7ed67de988a 100644 --- a/Libraries/LibJS/Tests/syntax/async-await.js +++ b/Libraries/LibJS/Tests/syntax/async-await.js @@ -202,7 +202,7 @@ describe("await cannot be used in class static init blocks", () => { }); describe("await thenables", () => { - test("async returning a thanable variable without fulfilling", () => { + test("async returning a thenable variable without fulfilling", () => { let isCalled = false; const obj = { then() { @@ -216,7 +216,7 @@ describe("await thenables", () => { expect(isCalled).toBe(true); }); - test("async returning a thanable variable that fulfills", () => { + test("async returning a thenable variable that fulfills", () => { let isCalled = false; const obj = { then(fulfill) { @@ -240,7 +240,7 @@ describe("await thenables", () => { }); f(); runQueuedPromiseJobs(); - expect(isCalled).toBe(true); + expect(isCalled).toBe(false); }); test("async returning a thenable directly that fulfills", () => { @@ -253,7 +253,7 @@ describe("await thenables", () => { }); f(); runQueuedPromiseJobs(); - expect(isCalled).toBe(true); + expect(isCalled).toBe(false); }); }); @@ -279,15 +279,10 @@ describe("await observably looks up constructor of Promise objects increasing ca try { await makeConstructorObservable(Promise.reject(3)); } catch {} - try { - return makeConstructorObservable(Promise.reject(1)); - } catch { - return 2; - } } test(); runQueuedPromiseJobs(); - expect(calls).toBe(4); + expect(calls).toBe(3); }); describe("await observably looks up constructor of Promise objects not increasing call count", () => { @@ -306,6 +301,11 @@ describe("await observably looks up constructor of Promise objects not increasin await makeConstructorObservable(new Boolean(true)); await makeConstructorObservable({}); await makeConstructorObservable(new Number(2)); + try { + return makeConstructorObservable(Promise.reject(1)); + } catch { + return 2; + } } test(); runQueuedPromiseJobs();