2
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')
)
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
padowbr
  • 31
  • 1
  • 3
  • 4
    `async` functions **always** return promises, that's nothing to do with fp-ts. [`Task`](https://gcanti.github.io/fp-ts/modules/Task.ts.html) just seems to be an alias for something callable that returns a promise, which an `async` function would also be. – jonrsharpe Jan 07 '21 at 23:03

1 Answers1

9

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 Tasks, 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.

Lauren Yim
  • 12,700
  • 2
  • 32
  • 59
  • thank you so much for taking your time to explain that to me, it means a lot! i get it better now. `Task`'s `map` takes a pure program and applies to `Task` before it even gets resolved using `then`. i know it's another question and I am sorry, but I tried doing the same as you did up there and it gives me an error. besides that, do you know if there is any way to pluck the value out of the Task? if you could look into it: https://codesandbox.io/s/young-field-lnzi2?file=/src/index.ts – padowbr Jan 08 '21 at 01:10
  • or, to put in better words, how do I execute it after composing it? – padowbr Jan 08 '21 at 01:14
  • @padowbr Sorry, I forgot to add such a simple thing! To execute a `Task`, simply call it: `logHelloAndWorld()`. This is the same for `IO`s as well. – Lauren Yim Jan 08 '21 at 03:30
  • And sorry about the error, my bad. I fixed it in my answer; it should be `getHello`, and not `getHello()`. – Lauren Yim Jan 08 '21 at 03:43
  • it's okay! thank you so much. but what if, instead of logging the resolved value to the console, I want to return the value itself? – padowbr Jan 08 '21 at 04:29
  • @padowbr You [can't return `'hello world'` directly](https://stackoverflow.com/q/14220321/8289918), but you can return a promise resolving to it using `return getHello()`. However, returning a `Task` using `return getHello` is more useful when using fp-ts – Lauren Yim Jan 08 '21 at 08:43
  • awesome, yes, i've heard that keeping things in the elevated world as long as possible in functional programming is recommended. but what about if I need to return what's in the Task to, for example, a client? i know it's a lot of questions and i swear this is the last one. sincerely, thank you so much for taking your time. i made an example here: https://codesandbox.io/s/young-field-lnzi2?file=/src/index.ts – padowbr Jan 08 '21 at 18:39
  • i understand you can return a promise but in this case, the promise is already resolved but wrapped in the Task. i'm sorry if this sounds dumb :/ – padowbr Jan 08 '21 at 18:40
  • nevermind, i got it! since Task is basically a Promise, you can use `await` or `then` to get the value at the end. thank you! – padowbr Jan 08 '21 at 21:26