diff --git a/lib/core/tasks.ts b/lib/core/tasks.ts index 74b8c32..c48815d 100644 --- a/lib/core/tasks.ts +++ b/lib/core/tasks.ts @@ -3,6 +3,7 @@ import { Resonate } from "../resonate"; import { ErrorCodes, ResonateError } from "./errors"; import { RetryPolicy, isRetryPolicy } from "./retry"; import { TaskMessage, TasksSource } from "./tasksSource"; +import * as utils from "./utils"; export type ResumeBody = { promiseId: string; @@ -121,7 +122,7 @@ export class TasksHandler { } localCallback(promiseId: string): Promise { - const { promise, resolve, reject } = Promise.withResolvers(); + const { promise, resolve, reject } = utils.promiseWithResolvers(); this.#callbackPromises.set(promiseId, { resolve, reject }); return promise as Promise; } diff --git a/lib/core/tasksSources/http.ts b/lib/core/tasksSources/http.ts index 3574eb6..f7655e6 100644 --- a/lib/core/tasksSources/http.ts +++ b/lib/core/tasksSources/http.ts @@ -1,13 +1,14 @@ import * as http from "node:http"; import { Logger } from "../loggers/logger"; import { TasksSource, TaskMessage, isTaskMessage } from "../tasksSource"; +import * as utils from "../utils"; const STOP = "stop"; export class HttpTaskSource implements TasksSource { readonly url: URL; readonly generator: AsyncGenerator; - readonly stopPromise: PromiseWithResolvers = Promise.withResolvers(); + readonly stopPromise: utils.PromiseWithResolvers = utils.promiseWithResolvers(); /** * Constructs a new instance of the HttpTaskSource with the provided URL and an optional logger. diff --git a/lib/core/tasksSources/local.ts b/lib/core/tasksSources/local.ts index 48b1086..d5cb9b0 100644 --- a/lib/core/tasksSources/local.ts +++ b/lib/core/tasksSources/local.ts @@ -1,9 +1,10 @@ import { TaskMessage, TasksSource } from "../tasksSource"; +import * as utils from "../utils"; export class LocalTasksSource implements TasksSource { private taskQueue: TaskMessage[] = []; private resolver: ((taskMessage: TaskMessage) => void) | undefined; - private stopPromise: PromiseWithResolvers = Promise.withResolvers(); + private stopPromise: utils.PromiseWithResolvers = utils.promiseWithResolvers(); readonly generator: AsyncGenerator; constructor() { diff --git a/lib/core/utils.ts b/lib/core/utils.ts index f4a7696..c14fc8f 100644 --- a/lib/core/utils.ts +++ b/lib/core/utils.ts @@ -120,3 +120,38 @@ export function promiseState(p: Promise): Promise<"pending" | "resolved" | () => "rejected", // Rejected branch ); } + +export type PromiseWithResolvers = { + promise: Promise; + resolve: (value: T | PromiseLike) => void; + reject: (reason?: any) => void; +}; + +/** + * Creates a Promise with externalized resolve and reject functions. + * + * @returns An object containing: + * - promise: A new Promise + * - resolve: A function to resolve the Promise + * - reject: A function to reject the Promise + * + * @example + * const { promise, resolve, reject } = promiseWithResolvers(); + * + * // Later in your code: + * resolve('Hello, World!'); + * + * // Or if an error occurs: + * reject(new Error('Something went wrong')); + */ +export function promiseWithResolvers(): PromiseWithResolvers { + let resolve: (value: T | PromiseLike) => void; + let reject: (reason?: any) => void; + + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + return { promise, resolve: resolve!, reject: reject! }; +} diff --git a/test/utils.test.ts b/test/utils.test.ts index c2bcf09..ad128be 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect, jest } from "@jest/globals"; -import { mergeObjects, sleep, promiseState } from "../lib/core/utils"; +import { mergeObjects, sleep, promiseState, promiseWithResolvers } from "../lib/core/utils"; jest.setTimeout(2000); @@ -130,3 +130,29 @@ describe("promiseState", () => { expect(laterRejectedResult).toBe("rejected"); }); }); + +describe("promiseWithResolvers", () => { + test("resolves the promise", async () => { + const { promise, resolve } = promiseWithResolvers(); + resolve("Success"); + await expect(promise).resolves.toBe("Success"); + }); + + test("rejects the promise", async () => { + const { promise, reject } = promiseWithResolvers(); + reject(new Error("Failure")); + await expect(promise).rejects.toThrow("Failure"); + }); + + test("resolve and reject functions are callable", () => { + const { resolve, reject } = promiseWithResolvers(); + expect(typeof resolve).toBe("function"); + expect(typeof reject).toBe("function"); + }); + + test("works with different types", async () => { + const { promise, resolve } = promiseWithResolvers(); + resolve(42); + await expect(promise).resolves.toBe(42); + }); +});