I have the following program that works fine when none of the functions is async.
interface Product {
count: number
pricePerItem: number
}
interface Tax {
tax: number
}
interface Delivery {
delivery: number
}
interface PTD { //ProductTaxDelivery
p: Product
t: number
d: number
}
function getProduct(): Either<Error, Product> {
return E.right({ count: 10, pricePerItem: 5 })
}
function getTax(p: Product): Either<Error, number> {
return E.right(p.pricePerItem * p.count * 0.085)
}
function getDelivery(p: Product): Either<Error, number> {
return E.right(p.count * 0.05)
//or maybe return E.left(Error('some error in delivery happened'))
}
function run(): Either<Error, PTD> {
return pipe(
E.Do,
E.bind('p', getProduct),
E.bind('tax', ({p}) => getTax(p)),
E.bind('delivery', ({p}) => getDelivery(p)),
E.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
E.fold(
(e) => {
console.log(`error: ${e}`)
},
(it) => {
console.log(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
}
)
)
}
main()
The question I'm having is if one of my functions, for example getDelivery()
is async, then I'm not sure how to solve it.
Here's what I have tried:
function getDelivery(p: Product): TaskEither<Error, number> {
return TE.right(p.count * 0.05)
}
TE.bind('delivery', ({p}) => getDelivery(p)),
and many other variations, but all ended up in compiler errors.
The equivalent in imperative style is something like:
const getDelivery = async (p: Product) => {
return await something()
}
const run = async (): PTD => {
const product = getProduct()
const tax = getTax(product)
const delivery = await getDelivery(product)
return {
p: product, t: tax, d: delivery
}
}
What is the correct functional way (that I think involves both Either
and TaskEither
) using fp-ts
?
Update: I also tried to replace Either with TaskEither, E with TE everywhere, but the problem is now a compiler error when I tried to fold
in main()
. Here's the code that replaces:
function getProduct(): TaskEither<Error, Product> {
return TE.right({ count: 10, pricePerItem: 5 })
}
function getTax(p: Product): TaskEither<Error, number> {
return TE.right(p.pricePerItem * p.count * 0.085)
}
function getDelivery(p: Product): TaskEither<Error, number> {
return TE.right(p.count * 0.05)
}
function run(): TaskEither<Error, PTD> {
return pipe(
TE.Do,
TE.bind('p', getProduct),
TE.bind('tax', ({ p }) => getTax(p)),
TE.bind('delivery', ({ p }) => getDelivery(p)),
TE.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
TE.fold(
(e) => {
console.log(`error: ${e}`)
},
(it) => {
console.log(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
//doNonFunctional()
}
)
)
}
main()
On line with (e) => {
, the compiler error says:
error TS2345: Argument of type '(e: Error) => void' is not assignable to parameter of type '(e: Error) => Task<unknown>'.
Type 'void' is not assignable to type 'Task<unknown>'.
Update 2 OK, so I get the code to compile but no output when the program runs
const printError = (e: Error): T.Task<unknown> => {
console.log(`error: ${e}`)
return () => Promise.resolve()
}
const printPTD = (ptd: PTD): T.Task<unknown> => {
console.log(`ok ${ptd.p.count} ${ptd.p.pricePerItem} ${ptd.t} ${ptd.d}`)
return () => Promise.resolve()
}
function run(): TaskEither<Error, PTD> {
return pipe(
TE.Do,
TE.bind('p', getProduct),
TE.bind('tax', ({ p }) => getTax(p)),
TE.bind('delivery', ({ p }) => getDelivery(p)),
TE.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
TE.fold(
(e) => printError(e),
(ptd) => printPTD(ptd)
)
)
}
main()