Wrap Synchronous Computations with sync and try
Guideline
To bring a synchronous side-effect into Effect, wrap it in a thunk (() => ...).
Use Effect.sync for functions guaranteed not to throw, and Effect.try for
functions that might throw.
Rationale
This is the primary way to safely integrate with synchronous libraries like
JSON.parse. Effect.try captures any thrown exception and moves it into
the Effect's error channel.
Good Example
import { Effect } from "effect";
const randomNumber = Effect.sync(() => Math.random());
const parseJson = (input: string) =>
Effect.try({
try: () => JSON.parse(input),
catch: (error) => new Error(`JSON parsing failed: ${error}`),
});
// More examples of wrapping synchronous computations
const divide = (a: number, b: number) =>
Effect.try({
try: () => {
if (b === 0) throw new Error("Division by zero");
return a / b;
},
catch: (error) => new Error(`Division failed: ${error}`),
});
const processString = (str: string) =>
Effect.gen(function* () {
yield* Effect.log(`Processing string: "${str}"`);
return str.toUpperCase().split("").reverse().join("");
});
// Demonstrate wrapping synchronous computations
const program = Effect.gen(function* () {
yield* Effect.log("=== Wrapping Synchronous Computations Demo ===");
// Example 1: Basic sync computation
yield* Effect.log("\n1. Basic sync computation (random number):");
const random1 = yield* randomNumber;
const random2 = yield* randomNumber;
yield* Effect.log(
`Random numbers: ${random1.toFixed(4)}, ${random2.toFixed(4)}`
);
// Example 2: Successful JSON parsing
yield* Effect.log("\n2. Successful JSON parsing:");
const validJson = '{"name": "Paul", "age": 30}';
const parsed = yield* parseJson(validJson);
yield* Effect.log("Parsed JSON:" + JSON.stringify(parsed));
// Example 3: Failed JSON parsing with error logging
yield* Effect.log("\n3. Failed JSON parsing with error logging:");
const invalidJson = '{"name": "Paul", "age":}';
yield* parseJson(invalidJson).pipe(
Effect.tapError((error) => Effect.log(`Parsing failed: ${error.message}`)),
Effect.catchAll(() => Effect.succeed({ name: "default", age: 0 }))
);
yield* Effect.log("Continued after error (with recovery)");
// Example 4: Division with error logging and recovery
yield* Effect.log("\n4. Division with error logging and recovery:");
const division1 = yield* divide(10, 2);
yield* Effect.log(`10 / 2 = ${division1}`);
// Use tapError to log, then catchAll to recover
const division2 = yield* divide(10, 0).pipe(
Effect.tapError((error) => Effect.log(`Division error: ${error.message}`)),
Effect.catchAll(() => Effect.succeed(-1))
);
yield* Effect.log(`10 / 0 = ${division2} (error handled)`);
// Example 5: String processing
yield* Effect.log("\n5. String processing:");
const processed = yield* processString("Hello Effect");
yield* Effect.log(`Processed result: "${processed}"`);
// Example 6: Combining multiple sync operations
yield* Effect.log("\n6. Combining multiple sync operations:");
const combined = yield* Effect.gen(function* () {
const num = yield* randomNumber;
const multiplied = yield* Effect.sync(() => num * 100);
const rounded = yield* Effect.sync(() => Math.round(multiplied));
return rounded;
});
yield* Effect.log(`Combined operations result: ${combined}`);
yield* Effect.log("\n✅ Synchronous computations demonstration completed!");
});
Effect.runPromise(program);
Explanation:
Use Effect.sync for safe synchronous code, and Effect.try to safely
handle exceptions from potentially unsafe code.
Anti-Pattern
Never use Effect.sync for an operation that could throw, like JSON.parse.
This can lead to unhandled exceptions that crash your application.