eventual-result
A
Result
andOption
implementation that works withPromise
Result
Implementation?
Why Another There are many other libraries that implement these patterns; a few sources of inspiration include ts-results
and oxide.ts
. These libraries share a problem, however: they do nothing to help you manage asynchronous code.
Since the dawn of ES6, Promise
and async
/await
have become critical parts of working on a modern JavaScript or TypeScript applications. While Result
provides a great way to model a potential success or failure for a synchronous action, needing to use something like Promise<Result>
to model an asynchronous action means you actually have two different ways that an error can occur: the Promise
can reject, or the Result
can be an Err
.
Promise<Result>
Let's suppose that we want to read a file asynchronously and then validate it to produce a Result
. That might look something like this:
import { readFile } from "node:fs/promises";
declare function isValid(content: string): boolean;
function validateFile(content: string): Result<string, string> {
if (isValid(content)) {
return Ok(content);
} else {
return Err("The file content is not valid");
}
}
async function readFile(path: string): Promise<Result<string, string>> {
try {
const content = await readFile(path);
return validateFile(content);
} catch (e: unknown) {
return Err(String(e));
}
}
It may be hard to recognize at first, but there are now two entirely different ways of handling errors in the code above! First, the try
/catch
handles an error while reading the file and has to manually transform that into an Err
. Second, validateFile
could itself return an Err
that represents a file that was successfully read but was, for other reasons, invalid.
Enter EventualResult
! This class provides us an alternative way to represent an eventual value that may result in a success or failure that, rather than competing with Result
, is compatible with it.
EventualResult
Let's look at the same example, but this time making use of an EventualResult
instead of a Promise<Result>
:
import { readFile } from "node:fs/promises";
declare function isValid(content: string): boolean;
function validateFile(content: string): Result<string, string> {
if (isValid(content)) {
return Ok(content);
} else {
return Err("The file content is not valid");
}
}
function readFile(path: string): EventualResult<string> {
return new EventualResult(readFile(path)).andThen((content) =>
validateFile(content)
);
}
What has changed?
- We no longer need specific
try
/catch
wrapping of the file read; by passing it throughEventualResult
, we no longer end up with aPromise
that can reject. If an error during the file read occurs, theEventualResult
will resolve to anErr
- We don't need any conditional logic when validating the file that handles what to do when the file read failed; because
EventualResult
implements most of the same methods thatResult
does, we can use our existing knowledge ofandThen
to only validate the contents if the file read eventually results in anOk
The end result is writing less defensive code that extends the predictability of Result
with the eventual resolution of Promise
.