Failure { error: Error }
Success<T> { value: T }
I assume you want value or error not value and error.```
if (result.error) { ... }
```
on a variable that possibly returns { value: T }
It'll complain that result may possibly have no attribute error
type Success<T> = { type: 'success', value: T};
type Error = { type: 'error', value: string };
type Result<T> = Success<T> | Error;
if (result.type === 'error') {
result.error;
} else {
result.value;
}
[1] https://www.typescriptlang.org/docs/handbook/advanced-types.... if ('error' in result) {
console.error(result.error); // ok: result inferred as 'Failure'
} else {
doThings(result.value) // ok: result inferred as 'Success<T>'
}
Even though you used string there, it's pretty type-safe because if you have a typo in your string, the inferred types will propagate other type errors if ('errors' in result) { // typo
console.error(result.errors); // Type error: result inferred as 'never'
} else {
doThings(result.value) // Type error: result inferred as Success<T> | Failure
}What do you expect here? The compiler is doing exactly what is expected.
{:ok, value}
or
{:error, err}
so, you would be correct.
Here's a small example:
```
function fetchCurrentWeather(){ return fetch(...) .catch(() => ({ error: new NetworkError(), value: null}) .then(res => res.json()) .then(maybeWeather => Weather.is(maybeWeather) ? { value: maybeWeather, error: null} : { error: new DecodeError(), value: null}) .catch(() => ({ error: new DecodeError(), value: null})) }
// And then you use it somewhere in you react app
async initialFetch(){ try { if (this.state.isLoading) return; this.setState({ isLoading: true, weather: null, error: null }); this.setState({ weather: await fetchCurrentWeather().then(reault => { if (result.error) throw result.error; return result.value; }) }); } catch (error) { this.setState({ error }) } finally { this.setState({ isLoading: false }); } }
render(){ return ( <div> {this.state.isLoading && ...} {this.state.weather && ...} {this.state.error && ...} </div> ) }
```
I know it is inefficient because creating a new error with a stack trace each time is bad for performance. However, you can also throw errors which do not extend Error to avoid this. Destructuring the output and explicitly checking for errors every time seems to be quite the hassle.
If you know that something truly isn't going to error then you can just force cast it as `foo as Success<T>`. That will still blow up at runtime if its not a `Success`.
Alternatively, you could introduce a monadic chaining that is able to pipe `Result<T>` objects through many functions then handle at the end.
Config it to maximum strictness!
Add in runtime type checks Parse, don't validate https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
Bring type to business logic, e.g.
RegisteredUser { userId: number, email: string }
AnonymousUser { userId: number, email: null }
User = RegisteredUser | AnonymousUser
Handle errors ala Rust,
Return errors/errortype rather than throws
Utilize structure to determine result, e.g.
Failure { error: Error, value: null }
Success<T> { error: null, value: T }
It is worth it. My experience with TypeScript is a whole lot different.
Conspiracy theory inducing facts:
- LogRocket's main use is monitoring bugs in browser application
- Airbnb is LogRocket's customer