Closed rrichardson closed 2 years ago
I should also mention that using Result.is(result)
in this case results in an error later on, because it doesn't know the type of unwrapErr()
Maybe the fix is to make a version of is()
that is typed? is this possible?
Result is a function and not the class constructor, and the ResultType is ideally an implementation detail and not part of the toolkit.
I'm not entirely sure where you are losing type information. If you already have a typed Result<T, E> then why do you need to check with instanceof
? If you don't have that, then I don't think you are in a position to infer it.
It would help to have a better idea of the problem and your current workaround that is solving it.
Adding type parameters to is
to inform the type guard is equivalent to doing x as Result<_, _>
and I prefer the latter as it's at least clear you are telling the compiler something.
Or am I missing something?
I don't have the type information. This is in a middleware function which unwraps and wraps API results. Unfortunately, not everything yet returns Result :(
If I use the instance of and isErr() then TS pretty much just assumes that the result
is still any
if (result instanceof Result && result.isErr()) {
const e = result.unwrapErr();
return handleErrorResponse(e, res);
}
where handleErrorResponse = <E extends Error>(e: E, res: Response): void
But if I use Result.is()
then it narrows the type into Result<unknown, unknown>
. Apparently I'm not allowed to pass unknown
into handleErrorResponse
The deal with TypeScript is that you won't be able to infer the correct types if you don't know them in advance or have some kind of type-guard. The Result.is()
method asserts that you are working with a ResultType
but does nothing else.
Additionally, the E
is not necessarily an Error
or any variant of it, rather it's just the value you that you choose to use to represent an Error. Do you know for certain that the e
in your script above is unquestionably an Error
if that branch of code executes? If so, you can just tell the compiler that it is. It's not a perfect solution, but it's like a stop-gap until the rest of your codebase is typed:
if (Result.is(result) && result.isErr()) {
const e = result.unwrapErr() as Error;
return handleErrorResponse(e, res);
}
You could also make your own type guard function, which checks the Result type and provides appropriate typing. This is the solution I would be most likely to use.
function isResultError(val: unknown): val is Result<unknown, Error> {
return Result.is(val) && val.isErr() && val.unwrapUnchecked() instanceof Error;
}
if (isResultError(result)) {
const e = result.unwrapErr();
return handleErrorResponse(e, res); // Should be typed now
}
To follow on from the issue raised, My assumption is that you are hoping for something like this:
if (Result.is<string, Error>(val)) {
const e = val.unwrapErr(); // Compiler knows val is Result<string, Error>
}
Such a function might be possible to make, but the inner workings would not be able to use the type parameters at runtime and would thus essentially be requiring that the developer enters the correct types (and thus, defeats the point of TypeScript). The function would just be this:
function isResult<T, E>(val: unknown): val is Result<T, E> {
return Result.is(val);
}
So, I'm happy to leave this open for a day or two for if you have any follow-ups but I think this issue will be closed as the function isn't really possible to implement in a useful way,.
That's fine by me. Thanks for the explanation.
This is a minor nit, as I can work around this by using
import { ResultType } from "oxide.ts/dist/result"
I use ResultType, because, unfortunately, I have to use
instanceof
for type inspection, e.g.typescript thinks that
Result
is{}
butinstanceof ResultType
works.If you know another way around this, I'm all ears.