Error Propagation
Funkcia offers a concise and convenient way to write your code in a more imperative style that utilizes the native scope provided by the generator syntax. This syntax is more linear and resembles normal synchronous code.
Drawing primarily from Rust's ?
operator for error propagation, and inspired by Gleam's use
expressions, neverthrow's safeTry
, and Effect's gen
functions, the following functions provide a clean way to handle sequential operations while maintaining proper error handling and type safety.
use
Evaluates an async generator early returning when an Option.None
is propagated or returning the OptionAsync
returned by the generator.
Each
yield*
automatically awaits and unwraps theOptionAsync
value or propagatesNone
.If any operation resolves to
Option.None
, the entire generator exits early.
import { OptionAsync } from 'funkcia';
declare const safeReadFile: (path: string) => OptionAsync;
declare const safeWriteFile: (path: string, content: string) => OptionAsync;
// ββββ OptionAsync
// βΌ
const mergedContent = OptionAsync.use(async function* () {
const = yield* safeReadFile('data.txt');
const = yield* safeReadFile('non-existent-file.txt'); // returns OptionAsync.None immediately
return safeWriteFile('output.txt', `${fileA}\n${fileB}`); // doesn't run
});
// Output: Promise
createUse
Returns a function that evaluates an async generator when called with the defined arguments, early returning when an Option.None
is propagated or returning the OptionAsync
returned by the generator.
Each
yield*
automatically awaits and unwraps theOptionAsync
value or propagatesNone
.If any operation resolves to
Option.None
, the entire generator exits early.
import { OptionAsync } from 'funkcia';
declare const safeReadFile: (path: string) => OptionAsync;
declare const safeWriteFile: (path: string, content: string) => OptionAsync;
// ββββ (output: string, pathA: string, pathB: string) => OptionAsync
// βΌ
const safeMergeFiles = OptionAsync.createUse(async function* (output: string, pathA: string, pathB: string) {
const = yield* safeReadFile(pathA);
const = yield* safeReadFile(pathB);
return safeWriteFile(output, `${fileA}\n${fileB}`);
});
const mergedContent = safeMergeFiles('output.txt', 'data.txt', 'updated-data.txt');
// Output: Promise
Understanding the use method
The use
method provides a way to write sequential operations that might fail, similar to Rust's ?
operator. It lets you write code that looks synchronous while safely handling potential failures.
It essentially creates a "safe context" where you can work with values as if they were guaranteed to exist, while maintaining all the safety guarantees of Option
. If anything fails, the failure propagates automatically. Like an electronic relay that controls current flow, β relay controls computation flow: β Option.Some
continues, β Option.None
breaks the circuit.
Here's a practical example:
import { Option } from 'funkcia';
declare function findUser(id: string): Option<User>;
declare function getUserPermissions(user: User): Option<Permissions>;
declare function checkAccess(permissions: Permissions, resource: string): Option<Access>;
const access = Option.relay(function* () {
// First, try to find the user
const user = yield* findUser('user_123');
// If user is found (`findUser` returns `Option.Some(User)`, get their permissions
const permissions = yield* getUserPermissions(user);
// If all steps succeed, we can use the accumulated context to check access to specific resource
return checkAccess(permissions, 'api-key');
});
The equivalent code without relay
would be much more nested:
import { Option } from 'funkcia';
declare function findUser(id: string): Option<User>;
declare function getUserPermissions(user: User): Option<Permissions>;
declare function checkAccess(permissions: Permissions, resource: string): Option<Access>;
const access = findUser('user_123')
.andThen(user =>
getUserPermissions(user)
.andThen(permissions =>
checkAccess(permissions, 'api-key')
)
);
Or with intermediate variables:
import { Option } from 'funkcia';
declare function findUser(id: string): Option<User>;
declare function getUserPermissions(user: User): Option<Permissions>;
declare function checkAccess(permissions: Permissions, resource: string): Option<Access>;
const maybeUser = findUser('user_123');
const maybePermissions = maybeUser.andThen(getUserPermissions);
const access = maybePermissions.andThen(permissions => {
return checkAccess(permissions, 'api-key');
});
Last updated
Was this helpful?