import * as T from 'fp-ts/lib/Task'
import { pipe, flow } from 'fp-ts/lib/function'
const getHello: T.Task<string> = () => new Promise((resolve) => {
resolve('hello')
})
I understand the purpose of Task
and why is it important. The thing is that I don't know how to use it properly or compose with it, really.
If I just call getHello()
, it will give me Promise<pending>
:
console.log(getHello()) // returns Promise<pending>
if I do this, however:
const run = async () => {
const hello = await getHello()
console.log(hello) // prints 'hello'
}
it works.
but this:
const waitAndGet = async () => {
return await getHello()
}
console.log(waitAndGet()) // prints Promise<pending>
doesn't.
Moreover, how would I be able to compose with it? Like so:
const getHelloAndAddWorld = flow(
getHello(),
addAtEnd('world')
)
First, let’s understand what Task
really is.
export interface Task<A> {
(): Promise<A>
}
// note that this could also be written as
export type Task<A> = () => Promise<A>
A Task
is simply a function that returns a Promise
, so in your example calling getHello
would return a Promise<string>
.
console.log(getHello())
is the same as console.log(Promise.resolve('hello'))
, so this is why it would log something like Promise {<fulfilled>: "hello"}
, Promise<pending>
, or something else instead of hello
:
// Promise.resolve(foo) is the same as new Promise(resolve => resolve(foo))
const getHello = () => Promise.resolve('hello')
console.log(getHello())
For more information on promises, I recommend reading ‘Using Promises’ on MDN.
As for how to compose with it, since Task
is a Monad
you can use map
, ap
, chain
, apSecond
etc.
For example, let’s say addAtEnd
was defined like this:
const addAtEnd = (b: string) => (a: string): string => a + b
You can use this with getHello()
by using Task.map
:
import * as T from 'fp-ts/Task'
import { pipe } from 'fp-ts/function'
// type of map:
// export declare const map: <A, B>(f: (a: A) => B) => (fa: Task<A>) => Task<B>
// Task<string> which, when called, would resolve to 'hello world'
const getHelloAndAddWorld = pipe(
getHello,
T.map(addAtEnd(' world'))
)
// same as
const getHelloAndAddWorld = T.map(addAtEnd(' world'))(getHello)
Or if you wanted to log the value of that, you could use chainIOK
and Console.log
:
import * as Console from 'fp-ts/Console'
// type of T.chainIOK:
// export declare function chainIOK<A, B>(f: (a: A) => IO<B>): (ma: Task<A>) => Task<B>
// type of Console.log:
// export declare function log(s: unknown): IO<void>
// Note that IO<A> is a function that (usually) does a side-effect and returns A
// (() => A)
// Task<void>
const logHelloAndWorld = pipe(
getHelloAndAddWorld,
T.chainIOK(Console.log)
)
// same as
const logHelloAndWorld = pipe(
getHello,
T.map(addAtEnd(' world')),
T.chainIOK(Console.log)
)
To execute Task
s, simply call it:
logHelloAndWorld() // logs 'hello world'
For a simple introduction to functors, applicatives, and monads, Adit’s ‘Functors, Applicatives, And Monads In Pictures’ or Tze-Hsiang Lin’s JavaScript version of that are some good starting points.