- docs
- std
- effect
@ozaco/std
import from
@ozaco/std/effectEffect
Structured concurrency for TypeScript. An operation is a generator; running one gives you a
Result instead of a thrown exception, and every task you start belongs to a scope that cannot
outlive it.
Operations settle, they do not throw
run takes an operation and returns a task you can await. The value that comes back is a
Result: a success carrying a value, or a failure carrying an error, a message and its causes.
Nothing crosses the boundary as an exception.
const result = await run(function* () {
const value = yield* call(() => Promise.resolve(21))
return value * 2
})
// from examples/std/effect.test.ts
A rejected promise inside the operation is not special. It settles the operation as a failure,
which you narrow with the guards from @ozaco/std/result.
const fails = () => Promise.reject(new Error('nope'))
const result = await run(function* () {
return yield* call(fails)
})
// from examples/std/effect.test.ts
Children belong to a scope
spawn starts an operation as a child of the current scope. The child begins at the next
opportunity and cannot outlive its parent — when the scope closes, the child is halted and
its teardown runs before the parent returns.
const result = await run(function* () {
const poll = yield* spawn(function* () {
for (let tick = 0; ; tick++) {
ticks.push(tick)
yield* sleep(5)
}
})
yield* sleep(20)
yield* poll.halt()
return ticks.length
})
// from examples/std/effect.test.ts
Task.halt() is lazy: consuming it is what interrupts. It resolves once the child has finished
unwinding, so by the time halt() returns there is no work left running.
The first outcome wins
race settles the moment any operation settles — including a failure. A branch that fails
quickly makes the whole race fail, even when a slower branch would have succeeded. Before
returning, race halts every loser and awaits those halts, so no work leaks past the winner.
const fail = () => Promise.reject(new Error('fast fail'))
const fast = function* () {
yield* sleep(10)
return yield* call(fail)
}
const slow = function* () {
yield* sleep(100)
return 'slow ok'
}
const result = await run(function* () {
return yield* race([fast(), slow()])
})
// from examples/std/effect.test.ts
const fail = () => Promise.reject(new Error('fast fail'))
const fast = function* () {
yield* sleep(10)
return yield* call(fail)
}
const slow = function* () {
yield* sleep(100)
return 'slow ok'
}
const result = await run(function* () {
return yield* race([fast(), slow()])
})
Drag the lanes or press an event to move the playhead. Trace times are the snippet’s own milliseconds, played back slowly.
- ›
race([])throws synchronously rather than returning a failure — guard against empty arrays. - ›The losing branch is halted at its suspension point, not abandoned.
Teardown is armed on the way in
ensure registers teardown against the current scope. It runs however the body leaves —
returning, failing, or being halted from outside.
const fails = () => Promise.reject(new Error('boom'))
const result = await run(function* () {
yield* ensure(() => {
closed.push('handle')
})
return yield* call(fails)
})
// from examples/std/effect.test.ts