-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to best emulate Rust's ?
operator?
#183
Comments
Unfortunately JS / TypeScript doesn't have an implicit return operator like Rust's However, by using One thing I noticed in your example is that your error types are the same. They're all string. But I see that you're changing the value of the string. There's a pattern in typescript that allows you to create a sum type whose values have different shapes. Example: export type RouteError =
| { type: 'NotFound'; context?: string }
| { type: 'Conflict'; context?: string }
| { type: 'Other'; error?: Error; context?: string, otherRandomField: SomeInterface }
| { type: 'MissingHeader' }
| { type: 'InvalidToken' }
| { type: 'InvalidSession' }
| { type: 'BadRequest'; context: string } Each value in this type / set can have very different shapes. But they all share the This might be helpful for you. So for your above example, what I would do is as follows: const exampleRequestA = (): ResultAsync<number, string> =>
wrap(exampleRequest())
.mapErr(() => 'something went wrong in request A')
.map((val) => val.some.nested.field)
const exampleRequestB = (val: number): ResultAsync<number, string> =>
wrap(exampleRequest(val))
.mapErr(() => 'something went wrong in request B')
.map((obj) => val + obj.other.nested.field) // compute intermediary sum
const exampleRequestC = (val: number): ResultAsync<number, string> =>
wrap(exampleRequest(val))
.mapErr(() => 'something went wrong in request C')
.map((obj) => val + obj.other.nested.field) // this would be the final sum
const requestChain = async (): Promise<Result<number, string>> =>
exampleRequestA()
.andThen(exampleRequestB)
.andThen(exampleRequestC) This may not be the most elegant solution, but it's a solution nonetheless. I've faced this issue in my side project as well. Here is an example where I have to accumulate values throughout the chain ( |
Ok done 🙂 |
Thanks for the inspiration, I'll experiment in this direction. As mentioned, the problem I was facing with
My example doesn't capture this well. The dependencies are much more complex then a linear chain of
Perhaps I need to combine let a: number
let b: number
let c: number
wrap(exampleRequest()).andThen(result => {
a = result.some.nested.field
return wrap(exampleRequest(a))
}).andThen(result => {
b = result.other.nested.field
return wrap(exampleRequest(a + b))
}).map(result => {
c = result.other.nested.field
return a + b + c
}) |
I understand. However, I personally haven't run into a situation that has so many interdependencies that have required me to try and find an alternative to "fusing" things together in a accumulator type. For example, that A hypothetical function that could be added to type AndThenFunc = (t: T) => ResultAsync<U, E> | Result<U, E>
ResultAsync<T, E>.andThenCollect<U>(f: AndThenFunc): ResultAsync<[U, T], E> { ... } It's conceptually the same as The whole point / goal being that you personally wouldn't have to pollute your code with your own version of this glue / fusing code. Is the above what you had in mind? |
Here is a full demo of the hoisting approach: async function exampleRequest(arg?: number): Promise<ResponseType> {
return Promise.resolve(42);
}
type WrappedError = {
msg: string,
originalError: Error,
}
function wrapPromise<T>(promise: Promise<T>, msg: string): ResultAsync<T, WrappedError> {
return ResultAsync.fromPromise(promise, (e) => ({
msg: msg,
originalError: e as Error
}));
}
function startChain(): ResultAsync<null, WrappedError> {
return okAsync(null)
}
function requestChain(): ResultAsync<number, WrappedError> {
// predefine stuff that should be remembered across requests
let a: number
let b: number
let c: number
return startChain().andThen(() => {
return wrapPromise(
exampleRequest(),
"Something failed in request A",
)
}).andThen(result => {
a = result
return wrapPromise(
exampleRequest(a),
"Something failed in request B",
)
}).andThen(result => {
b = result
return wrapPromise(
exampleRequest(a + b),
"Something failed in request C",
)
}).andThen(result => {
c = result
return ok(a + b + c)
})
} A few notes:
EDIT: One of your examples inspired me to re-write that using destructuring, which also looks pretty good: const requestChain: () => ResultAsync<number, WrappedError> = () => (
startChain().andThen(() =>
wrapPromise(
exampleRequest(),
"Something failed in request A",
).map(result => ({
a: result, // extract `a` here instead
}))
).andThen(({a}) =>
wrapPromise(
exampleRequest(a),
"Something failed in request B",
).map(result => ({
a: a, // forward
b: result, // extract `b` here instead
}))
).andThen(({a, b}) =>
wrapPromise(
exampleRequest(a + b),
"Something failed in request C",
).map(result => ({
a: a, // forward
b: b, // forward
c: result, // extract `c` here instead
}))
).andThen(({a, b, c}) =>
ok(a + b + c)
)
)
Yes, exactly. I assume the nested tuple approach would work with a similar destructuring on input side, but would perform the "accumulation" automatically, i.e., it spares the explicit (From my perspective the current API allows for a good enough approximation of the |
I added a new issue (#186) to track the |
I assume you are familiar with
?
operator in Rust? I'm wondering what is the best way to achieve something similar with neverthrow'sResultAsync
.REST APIs often require to execute a chain of requests that depend on each other, i.e., I cannot spawn them independently but need to await a successful result sequentially. In case of an error, I want to translate the error into a different error type. Here's an example:
It looks like I'm looking for some kind of
unwrapOrReturnError
. In Rust such chains simplify a lot when using the?
operator. Any thoughts what would be a nice solution with neverthrow?I feel simply using
map
doesn't scale so well, because each request would add a level of indentation (in the specific API case I had a chain of 8 requests). UsingandThen
seems tricky as well, because the steps have complex interdependencies, e.g. I would need the results of step 3+4 in step 7, but step 1+3 in step 8 or so.The text was updated successfully, but these errors were encountered: