PROPOSAL: Safe assignment using neverthrow
vekexasia opened this issue · 0 comments
Hello all,
I started using neverthrow and enjoying it big time. Unfortunately all the attempts to have clean code when handling errors are failing big time.
I tried with safeTry but I instantly hit #604. I also tried other approaches like chaining andThen
as per this comment in #301. My brain forbids me have that (although i have found brief relief in using it)..
Following the safe assignment proposal I figured that the best way would be to inherit from Go error handling (but with error first as explained in the repo).
I then created the following snippet of code that would let me call .safeRet()
to produce a tuple containing the Error and the Result.
declare module "neverthrow" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export class Err<T, E> {
public safeRet(): E extends never ? [E, T] : [E, undefined];
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export class Ok<T, E> {
public safeRet(): T extends never ? [E, T] : [undefined, T];
}
}
Ok.prototype.safeRet = function () {
return [undefined, this.value];
};
Err.prototype.safeRet = function () {
return [this.error, undefined];
};
What happens is the following:
const [e, s] = ok("string").safeRet();
// e: undefined, s: string
const [e2, s2] = err("error").safeRet();
// e2: "error", s2: undefined
function a(x: Result<number, string>): Result<number, string> {
const [e3, s3] = x.safeRet();
// e3: string | undefined, s3: number | undefined
// this forces you to check for e3
if (typeof e3 !== "undefined") {
return err(e3);
}
// s3 is now number
return ok(s3);
}
NOTE: when using ok
we can directly use the result as typescript properly infer that s
is always set
If by any case we change from Result<*, never>
to an Error, then automatically typescript infers the result to be T
| undefined
forcing you to either use the Non-null assertion operator (!) or check for error like in the a
function above.
just to give you perspective of what i mean this is what a chain of neverthrow "compatible" functions might look like.
declare function randomNumber(): Ok<number, never>;
declare function safeDivision(
a: number,
b: number,
): Result<number, "cannot divide by zero">;
declare function errIfAbove1(
what: number,
): Result<undefined, "CannotBe>1" | undefined>;
function randomDivision(): Result<
number,
"cannot divide by zero" | "CannotBe>1"
> {
const [, firstOperand] = randomNumber().safeRet();
const [, secondOperand] = randomNumber().safeRet();
const [divErr, divRes] = safeDivision(firstOperand, secondOperand).safeRet();
if (typeof divErr !== "undefined") {
return err(divErr);
}
const [above1Err] = errIfAbove1(divRes).safeRet();
if (typeof above1Err !== "undefined") {
return err(above1Err);
}
return ok(divRes);
}
For now i am monkey patching like shown above. I was thinking of writing a small library but I thought I should first stop by here and see if maybe this could come handy and get included in a future neverthrow version.