diff --git a/src/js/internal/fs/cp.ts b/src/js/internal/fs/cp.ts index f7db620f191e..cb30e7827478 100644 --- a/src/js/internal/fs/cp.ts +++ b/src/js/internal/fs/cp.ts @@ -30,7 +30,7 @@ const { const { dirname, isAbsolute, join, parse, resolve } = require("node:path"); const PromisePrototypeThen = $Promise.prototype.$then; -const PromiseReject = Promise.$reject; +const PromiseReject = Promise.$reject.bind(Promise); async function checkPaths(src, dest, opts) { if (opts.filter && !(await opts.filter(src, dest))) { diff --git a/src/js/internal/primordials.js b/src/js/internal/primordials.js index e8be2528d218..14dc8f46ab26 100644 --- a/src/js/internal/primordials.js +++ b/src/js/internal/primordials.js @@ -95,7 +95,7 @@ const arrayToSafePromiseIterable = (promises, mapFn) => new Promise((a, b) => PromisePrototypeThen.$call(mapFn == null ? promise : mapFn(promise, i), a, b)), ), ); -const PromiseAll = Promise.all; +const PromiseAll = Promise.all.bind(Promise); const PromiseResolve = Promise.$resolve.bind(Promise); const SafePromiseAll = (promises, mapFn) => PromiseAll(arrayToSafePromiseIterable(promises, mapFn)); // Shared scheduler for SafePromiseAllReturnVoid/ReturnArrayLike: `returnVal` diff --git a/src/js/node/readline.ts b/src/js/node/readline.ts index dc1e52d686f1..ceae253d098d 100644 --- a/src/js/node/readline.ts +++ b/src/js/node/readline.ts @@ -43,7 +43,7 @@ const { const internalGetStringWidth = $newCppFunction("stringWidth.cpp", "jsFunctionBunStringWidth", 1); -const PromiseReject = Promise.$reject; +const PromiseReject = Promise.$reject.bind(Promise); var isWritable; diff --git a/test/js/node/readline/readline_promises.node.test.ts b/test/js/node/readline/readline_promises.node.test.ts index 97f9d6f3af27..d96d458d2ec1 100644 --- a/test/js/node/readline/readline_promises.node.test.ts +++ b/test/js/node/readline/readline_promises.node.test.ts @@ -1,6 +1,8 @@ import { createTest } from "node-harness"; import { EventEmitter } from "node:events"; +import readline from "node:readline"; import readlinePromises from "node:readline/promises"; +import { promisify } from "node:util"; const { describe, it, expect, createDoneDotAll, createCallCheckCtx, assert } = createTest(import.meta.path); // ---------------------------------------------------------------------------- @@ -93,3 +95,63 @@ describe("readline/promises.createInterface()", () => { assert.strictEqual(rl.closed, true); }); }); + +describe("readline question() with an aborted signal", () => { + async function rejection(promise: Promise) { + return await promise.then( + () => { + throw new Error("question() resolved, expected it to reject"); + }, + (err: any) => err, + ); + } + + it("rejects instead of throwing synchronously", async () => { + const fi = new FakeInput(); + using rl = readlinePromises.createInterface({ input: fi, output: fi }); + + const reason = new Error("cancelled before the prompt"); + const promise = rl.question("Question?", { signal: AbortSignal.abort(reason) }); + expect(promise).toBeInstanceOf(Promise); + + const error = await rejection(promise); + expect({ name: error.name, code: error.code, cause: error.cause }).toEqual({ + name: "AbortError", + code: "ABORT_ERR", + cause: reason, + }); + // the prompt is never written when the signal is already aborted + expect(fi.output).toBe(""); + }); + + it("rejects instead of throwing synchronously through util.promisify", async () => { + const fi = new FakeInput(); + using rl = readline.createInterface({ input: fi, output: fi }); + const question = promisify(rl.question); + + const reason = new Error("cancelled before the prompt"); + const promise = question.call(rl, "Question?", { signal: AbortSignal.abort(reason) }); + expect(promise).toBeInstanceOf(Promise); + + const error = await rejection(promise); + expect({ name: error.name, code: error.code, cause: error.cause }).toEqual({ + name: "AbortError", + code: "ABORT_ERR", + cause: reason, + }); + expect(fi.output).toBe(""); + }); + + it("rejects when the signal aborts after the question was asked", async () => { + const fi = new FakeInput(); + using rl = readlinePromises.createInterface({ input: fi, output: fi }); + + const controller = new AbortController(); + const promise = rl.question("Question?", { signal: controller.signal }); + expect(fi.output).toBe("Question?"); + controller.abort(); + + const error = await rejection(promise); + expect({ name: error.name, code: error.code }).toEqual({ name: "AbortError", code: "ABORT_ERR" }); + }); +});