Results
Results are for when you know a function can fail:
// So instead of this signature:
type function = (...) => string;
// You can have this:
type function = (...) => Result<string>;
However, a function that returns a result should never throw. This allows a user to skip the try and catch blocks.
Control Flows
There are several ways to create control flows for this.
Match Arms
One is match arms:
import { Result } from '...';
let result: Result<T, E>;
result.match({
[Result.OKAY]: (value: T) => console.log(value),
[Result.ERROR]: (error: E) => console.error(error),
});
You can even return match statements!
import { Result } from '...';
function multiply<E>(result: Result<number, E>): Result<number, E> {
return result.match({
[Result.ERROR]: (error) => Result.error(error),
[Result.OKAY]: (value) => Result.okay(value * 2),
});
}
Is Variant Functions
There is also the isOkay() and isError() functions:
import { Result } from '...';
let result: Result<T, E>;
if (result.isOkay()) {
console.log(result.value),
}
else if (result.isError()) {
console.error(result.error),
}
else {
// unreachable statement
}
Instance Checks
Lastly there is instance matching:
import { Result } from '...';
let result: Result<T>;
if (result instanceof Result.Okay) {
console.log(result.value),
}
if (result instanceof Result.Error) {
console.error(result.error),
}
if (result instanceof Result.Class) {
// check if okay or error
}
Creating
There are several ways to create instances of Results. Even if you don't control the functions signature for example.
Exec
The Result.exec function wraps execution of a function and always returns an Result even if the function throws:
import { Result } from '...';
import myFunction from '...';
let result = Result.exec(myFunction, ...);
Where it takes the arguments that function would take, fully typed:
import { Result } from '...';
function divide(a: number, b: number): number | null;
let result = Result.exec(divide, 1, 2); // Okay!
let result = Result.exec(divide, 1, '3'); // Error: string is not a number
let result = Result.exec(divide, 1); // Error: missing argument
let result = Result.exec(divide, 1, 2, 3); // Error: too many arguments
Okay and Error functions
You can also create Results when you do control the signature:
import { Result } from '...';
function divide(a: number, b: number): Result<number> {
if (b === 0) return Result.error('Cannot divide by zero');
else return Result.okay(a / b);
}
Chose either a string to create a base error class instance, or pick a specific class:
class MyError extends Error {}
const instance = new MyError('a message on what went wrong');
Result.error(instance);
From function
You can also let prevent regular try/catch patterns using Result.fromThrown:
function maybe<T>(a: T): Result<T> {
try {
...
} catch (error) {
const fallback = 'in case `error` is not a string or error instance';
return Result.fromThrown(error, fallback);
}
}