A practical Result type inspired by Rust Result.
Errors are normal in an applications lifecycle, they should be regular values and we should be able to know them by looking at function type signatures.
try/catch should be reserved to unexpected events recovery.
- integrate nicely with typescript/javascript
- small api surface
- practical over academical
npm i okerr
import { Ok, Err } from "okerr";
or
import "okerr/globals";
will import the Ok
and Err
global functions, Result
type and add the toResult
method to Promises.
import { Ok, Err } from "okerr";
// import 'okerr/globals';
enum ValidationErrors {
NameEmpty = "NameEmpty",
EmailEmpty = "EmailEmpty",
}
interface Input {
name: string;
email: string;
}
// function validate(input: Input): Err<ValidationErrors> | Ok<Input>
function validate(input: Input) {
if (!input.name) {
return Err(ValidationErrors.NameEmpty);
}
if (!input.email) {
return Err(ValidationErrors.EmailEmpty);
}
return Ok(input);
}
catch exceptions from a Promise into a Promise<Ok | Err>
import { toResult } from 'okerr';
// or
import 'okerr/globals';
async function someAsyncFunction(value: string): string {
...
}
const result = await toResult<ApiErrors>(someAsyncFunction(value));
// or
const result = await toResult(someAsyncFunction(value), e => e as ApiErrors);
// or if import 'okerr/globals'
const result = await someAsyncFunction(value).toResult<ApiErrors>();
// result: Ok<string> | Err<ApiErrors>
naturally bubble errors up the callstack until you want to deal with them.
notice how the following function do not throw and instead describe precisely all errors it might return without any visible error handling.
// function getItemsFromApi(input: Input): Promise<Err<ValidationErrors> | Ok<string> | Err<ApiErrors>>
async function getItemsFromApi(input: Input) {
const validateResult = validate(input);
// validateResult: Err<ValidationErrors> | Ok<Input>
const apiCallResult = await validateResult.mapOk(async (value) => {
return await someAsyncFunction(value).toResult<ApiErrors>();
});
// apiCallResult: Err<ValidationErrors> | Ok<string> | Err<ApiErrors>
return apiCallResult;
}
const result = validate({ name: "John", email: "john@email.com" });
// result: Err<ValidationErrors> | Ok<Input>
if (result.isErr()) {
// result.error: ValidationErrors
toast(translate(result.error));
return;
}
// result.data: Input
console.log(result.data);
const { error, data } = validate({ name: "John", email: "john@email.com" });
// error: ValidationErrors | undefined
// data: Input | undefined
if (error) {
// error: ValidationErrors
toast(translate(error));
return;
}
// data: Input
console.log(data);
transform a function returning Promise into a function returning Promise<Ok | Err>
import { resultify } from "okerr";
const someAsyncResultFunction = resultify(someAsyncFunction);
// someAsyncResultFunction: <E = unknown>(value: string) => Promise<Ok<string> | Err<E>>
const result = await someAsyncResultFunction<ApiErrors>(value);
// result: Ok<string> | Err<ApiErrors>
// or
const someAsyncResultFunction = resultify(someAsyncFunction)<ApiErrors>;
// someAsyncResultFunction: (value: string) => Promise<Ok<string> | Err<ApiErrors>>
const result = await someAsyncResultFunction(value);
// result: Ok<string> | Err<ApiErrors>
keep return types readable by merging Ok and Err types
import { Result } from "okerr";
// import 'okerr/globals';
// function getItemsFromApi(input: Input): Promise<Result<string, ValidationErrors | ApiErrors>>
async function getItemsFromApi(
input: Input
): Result<string, ValidationErrors | ApiErrors> {
const validateResult = validate(input);
// validateResult: Err<ValidationErrors> | Ok<Input>
const apiCallResult = await validateResult.mapOk(async (value) => {
return await someAsyncFunction(value).toResult<ApiErrors>();
});
// apiCallResult: Err<ValidationErrors> | Ok<string> | Err<ApiErrors>
return apiCallResult;
}