Async tasks

Tasks that always succeed

If you’re working with asynchronous tasks that are guaranteed to succeed, use Task.

import { Task } from 'fp-ts/lib/Task'

const deepThought: Task<number> = () => Promise.resolve(42)

deepThought().then(n => {
  console.log(`The answer is ${n}.`)
})

Tasks that may fail

If you’re working with asynchronous tasks that may fail, use TaskEither. If the JSON in this example is malformed (try it!), an “I’m sorry” message is displayed.

import { fold } from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/pipeable'
import { tryCatch } from 'fp-ts/lib/TaskEither'

const fetchGreeting = tryCatch<Error, { name: string }>(
  () => new Promise(resolve => resolve(JSON.parse('{ "name": "Carol" }'))),
  reason => new Error(String(reason))
)

fetchGreeting()
  .then(e =>
    pipe(
      e,
      fold(err => `I'm sorry, I don't know who you are. (${err.message})`, x => `Hello, ${x.name}!`)
    )
  )
  .then(console.log)

Work with a list of tasks in parallel

JavaScript provides Promises.all to wait for a list of Promises.

Promise.all([Promise.resolve(1), Promise.resolve(2)]).then(console.log) // [1, 2]

With Tasks you can achieve the same using sequence. Both the Promise.all and the sequence approach run in parallel and wait until all results have arrived before they proceed.

import { task } from 'fp-ts/lib/Task'
import { array } from 'fp-ts/lib/Array'

const tasks = [task.of(1), task.of(2)]
array
  .sequence(task)(tasks)()
  .then(console.log) // [ 1, 2 ]

Run a list of tasks in sequence

If you need to run a list of Tasks in sequence, i.e. you have to wait for one Task to finish before you run the second Task, you can use the taskSeq instance.

import { array } from 'fp-ts/lib/Array'
import { pipe } from 'fp-ts/lib/pipeable'
import { delay, map, task, taskSeq } from 'fp-ts/lib/Task'

const log = <A>(x: A) => {
  console.log(x)
  return x
}

const tasks = [
  pipe(
    delay(200)(task.of('first')),
    map(log)
  ),
  pipe(
    delay(100)(task.of('second')),
    map(log)
  )
]

// Parallel: logs 'second' then 'first'
array.sequence(task)(tasks)()

// Sequential: logs 'first' then 'second'
array.sequence(taskSeq)(tasks)()

Work with a list of dependent tasks

If you need the result of on task before you can continue with the next, you can chain the tasks like so:

import { pipe } from 'fp-ts/lib/pipeable'
import { chain, task } from 'fp-ts/lib/Task'

pipe(
  task.of(2),
  chain(result => task.of(result * 3)),
  chain(result => task.of(result + 4))
)().then(console.log) // 10

Traverse: map and sequence

If you have a list of items that you need to map over before running them in sequence, you can use traverse, which is a shortcut for doing both operations in one step.

import { access, constants } from 'fs'
import { array } from 'fp-ts/lib/Array'
import { task } from 'fp-ts/lib/Task'

const checkPathExists = (path: string) => () =>
  new Promise(resolve => {
    access(path, constants.F_OK, err => resolve({ path, exists: !err }))
  })

const items = ['/bin', '/no/real/path']

array
  .traverse(task)(items, checkPathExists)()
  .then(console.log) // [ { path: '/bin', exists: true }, { path: '/no/real/path', exists: false } ]