Wrap Asynchronous Computations with tryPromise
Guideline
To integrate a Promise-based function (like fetch), use Effect.tryPromise.
Rationale
This is the standard bridge from the Promise-based world to Effect, allowing
you to leverage the massive async/await ecosystem safely.
Good Example
import { Effect, Data } from "effect";
// Define error type using Data.TaggedError
class HttpError extends Data.TaggedError("HttpError")<{
readonly message: string;
}> {}
// Define HTTP client service
export class HttpClient extends Effect.Service<HttpClient>()("HttpClient", {
// Provide default implementation
sync: () => ({
getUrl: (url: string) =>
Effect.tryPromise({
try: () => fetch(url),
catch: (error) =>
new HttpError({ message: `Failed to fetch ${url}: ${error}` }),
}),
}),
}) {}
// Mock HTTP client for demonstration
export class MockHttpClient extends Effect.Service<MockHttpClient>()(
"MockHttpClient",
{
sync: () => ({
getUrl: (url: string) =>
Effect.gen(function* () {
yield* Effect.logInfo(`Fetching URL: ${url}`);
// Simulate different responses based on URL
if (url.includes("success")) {
yield* Effect.logInfo("✅ Request successful");
return new Response(JSON.stringify({ data: "success" }), {
status: 200,
});
} else if (url.includes("error")) {
yield* Effect.logInfo("❌ Request failed");
return yield* Effect.fail(
new HttpError({ message: "Server returned 500" })
);
} else {
yield* Effect.logInfo("✅ Request completed");
return new Response(JSON.stringify({ data: "mock response" }), {
status: 200,
});
}
}),
}),
}
) {}
// Demonstrate wrapping asynchronous computations
const program = Effect.gen(function* () {
yield* Effect.logInfo("=== Wrapping Asynchronous Computations Demo ===");
const client = yield* MockHttpClient;
// Example 1: Successful request
yield* Effect.logInfo("\n1. Successful request:");
const response1 = yield* client
.getUrl("https://api.example.com/success")
.pipe(
Effect.catchAll((error) =>
Effect.gen(function* () {
yield* Effect.logError(`Request failed: ${error.message}`);
return new Response("Error response", { status: 500 });
})
)
);
yield* Effect.logInfo(`Response status: ${response1.status}`);
// Example 2: Failed request with error handling
yield* Effect.logInfo("\n2. Failed request with error handling:");
const response2 = yield* client.getUrl("https://api.example.com/error").pipe(
Effect.catchAll((error) =>
Effect.gen(function* () {
yield* Effect.logError(`Request failed: ${error.message}`);
return new Response("Fallback response", { status: 200 });
})
)
);
yield* Effect.logInfo(`Fallback response status: ${response2.status}`);
// Example 3: Multiple async operations
yield* Effect.logInfo("\n3. Multiple async operations:");
const results = yield* Effect.all(
[
client.getUrl("https://api.example.com/endpoint1"),
client.getUrl("https://api.example.com/endpoint2"),
client.getUrl("https://api.example.com/endpoint3"),
],
{ concurrency: 2 }
).pipe(
Effect.catchAll((error) =>
Effect.gen(function* () {
yield* Effect.logError(`One or more requests failed: ${error.message}`);
return [];
})
)
);
yield* Effect.logInfo(`Completed ${results.length} requests`);
yield* Effect.logInfo(
"\n✅ Asynchronous computations demonstration completed!"
);
});
// Run with mock implementation
Effect.runPromise(Effect.provide(program, MockHttpClient.Default));
Explanation:
Effect.tryPromise wraps a Promise-returning function and safely handles
rejections, moving errors into the Effect's error channel.
Anti-Pattern
Manually handling .then() and .catch() inside an Effect.sync. This is
verbose, error-prone, and defeats the purpose of using Effect's built-in
Promise integration.