Skip to content

Commit 8b1850a

Browse files
committed
fix(async): abortable should prevent uncaught error when promise is rejected
1 parent d244cb9 commit 8b1850a

File tree

2 files changed

+49
-6
lines changed

2 files changed

+49
-6
lines changed

async/abortable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ function abortablePromise<T>(
127127
p: Promise<T>,
128128
signal: AbortSignal,
129129
): Promise<T> {
130-
if (signal.aborted) return Promise.reject(signal.reason);
131130
const { promise, reject } = Promise.withResolvers<never>();
132131
const abort = () => reject(signal.reason);
132+
if (signal.aborted) abort();
133133
signal.addEventListener("abort", abort, { once: true });
134134
return Promise.race([promise, p]).finally(() => {
135135
signal.removeEventListener("abort", abort);

async/abortable_test.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,26 @@ import { assertEquals, assertRejects } from "@std/assert";
33
import { abortable } from "./abortable.ts";
44
import { delay } from "./delay.ts";
55

6-
Deno.test("abortable() handles promise", async () => {
6+
Deno.test("abortable() handles resolved promise", async () => {
77
const c = new AbortController();
88
const { promise, resolve } = Promise.withResolvers<string>();
99
setTimeout(() => resolve("Hello"), 10);
1010
const result = await abortable(promise, c.signal);
1111
assertEquals(result, "Hello");
1212
});
1313

14-
Deno.test("abortable() handles promise with aborted signal after delay", async () => {
14+
Deno.test("abortable() handles rejected promise", async () => {
15+
const c = new AbortController();
16+
const { promise, reject } = Promise.withResolvers<string>();
17+
setTimeout(() => reject(new Error("This is my error")), 10);
18+
await assertRejects(
19+
() => abortable(promise, c.signal),
20+
Error,
21+
"This is my error",
22+
);
23+
});
24+
25+
Deno.test("abortable() handles resolved promise with aborted signal after delay", async () => {
1526
const c = new AbortController();
1627
const { promise, resolve } = Promise.withResolvers<string>();
1728
setTimeout(() => resolve("Hello"), 10);
@@ -25,7 +36,22 @@ Deno.test("abortable() handles promise with aborted signal after delay", async (
2536
await delay(5); // wait for the promise to resolve
2637
});
2738

28-
Deno.test("abortable() handles promise with aborted signal after delay with reason", async () => {
39+
Deno.test("abortable() handles rejected promise with aborted signal after delay", async () => {
40+
const c = new AbortController();
41+
const { promise, reject } = Promise.withResolvers<string>();
42+
setTimeout(() => reject(new Error("This is my error")), 10);
43+
setTimeout(() => c.abort(), 5);
44+
const error = await assertRejects(
45+
() => abortable(promise, c.signal),
46+
DOMException,
47+
"The signal has been aborted",
48+
);
49+
assertEquals(error.name, "AbortError");
50+
await delay(5); // wait for the promise to reject
51+
// an uncaught error should not occur
52+
});
53+
54+
Deno.test("abortable() handles resolved promise with aborted signal after delay with reason", async () => {
2955
const c = new AbortController();
3056
const { promise, resolve } = Promise.withResolvers<string>();
3157
setTimeout(() => resolve("Hello"), 10);
@@ -38,7 +64,7 @@ Deno.test("abortable() handles promise with aborted signal after delay with reas
3864
await delay(5); // wait for the promise to resolve
3965
});
4066

41-
Deno.test("abortable() handles promise with already aborted signal", async () => {
67+
Deno.test("abortable() handles resolved promise with already aborted signal", async () => {
4268
const c = new AbortController();
4369
const { promise, resolve } = Promise.withResolvers<string>();
4470
setTimeout(() => resolve("Hello"), 10);
@@ -54,7 +80,24 @@ Deno.test("abortable() handles promise with already aborted signal", async () =>
5480
await delay(10); // wait for the promise to resolve
5581
});
5682

57-
Deno.test("abortable() handles promise with already aborted signal with reason", async () => {
83+
Deno.test("abortable() handles rejected promise with already aborted signal", async () => {
84+
const c = new AbortController();
85+
const { promise, reject } = Promise.withResolvers<string>();
86+
setTimeout(() => reject(new Error("This is my error")), 10);
87+
c.abort();
88+
const error = await assertRejects(
89+
async () => {
90+
await abortable(promise, c.signal);
91+
},
92+
DOMException,
93+
"The signal has been aborted",
94+
);
95+
assertEquals(error.name, "AbortError");
96+
await delay(10); // wait for the promise to reject
97+
// an uncaught error should not occur
98+
});
99+
100+
Deno.test("abortable() handles resolved promise with already aborted signal and reason", async () => {
58101
const c = new AbortController();
59102
const { promise, resolve } = Promise.withResolvers<string>();
60103
setTimeout(() => resolve("Hello"), 10);

0 commit comments

Comments
 (0)