Type-safe errors
ErrorBox#
The way that LazyPromise supports type-safe errors reflects the JavaScript reality that you cannot typecheck errors that you throw and have to represent typed errors with return values. Instead of having an extra channel in addition to resolve and reject, we pass typed errors through the resolve channel, wrapping them in ErrorBox class to differentiate them from other values. new ErrorBox(error) simply stores error in its .error property. Besides resolving with an error box in the constructor callback, you can return one from an operator callback, a generator function, or an async function that you pass to fromEager.
catchBoxed#
There is an operator catchBoxed which is a boxed error counterpart of catch:
new LazyPromise<number | ErrorBox<"oops">>((sink) => {
...
sink.resolve(new ErrorBox("oops"));
...
}).catchBoxed(
// Type inferred as "oops"
(error) => ...,
);How other APIs treat error boxes#
ErrorBox instances are treated differently from other values by some of the LazyPromise APIs:
-
If you call
.subscribeor.toEageron a LazyPromise that can resolve to boxed errors, you’ll get a typechecking error. This makes sure that if for example you add a new error to a server endpoint, you’ll catch all the places on the client where that error isn’t handled. The.subscribemethod has an optional generic type parameterWhitelistedErrorthat you can use to silence the check for some or all errors. -
map,all, andraceoperators pass boxed errors through the same way they pass through rejections, e.g.declare const promiseA: LazyPromise<number | ErrorBox<"oops">>; // Type inferred as LazyPromise<string | ErrorBox<"oops">> const promiseB = promiseA.map( ( // Type inferred as number value, ) => String(value), ); -
We talked about how when
lazyPromiserejects witherror,yield* lazyPromiseacts exactly likethrow error. IflazyPromiseresolves with an ErrorBox instanceboxedError,yield* lazyPromiseacts exactly likereturn boxedError. In both cases the execution of the generator function is interrupted, the only difference is that you can’tcatcha boxed error: you have to use thecatchBoxedoperator instead. If the execution continues, we know thatlazyPromisehas resolved with something other than a boxed error:declare const promiseA: LazyPromise<number | ErrorBox<"oops">>; // Type inferred as LazyPromise<string | ErrorBox<"oops">> const promiseB = fromGen(function* () { // Type inferred as number const value = yield* promiseA; return String(value); });
any#
Unlike other operators, any does not behave like its native promise counterpart, but this is only because that operator isn’t very ergonomic without typed errors anyway. When one of the promises passed to Promise.any rejects because of a bug, the bug passes undetected if some other input promise resolves. The LazyPromise version of any works like Promise.any with respect to boxed errors, but rejects if just one input rejects:
declare const promiseA: LazyPromise<string | ErrorBox<"a">>;
declare const promiseB: LazyPromise<number | ErrorBox<"b">>;
// Type inferred as LazyPromise<string | number | ErrorBox<["a", "b"]>>
const lazyPromise = any([promiseA, promiseB]);anyKeyed#
anyKeyed is to any what allKeyed is to all: it takes an object instead of an iterable, and if every input resolves with a boxed error, the errors are collected into an object with the same keys. There is no native counterpart because Promise.any collects errors into an AggregateError.
declare const promiseA: LazyPromise<string | ErrorBox<"a">>;
declare const promiseB: LazyPromise<number | ErrorBox<"b">>;
// Type inferred as LazyPromise<string | number | ErrorBox<{ a: "a"; b: "b" }>>
const lazyPromise = anyKeyed({ a: promiseA, b: promiseB });UnboxError#
This helper type extracts the error from the type a LazyPromise resolves with, so UnboxError<number | ErrorBox<"oops">> will give you "oops".
NotAnErrorBox#
This is the type of any value that isn’t an ErrorBox. It’s what .subscribe and .toEager use under the hood, and you can use it yourself to write a function that only accepts lazy promises without unhandled boxed errors, e.g. (source: LazyPromise<NotAnErrorBox, undefined>) => ... (see the next section regarding the second undefined type parameter).