From 817a04cb2df0f4140984dc97eb3e1bb14a6c4a38 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 13 Mar 2024 13:42:46 +1300 Subject: [PATCH] add support for AbortSignal's to runPromise (#2285) --- .changeset/five-games-sneeze.md | 18 +++++++++++ packages/effect/src/Effect.ts | 11 +++++-- packages/effect/src/Runtime.ts | 9 ++++-- packages/effect/src/internal/runtime.ts | 41 +++++++++++++++++-------- packages/effect/test/Runtime.test.ts | 15 ++++++++- 5 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 .changeset/five-games-sneeze.md diff --git a/.changeset/five-games-sneeze.md b/.changeset/five-games-sneeze.md new file mode 100644 index 0000000000..31f2bb2c8b --- /dev/null +++ b/.changeset/five-games-sneeze.md @@ -0,0 +1,18 @@ +--- +"effect": patch +--- + +add support for AbortSignal's to runPromise + +If the signal is aborted, the effect execution will be interrupted. + +```ts +import { Effect } from "effect"; + +const controller = new AbortController(); + +Effect.runPromise(Effect.never, { signal: controller.signal }); + +// abort after 1 second +setTimeout(() => controller.abort(), 1000); +``` diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index 545fdf25df..e0139c723e 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -4672,7 +4672,10 @@ export const runCallback: ( * @since 2.0.0 * @category execution */ -export const runPromise: (effect: Effect) => Promise = _runtime.unsafeRunPromiseEffect +export const runPromise: ( + effect: Effect, + options?: { readonly signal?: AbortSignal } | undefined +) => Promise = _runtime.unsafeRunPromiseEffect /** * Runs an `Effect` workflow, returning a `Promise` which resolves with the @@ -4681,8 +4684,10 @@ export const runPromise: (effect: Effect) => Promise = _runtime.u * @since 2.0.0 * @category execution */ -export const runPromiseExit: (effect: Effect) => Promise> = - _runtime.unsafeRunPromiseExitEffect +export const runPromiseExit: ( + effect: Effect, + options?: { readonly signal?: AbortSignal } | undefined +) => Promise> = _runtime.unsafeRunPromiseExitEffect /** * @since 2.0.0 diff --git a/packages/effect/src/Runtime.ts b/packages/effect/src/Runtime.ts index cf135d57d3..d6103c25bb 100644 --- a/packages/effect/src/Runtime.ts +++ b/packages/effect/src/Runtime.ts @@ -134,7 +134,9 @@ export const runCallback: ( * @since 2.0.0 * @category execution */ -export const runPromise: (runtime: Runtime) => (effect: Effect.Effect) => Promise = +export const runPromise: ( + runtime: Runtime +) => (effect: Effect.Effect, options?: { readonly signal?: AbortSignal } | undefined) => Promise = internal.unsafeRunPromise /** @@ -149,7 +151,10 @@ export const runPromise: (runtime: Runtime) => (effect: Effect.Effec */ export const runPromiseExit: ( runtime: Runtime -) => (effect: Effect.Effect) => Promise> = internal.unsafeRunPromiseExit +) => ( + effect: Effect.Effect, + options?: { readonly signal?: AbortSignal } | undefined +) => Promise> = internal.unsafeRunPromiseExit /** * @since 2.0.0 diff --git a/packages/effect/src/internal/runtime.ts b/packages/effect/src/internal/runtime.ts index 49751dc33e..b36cbf7949 100644 --- a/packages/effect/src/internal/runtime.ts +++ b/packages/effect/src/internal/runtime.ts @@ -257,30 +257,45 @@ export const unsafeRunSyncExit = } /** @internal */ -export const unsafeRunPromise = - (runtime: Runtime.Runtime) => (effect: Effect.Effect): Promise => - unsafeRunPromiseExit(runtime)(effect).then((result) => { - switch (result._tag) { - case OpCodes.OP_SUCCESS: { - return result.i0 - } - case OpCodes.OP_FAILURE: { - throw fiberFailure(result.i0) - } +export const unsafeRunPromise = (runtime: Runtime.Runtime) => +(effect: Effect.Effect, options?: { + readonly signal?: AbortSignal +}): Promise => + unsafeRunPromiseExit(runtime)(effect, options).then((result) => { + switch (result._tag) { + case OpCodes.OP_SUCCESS: { + return result.i0 } - }) + case OpCodes.OP_FAILURE: { + throw fiberFailure(result.i0) + } + } + }) /** @internal */ export const unsafeRunPromiseExit = - (runtime: Runtime.Runtime) => (effect: Effect.Effect): Promise> => + (runtime: Runtime.Runtime) => + (effect: Effect.Effect, options?: { + readonly signal?: AbortSignal + }): Promise> => new Promise((resolve) => { const op = fastPath(effect) if (op) { resolve(op) } - unsafeFork(runtime)(effect).addObserver((exit) => { + const fiber = unsafeFork(runtime)(effect) + fiber.addObserver((exit) => { resolve(exit) }) + if (options?.signal !== undefined) { + if (options.signal.aborted) { + fiber.unsafeInterruptAsFork(fiber.id()) + } else { + options.signal.addEventListener("abort", () => { + fiber.unsafeInterruptAsFork(fiber.id()) + }) + } + } }) /** @internal */ diff --git a/packages/effect/test/Runtime.test.ts b/packages/effect/test/Runtime.test.ts index 1ab8d76e14..2ca0c6bc2b 100644 --- a/packages/effect/test/Runtime.test.ts +++ b/packages/effect/test/Runtime.test.ts @@ -1,4 +1,4 @@ -import { Effect, FiberRef, Layer, Runtime } from "effect" +import { Effect, Exit, FiberRef, Layer, Runtime } from "effect" import { assert, describe } from "vitest" import * as it from "./utils/extend.js" @@ -27,4 +27,17 @@ describe("Runtime", () => { result = Runtime.runSync(Runtime.deleteFiberRef(runtime, ref))(FiberRef.get(ref)) assert.deepStrictEqual(result, { value: 0 }) })) + + it.it("runPromiseExit/signal", async () => { + const aborted = AbortSignal.abort() + assert(Exit.isInterrupted(await Runtime.runPromiseExit(Runtime.defaultRuntime)(Effect.never, { signal: aborted }))) + + const controller = new AbortController() + setTimeout(() => controller.abort(), 10) + assert( + Exit.isInterrupted( + await Runtime.runPromiseExit(Runtime.defaultRuntime)(Effect.never, { signal: controller.signal }) + ) + ) + }) })