An alternative approach to async/await error handling for TypeScript
git clone https://github.com/ethossoftworks/outcome-ts.gitcd outcome-tsyarn # or npm installyarn build # or npm run build# yarn test # Run testing script# yarn test-inspect # Run testing script with chrome dev-tools inspector
Outcome works best (while not required) with the following TypeScript compiler option:
strictNullChecks will prevent the Outcome value from being set to null or undefined unless the user-specified Outcome type supports it.
Add to Project
npm install @ethossoftworks/outcome# oryarn add @ethossoftworks/outcome
Create a Standard Outcome
Consume an Outcome
if fooResult.isError// Type inference allows type-safe compile time use of the valueconsole.logfooResult.value
Wrap an Existing Promise With an Outcome
if wrappedResult.isError// Type inference allows type-safe compile time use of the valueconsole.logwrappedResult.value
Usage With a Defined Error Type
if userResult.isError// Type inference allows type-safe compile time use of the valueconsole.loguserResult.value
Promises and Async/Await are wonderful, flexible APIs that provide a great developer experience. However, promises and async/await have some undesirable syntax when dealing with complex error handling.
Promise callbacks can make logic branching more difficult and more confusing to reason about while handling all errors appropriately.
Promises in TypeScript do not have error types associated with them.
Using Async/Await safely necessitates the use try/catch blocks to handle errors
To use a value outside of the try/catch block, a user must define a
letinstead of a
constoutside of the try/catch block. This is not guaranteed to be safe at compile time and type inference is iffy.
const, the resultant value can only be used within the try block which may prevent clean handling of additional errors when using the resultant value. In addition, another level of indentation is undesirable.
Often errors in an application are not exceptional and it should not be necessary to treat them as exceptions.
All errors are exception and have no helpful type association.
Golang uses the concept of errors as values which allows for checking for errors and handling them without requiring another block indentation. This also allows easier handling of complex error branches.
The downside of how Golang handles errors is that it does not enforce handling errors at compile time which can lead to the Golang equivalent of null pointer exceptions.
Outcome strives to provide type-safe error handling without the syntactic bloat of try/catches or callbacks.
The goal of Outcome is to allow for clean, complex branching based on success or failure of operations (whether asynchronous or not). This is achieved through type guards (which allow for compile time type inference) and forcing the checking of success/failure before working with the corresponding value. Outcome is also designed to allow for specified error types to allow for cleaner APIs where users can know ahead-of-time all of the different errors that a particular function can return.
Outcome<T, E> is a union type of an
OutcomeValue<T> and an
OutcomeError<E> type. The
error properties of those respective types are only accessible after a type check on the
OutcomeError is by default an
unknown type which enforces type checking to handle any error. This prevents changes to error types causing unknown problems down the road. An error type may be specified to enforce only returning specific types of errors for a giving
isError() is a TypeScript type guard that allows for compile-time type inference.
Any existing promise can be converted to an
Outcome with the provided helper function
Outcome.wrap(). Since promises do not have error types, wrapping promises will use the
unknown type for the error value.