diff --git a/src/controllers/episodes/anify.ts b/src/controllers/episodes/anify.ts index c8e4fd8..c3cb387 100644 --- a/src/controllers/episodes/anify.ts +++ b/src/controllers/episodes/anify.ts @@ -1,3 +1,4 @@ +import { PromiseTimedOutError, promiseTimeout } from "~/libs/promiseTimeout"; import { sortByProperty } from "~/libs/sortByProperty"; import type { EpisodesResponse } from "./episode"; @@ -11,22 +12,18 @@ export async function getEpisodesFromAnify( } let response: AnifyEpisodesResponse[] | null = null; + const abortController = new AbortController(); try { - const abortController = new AbortController(); - response = await Promise.race([ + response = await promiseTimeout( fetch(`https://api.anify.tv/episodes/${aniListId}`, { signal: abortController.signal, }).then((res) => res.json()), - // set a limit of 30 seconds - new Promise((resolve) => setTimeout(resolve, 30 * 1000)).then(() => { - abortController.abort("Loading episodes from Anify timed out"); - console.error( - `Loading episodes from Anify timed out; aniListId: ${aniListId}`, - ); - return null; - }), - ]); + 30 * 1000, + ); } catch (e) { + if (e instanceof PromiseTimedOutError) { + abortController.abort("Loading episodes from Anify timed out"); + } console.error( new Error( `Error trying to load episodes from anify; aniListId: ${aniListId}`, diff --git a/src/libs/promiseTimeout.spec.ts b/src/libs/promiseTimeout.spec.ts new file mode 100644 index 0000000..a2bae59 --- /dev/null +++ b/src/libs/promiseTimeout.spec.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "bun:test"; + +import { PromiseTimedOutError, promiseTimeout } from "./promiseTimeout"; + +describe("promiseTimeout", () => { + it("promise resolves within timeout, returns value", () => { + expect( + promiseTimeout( + wait(1).then(() => 2), + 2, + ), + ).resolves.toBe(2); + }); + + it("promise does not resolve within timeout, throws PromiseTimedOutError", () => { + expect( + promiseTimeout( + wait(2).then(() => 2), + 1, + ), + ).rejects.toThrow(PromiseTimedOutError); + }); +}); + +function wait(ms: number) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} diff --git a/src/libs/promiseTimeout.ts b/src/libs/promiseTimeout.ts new file mode 100644 index 0000000..bf1c947 --- /dev/null +++ b/src/libs/promiseTimeout.ts @@ -0,0 +1,27 @@ +export function promiseTimeout( + promise: Promise, + timeoutMs: number, +): Promise { + let hasPromiseResolved = false; + + return Promise.race([ + promise.then((value) => { + hasPromiseResolved = true; + return value; + }), + new Promise((resolve) => setTimeout(resolve, timeoutMs)).then(() => { + if (hasPromiseResolved) { + return null; + } + + throw new PromiseTimedOutError(); + }), + ]); +} + +export class PromiseTimedOutError extends Error { + constructor(message?: string) { + super(message ?? "Promise timed out"); + this.name = "PromiseTimedOutError"; + } +}