Replies: 7 comments 1 reply
-
Great question! I'll try to write up some notes on this by the end of the week! One extra complication here: I assume these are all async operations? |
Beta Was this translation helpful? Give feedback.
-
Yes, that's right. Apart from the code that starts Node.js, all the logic goes on in
|
Beta Was this translation helpful? Give feedback.
-
Another similar pattern is doing an upsert:
Again, both the read (checking if it exists) and the two possible write operations (updating or creating) are asynchronous and return a |
Beta Was this translation helpful? Give feedback.
-
Thanks for that clarification – I'll write up a pattern that does work with |
Beta Was this translation helpful? Give feedback.
-
Sorry, this got buried among many other responsibilities! I've a draft of some blog posts that dig into this, but it'll likely be late October or November before I have them published. |
Beta Was this translation helpful? Give feedback.
-
This obviously got dropped hard, but I haven't forgotten about it. I may do some follow-on work on #25 inspired by https://github.com/hojberg/seidr#asyncresult, which is fairly close to the experimental implementations I've done in this space in the past. |
Beta Was this translation helpful? Give feedback.
-
I'm late to the party but just wanted to share what I came up with to deal with the complexity of dependent async call In my case, I have an adapter that does two async call to fetch all the data need to mount the necessary payload, and the second async function depends on the result of the first one. The abstract originally looked this: import { CustomError, Struct0, Struct1, FullStruct } from '../shared'
import { Result } from 'true-myth'
export abstract class AbstractAdapter {
public abstract mountData(): Promise<Result<FullStruct, CustomError>>
private abstract fetchData0(): Promise<Result<Struct0 | CustomError>>
private abstract fetchData1(struct0: Struct0): Promise<<Struct1, CustomError>>
} Implementing the function signatures above made me feel like I was falling into a callback hell. I was mapping over a result, using the unwrapped value to call another function that also returns a result and so on so forth. export class AdapterImplementation extends AbstractAdapter {
public async mountData(): Promise<Result<Struct1, CustomError>> {
// Result<Promise<Result<Struct1, CustomError>>, CustomError>
const result = (await this.fetchData0())
.map(struct0 => this.fetchData1(struct0))
.map(struct1 => struct1)
// then unwrap all the layers to get to struct1
}
private async fetchData0(): Promise<Result<Struct0, CustomError>> {
try {
const response = await fetchExternalStruct0()
return Result.ok(this.adaptStruct0(response))
} catch(error) {
return Result.err(new CustomError((error as Error).message))
}
}
private async fetchData1(struct0: Struct0): Promise<Result<Struct1, CustomError>> {
try {
const response = await fetchExternalStruct1(struct0)
return Result.ok(this.adaptStruct1(response))
} catch(error) {
return Result.err(new CustomError((error as Error).message))
}
}
private adaptStruct0({ externalField0, externalField1 }: ResponseExternalStruct0): Struct0 {
return {
field0: Number(externalField0),
field1: Number(externalField1),
}
}
private adaptStruct1({ externalField2, externalField3 }: ResponseExternalStruct1): Struct1 {
return {
field2: Number(externalField2),
field3: Number(externalField3),
}
}
} I decided to go with something a little more idiomatic at the cost of not returning a Result from every single function. import { CustomError, Struct0, Struct1, FullStruct } from '../shared'
import { Result } from 'true-myth'
export abstract class AbstractAdapter {
public abstract mountData(): Promise<Result<FullStruct, CustomError>>
private abstract fetchData0(): Promise<Struct0 | CustomError>
private abstract fetchData1(struct0: Struct0): Promise<Struct1 | CustomError>
} The functions that fetch external data now returns a export class AdapterImplementation extends AbstractAdapter {
public async mountData(): Promise<Result<FullStruct, CustomError>> {
const struct0 = await this.fetchData0()
if (struct0 instanceof CustomError)
return Result.err(struct0)
const struct1 = await this.fetchData1(struct0)
if (struct1 instanceof CustomError)
return Result.err(struct1)
return Result.ok({
...struct0,
...struct1,
})
}
public async fetchData0(): Promise<Struct0 | CustomError> {
try {
const response = await fetchExternalStruct0()
return this.adaptStruct0(response)
} catch(error) {
return new CustomError((error as Error).message)
}
}
public async fetchData1(struct0: Struct0): Promise<Struct1 | CustomError> {
try {
const response = await fetchExternalStruct1(struct0)
return this.adaptStruct1(response)
} catch(error) {
return new CustomError((error as Error).message)
}
}
private adaptStruct0({ externalField0, externalField1 }: ResponseExternalStruct0): Struct0 {
return {
field0: Number(externalField0),
field1: Number(externalField1),
}
}
private adaptStruct1({ externalField2, externalField3 }: ResponseExternalStruct1): Struct1 {
return {
field2: Number(externalField2),
field3: Number(externalField3),
}
}
} In some extent, returning Do you guys see any way I can improve this implementation? Spot any misuse of the lib? |
Beta Was this translation helpful? Give feedback.
-
Hi, here's a use case where I need to perform some more complex IO (function names simplified here to be as generic as possible):
My app needs to fetch data from a third party API.
Since such calls are expensive, I want to first hit a local Mongo cache.
There are two cases:
readMongo(id)
returns atrue-myth
Result
)readAPI(id)
returns atrue-myth
Result
)writeMongo(id, data)
returns atrue-myth
Result
)true-myth
Result
)As mentioned, all IO operations return
Result
types, that contain either the proper result of the operation or anError
object. How would you mix the three IO operations inside a single function that also returns aResult
? (Which can be either the requested data asResult.ok
, or aResult.err
(containing all the various ways in which the three IO operations can fail)).One way I see is to just go back to old imperative land and do dirty stuff like:
Is there a better, idiomatic way? Or is IO entirely to be avoided inside
true-myth
methods likemapOrElse
etc.?Beta Was this translation helpful? Give feedback.
All reactions