Created
February 28, 2025 21:38
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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