I'm learning fp-ts
now and try to replace some parts of my API with its approach.
Simple example:
- get
itemId
with request - use this
itemId
with async function to get (or not) the item
The only way I found is:
const itemId: E.Either<string, Item["id"]> = pipe(
body.itemId,
E.fromNullable('Item ID is missed')
);
const item: E.Either<string, Item> = E.isLeft(itemId)
? itemId
: pipe(
await getItem(itemId.right),
E.fromNullable('cant find item')
);
It works. But is it right way? Is it any way to do it all in one pipe
function?
Thank you.
CodePudding user response:
There are a few layers to this question so I'll present a possible solution and walk through how it works.
First, fp-ts
uses Task
s to represent asynchronous work, so most of the time when working with async code in fp-ts
you first convert the code to a Task
or TaskEither
(more on that in a second). First, we need a function to perform the getItem
task and return an Either
instead of null
when null is returned. This can be defined as:
import * as TE from 'fp-ts/lib/TaskEither';
import * as T from 'fp-ts/lib/Task';
// flow is function composition and avoids some syntax.
// This can be thought of a "point-free" although internally I use a
// not point-free function.
const getItemTE: (id: number) => TE.TaskEither<string, Item> = flow(
getItem,
// A task must be a function with no arguments that returns a promise,
// This takes the result of calling getItem and returns a function that
// returns that response in order to match the Task interface
(promisedItem) => () => promisedItem,
// We convert that task into a `TaskEither`, to get the type we want,
// but note that this value will never be `left` because the underlying
// task was returning `Item | null`. Both of those values are considered
// a Right.
TE.fromTask,
// Converting to a TaskEither allows us to chain, which will hand us the
// unwrapped `right` value and expects us to return a new TaskEither.
// In this case, we want the task either we get by converting null
// values into Eithers using the `fromNullable` function.
TE.chain(TE.fromNullable("can't find item")),
);
Now we have a function that takes a number and produces a TaskEither
which will fetch the item.
Next you want to use this function with your body
object. You can use the newly created function along with body
to get an item:
// Note: at this point the Promise will be created so
// any IO is going to be kicked off here, not when the value
// is eventually awaited.
const item: TE.TaskEither<string, Item> = pipe(
body.itemId,
// This will wrap up the value into a task either
// which enables us to chain.
TE.fromNullable('Item id is missed'),
// Now we just chain with the newly created function.
// If the value is `right` then the value is fetched,
// otherwise the 'Item id is missed' value is stored
// in left and is returned.
TE.chain(getItemTE)
);
Finally, you can do something with the value as follows:
async function doSomethingWithItem(item: TE.TaskEither<string, Item>) {
pipe(
// The item will already have been fetched at this point due
// to the way the code is written. If you want to avoid fetching
// the object right away, you might need to change around
// the line where I went from the `getItem` response to a Task
await item(),
E.fold(
(err) => console.error(err),
(item) => {
console.log(`Got item with id: ${item.itemId}`);
},
)
);
}
doSomethingWithItem(item);