Skip to content

Commit b50a55b

Browse files
author
João Dias
committed
feat(make-cancelable): added signal as part of the returning object
1 parent dc930f9 commit b50a55b

File tree

2 files changed

+97
-1
lines changed

2 files changed

+97
-1
lines changed

cypress/test/functions/utlities/make-cancelable.cy.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ describe("makeCancelable", () => {
3636
await expectAbort(cancelable);
3737
});
3838

39-
it("should return an object with a promise, cancel function, and isCancelled function", () => {
39+
it("should return an object with a promise, cancel function, isCancelled function, and signal", () => {
4040
const promise = wait(25);
4141
const cancelable = makeCancelable(promise);
4242
expect(cancelable).to.be.an("object");
4343
expect(cancelable.promise).to.be.a("promise");
4444
expect(cancelable.cancel).to.be.a("function");
4545
expect(cancelable.isCancelled).to.be.a("function");
46+
expect(cancelable.signal).to.be.an("AbortSignal");
4647
});
4748

4849
it("should resolve the promise if not cancelled", async () => {
@@ -138,4 +139,91 @@ describe("makeCancelable", () => {
138139
}
139140
}
140141
});
142+
143+
describe("signal property", () => {
144+
it("should be an AbortSignal instance", () => {
145+
const promise = wait(25);
146+
const cancelable = makeCancelable(promise);
147+
expect(cancelable.signal).to.be.instanceOf(AbortSignal);
148+
});
149+
150+
it("should reflect cancellation state", () => {
151+
const promise = wait(25);
152+
const cancelable = makeCancelable(promise);
153+
expect(cancelable.signal.aborted).to.be.false;
154+
cancelable.cancel();
155+
expect(cancelable.signal.aborted).to.be.true;
156+
});
157+
158+
it("should be usable with fetch", async () => {
159+
const promise = wait(25);
160+
const cancelable = makeCancelable(promise);
161+
162+
// Simulate a fetch request that would use the signal
163+
const fetchPromise = new Promise((resolve, reject) => {
164+
cancelable.signal.addEventListener("abort", () => {
165+
reject(new AbortPromiseError());
166+
});
167+
setTimeout(resolve, 50);
168+
});
169+
170+
cancelable.cancel();
171+
try {
172+
await fetchPromise;
173+
throw new Error("Promise should have been rejected");
174+
} catch (error: unknown) {
175+
expect(error).to.be.instanceOf(AbortPromiseError);
176+
}
177+
});
178+
179+
it("should be usable with multiple promises", async () => {
180+
const promise1 = wait(25);
181+
const cancelable1 = makeCancelable(promise1);
182+
183+
// Simulate multiple operations using the same signal
184+
const operation1 = new Promise((resolve, reject) => {
185+
cancelable1.signal.addEventListener("abort", () => reject(new AbortPromiseError()));
186+
setTimeout(resolve, 50);
187+
});
188+
189+
const operation2 = new Promise((resolve, reject) => {
190+
cancelable1.signal.addEventListener("abort", () => reject(new AbortPromiseError()));
191+
setTimeout(resolve, 50);
192+
});
193+
194+
cancelable1.cancel();
195+
196+
try {
197+
await operation1;
198+
throw new Error("Promise should have been rejected");
199+
} catch (error: unknown) {
200+
expect(error).to.be.instanceOf(AbortPromiseError);
201+
}
202+
203+
try {
204+
await operation2;
205+
throw new Error("Promise should have been rejected");
206+
} catch (error: unknown) {
207+
expect(error).to.be.instanceOf(AbortPromiseError);
208+
}
209+
});
210+
211+
it("should handle custom abort reason", async () => {
212+
const promise = wait(25);
213+
const cancelable = makeCancelable(promise);
214+
const reason = "Custom abort reason";
215+
216+
cancelable.cancel(reason);
217+
218+
try {
219+
await cancelable.promise;
220+
throw new Error("Promise should have been rejected");
221+
} catch (error: unknown) {
222+
expect(error).to.be.instanceOf(AbortPromiseError);
223+
if (error instanceof AbortPromiseError) {
224+
expect(error.message).to.equal("Promise was aborted");
225+
}
226+
}
227+
});
228+
});
141229
});

src/functions/utilities/make-cancelable.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ export interface MakeCancelablePromise<T = unknown> {
3434
* Checks whether the promise has been cancelled
3535
*/
3636
isCancelled: () => boolean;
37+
38+
/**
39+
* The AbortSignal object that can be used to check if the promise has been cancelled.
40+
* This signal can be used to coordinate cancellation across multiple promises or network requests
41+
* by passing it to other abortable operations that should be cancelled together.
42+
*/
43+
signal: AbortSignal;
3744
}
3845

3946
/**
@@ -142,5 +149,6 @@ export function makeCancelable<T = unknown>(promise: Promise<T>): MakeCancelable
142149
isCancelled() {
143150
return controller.signal.aborted;
144151
},
152+
signal: controller.signal,
145153
};
146154
}

0 commit comments

Comments
 (0)