Skip to content

Instantly share code, notes, and snippets.

@anglinb
Created February 28, 2025 21:38
Show Gist options
  • Save anglinb/5138a3a04d41a6e6e6edfc65cd6d0e85 to your computer and use it in GitHub Desktop.
Save anglinb/5138a3a04d41a6e6e6edfc65cd6d0e85 to your computer and use it in GitHub Desktop.
Shows how to invoke a scoped effect in a cloudflare worker and return the result while keeping it alive for the scope to close.
import { Duration, Effect, Either, Scope, Cause } from 'effect';
// This function allows you call to a scoped effect, return the response
// then use the ctx.waitUntil api to ensure the worker continues
// to run until the scope is done closing. It handles, success,
// failures and defects.
const runAwaitingScopeClose =
<Input, S extends Response, E>(effect: (input: Input, env: Env, ctx: ExecutionContext) => Effect.Effect<S, E, Scope.Scope>) =>
(input: Input, env: Env, ctx: ExecutionContext): Promise<Response> => {
const innerEffect = Effect.gen(function* (_) {
const response = yield* effect(input, env, ctx);
yield* Effect.logInfo('done getting response', response);
return response;
});
return new Promise<Either.Either<S, E | Cause.Cause<never>>>((resolve) => {
ctx.waitUntil(
Effect.runPromise<S, E>(
innerEffect.pipe(
Effect.tapDefect((cause) => Effect.sync(() => resolve(Either.left(cause)))),
Effect.tapBoth({
onSuccess: (result) => Effect.sync(() => resolve(Either.right(result))),
onFailure: (result) => Effect.sync(() => resolve(Either.left(result))),
}),
Effect.scoped
)
)
);
}).then((result) => Either.getOrThrowWith((left) => left)(result));
};
// Simulates some reporting or other cleanup that we need to
// do after the response but we don't want to actually await it.
const someLongFunction = Effect.gen(function* () {
yield* Effect.log(`generate response - inside finalizer - start`);
yield* Effect.sleep(Duration.seconds(2));
yield* Effect.log(`generate response - inside finalizer - complete`);
});
// Example implementation of some worker function. Would be
// the code you usually put in the main body of the fetch handler
// if you were using promises instead of effect.
const generateResponse = (request: Request, env: Env, ctx: ExecutionContext) =>
Effect.gen(function* (_) {
yield* Effect.addFinalizer(() => someLongFunction);
const path = new URL(request.url).pathname.slice(1);
switch (path) {
case 'success': {
return new Response(JSON.stringify({ status: 'ok' }));
}
case 'failure': {
return yield* Effect.fail(new Error('failure'));
}
case 'defect': {
throw new Error('Unexpected error!');
}
default: {
return new Response(`Try requesting /success, /failure or /defect`);
}
}
});
// Simple example binding the inputs from
export default {
async fetch(...args): Promise<Response> {
return runAwaitingScopeClose(generateResponse)(...args);
},
} satisfies ExportedHandler<Env>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment