After exploring various examples of using TaskEither
for tasks like making HTTP requests or reading files, I am now attempting to simulate the process of retrieving an item from a database by its ID. The possible outcomes of this operation could be:
- The item is found
- No item was found with the specified ID
- An error occurred (e.g., DB connection issue)
An appropriate interface for handling such scenarios would be
TaskEither<Error, Option<A>>
:
type TaskEO<A> = TaskEither<Error, Option<A>>
Since the result will be sent as an HTTP response (in response to a GET
query), it is crucial to clearly differentiate between the three potential outcomes mentioned above. The corresponding response codes would be:
- 200 + payload
- 404
- 500
I have devised the following code snippet to map these scenarios into their respective HTTP responses:
import * as O from "fp-ts/Option";
import * as TE from "fp-ts/TaskEither";
import * as E from "fp-ts/Either";
import { pipe } from "fp-ts/function";
type TaskEO<A> = TE.TaskEither<Error, O.Option<A>>;
const getGoodStuff = (id: string): TaskEO<string> => TE.of(O.some(`result for ${id}`));
const getBadStuff = (id: string): TaskEO<string> =>
TE.left(new Error(`failed fetching ${id}`));
const getEmptyStuff = (id: string): TaskEO<string> => TE.of(O.none);
getGoodStuff("123")()
.then((e) =>
pipe(
e,
E.fold(
(error) => `500: Internal Server Error`,
(stuff) =>
pipe(
stuff,
O.match(
() => `404: Not Found Error`,
(value) => `200: Yay we got: "${value}"`
)
)
)
)
)
.then(console.log);
Feel free to replace the getGoodStuff
function call with any other get...Stuff
functions and observe how they handle different responses appropriately!
Now comes the question for YOU, dear reader - Do you think there's a more efficient way to structure this composition? Share your thoughts and suggestions on optimizing the code!
EDIT I've refined the code to something like this:
enum HttpResponseCode {
OK = 200,
NOT_FOUND = 404,
INTERNAL_SERVER_ERROR = 500
}
type HttpResponse = {
code: HttpResponseCode;
payload: unknown;
}
const toHttpResponse = <A>(e: E.Either<Error, O.Option<A>>): HttpResponse =>
E.fold(
(error) => ({ code: HttpResponseCode.INTERNAL_SERVER_ERROR, payload: "Internal Server Error" }),
O.match(
() => ({ code: HttpResponseCode.NOT_FOUND, payload: "Resource not found" }),
(value) => ({ code: HttpResponseCode.OK, payload: value })
)
)(e)
This can then be utilized in an Express route handler like so:
async (req, res) => {
await findStuffById(req.params.stuffId)()
.then(toHttpResponse)
.then(({ code, payload }) => res.status(code).send(payload))
}