Using Generators
Effect offers a convenient syntax, similar to async/await, to write effectful code using generators.
Understanding Effect.gen
The Effect.gen utility simplifies the task of writing effectful code by utilizing JavaScript’s generator functions. This method helps your code appear and behave more like traditional synchronous code, which enhances both readability and error management.
Example (Performing Transactions with Discounts)
Let’s explore a practical program that performs a series of data transformations commonly found in application logic:
import { Effect } from "effect"
// Function to add a small service charge to a transaction amountconst addServiceCharge = (amount: number) => amount + 1
// Function to apply a discount safely to a transaction amountconst applyDiscount = ( total: number, discountRate: number,): Effect.Effect<number, Error> => discountRate === 0 ? Effect.fail(new Error("Discount rate cannot be zero")) : Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from a// databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Simulated asynchronous task to fetch a discount rate from a// configuration fileconst fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
// Assembling the program using a generator functionconst program = Effect.gen(function* () { // Retrieve the transaction amount const transactionAmount = yield* fetchTransactionAmount
// Retrieve the discount rate const discountRate = yield* fetchDiscountRate
// Calculate discounted amount const discountedAmount = yield* applyDiscount(transactionAmount, discountRate)
// Apply service charge const finalAmount = addServiceCharge(discountedAmount)
// Return the total amount after applying the charge return `Final amount to charge: ${finalAmount}`})
// Execute the program and log the resultEffect.runPromise(program).then(console.log)// Output: Final amount to charge: 96Key steps to follow when using Effect.gen:
- Wrap your logic in
Effect.gen - Use
yield*to handle effects - Return the final result
If any of the effects that you handle inside of the generator with yield*
fail, then the generator will stop and exit with that failure.
Comparing Effect.gen with async/await
If you are familiar with async/await, you may notice that the flow of writing code is similar.
Let’s compare the two approaches:
import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number,): Effect.Effect<number, Error> => discountRate === 0 ? Effect.fail(new Error("Discount rate cannot be zero")) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount(transactionAmount, discountRate) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (total: number, discountRate: number): Promise<number> => discountRate === 0 ? Promise.reject(new Error("Discount rate cannot be zero")) : Promise.resolve(total - (total * discountRate) / 100)
const fetchTransactionAmount = Promise.resolve(100)
const fetchDiscountRate = Promise.resolve(5)
export const program = async function () { const transactionAmount = await fetchTransactionAmount const discountRate = await fetchDiscountRate const discountedAmount = await applyDiscount(transactionAmount, discountRate) const finalAmount = addServiceCharge(discountedAmount)