@task.flow/task

0.1.0 • Public • Published

outtask

travis npm downloads js-standard-style

Library allows describing asynchronous operations that may fail, like HTTP requests or writing to a database and provides tools to manage them. Library also provides a light weight process / thread abstraction so you can get bunch of different tasks running concurrently.

Primary goal of this library is to provide an alternative to programming with side-effects in JS, by programming with managed effects. Task abstraction just describes operation, performing it is a job of scheduler, which provides a handy separation. Think of tasks as an items on the todo list - they just describe things to do, who will do those tasks and when is a separate concern, concern of scheduler.

Usage

All examples presume following import:

import Task from 'outtask'

Task <error, value>

In nutshell Task <error, value> type represents a task that may fail with a specific error or succeed with a specific value.

For example, maybe we have a task with the type Task<string, User>. This implies that when task is performed, it may either fail with a string message describing error or succeed with a value of User (instance). For example this task could be asking a server for a certain user.

Note: Thinking about task types is useful as that gives very specific insight what the result of performing that task will look like. Library is written in flow to take advantage of type checker and to let you catch all the possible errors associated with invalid uses of it early on.

succeed

A task that succeeds immediately when run with a value provided:

Task
  .succeed(42)
  .fork(console.log, econsole.error)

// => Log: 42

fail

A task that fails immediately when run with a value provided:

Task
  .fail("file not found")
  .fork(console.log, console.error)

// => Error: "file not found"

Mapping

map

Task
  .succeed(4)
  .map(x => x * 2)
  .fork(console.log, console.error)
// => Log: 8

map2

Task.map2((a, b) => a + b, Task.succeed(2), Task.succeed(3))
  .fork(console.log, console.error)
// => Log: 5

map3

Task.map3((a, b, c) => a + b + c,
          Task.succeed(2),
          Task.succeed(3),
          Task.succeed(4))
  .fork(console.log, console.error)
// => Log: 9

map4

Task.map4((a, b, c, d) => a * b - c + d],
          Task.succeed(2),
          Task.succeed(3),
          Task.succeed(4),
          Task.succeed(5))
  .fork(console.log, console.error)
// => Log: 7

map5

Task.map5((a, b, c, d, e) => a * b + c + d / e],
          Task.succeed(2),
          Task.succeed(3),
          Task.succeed(4),
          Task.succeed(5),
          Task.succeed(2))
  .fork(console.log, console.error)
// => Log: 12.5

chaining

chain

Chain together a task and a callback. The first task will run, and if it is successful, you give the another task from the callback which will be performed next.

Task
  .succeed(2)
  .chain(n => Task.succeed(n + 2)) // Succeed(4)
  .fork(console.log, console.error)

// => Log: 4

Task
  .fail('Boom')
  .chain(n => Task.succeed(n + 2)) // Succeed(4)
  .fork(console.log, console.error)

// => Error: 'Boom'

sequence

Start with a list of tasks, and turn them into a single task that returns an array. The tasks will be run in order one-by-one and if any task fails the whole sequence fails.

Task
  .sequence(fetch(url1), fetch(url2))
  .fork(console.log, console.error)


// => Log: [contentURL1, contentURL2]

Errors

capture

Recover from a failure in a task. If the given task fails, we use the callback to turn error into another fallback task.

Task
  .fail('Boom')
  .capture(error => Task.succeed(5))
  .fork(console.log, console.error)
// => Log: 5

Task
  .fail('Boom')
  .capture(error => Task.fail('Oops'))
  .fork(console.log, console.error)

// => Error: Oops

format

Transform the error value. This can be useful if you need a bunch of error types to match up.

Task
  .fail('Boom')
  .format(Error)
  .fork(console.log, console.error)

// => Error: Error('Boom')

Task
  .fail({ code: 15 })
  .format(JSON.stringify)
  .format(Error)
  .fork(console.log, console.error)

// => Error: Error('{code:15}')

recover

Transform the error value into success value. Useful if you need never failing tasks:

Task
  .fail('Boom')
  .map(value => { ok: value })
  .recover(error => { error: error })
  .fork(console.log, console.error)

// => Log: { error: 'Boom' }

Task
  .succeed('Data')
  .map(value => { ok: value })
  .recover(error => { error: error })
  .fork(console.log, console.error)

// => Log: { ok: 'Data' }

Bulit-in tasks

sleep

Task that wait for given number of milliseconds and then succeeds with void:

Task
  .sleep(50)
  .chain(_ => Task.succeed(5))
  .fork(console.log, console.error)

// => Log: 5 // prints in 50ms

requestAnimationFrame

Task succeeds with DOMHighResTimeStamp on next animation (It's just a requestAnimationFrame wrapped in task API):

Task
  .requestAnimationFrame()
  .fork(console.log, console.error)

// => Log: 124256.00000000001

Custom tasks

You can wrap arbitrary code into a task. API is intentionally similar to Promise API:

const store =
  (key:string, value:string):Task<Error, void> =>
  new Task((succeed, fail) => {
    try {
      window.localStorage[key] = value
      succeed()
    } catch (error) {
      fail(error)
    }
  })

Custom cancellable tasks

You can write cancellable task, API is pretty similar to custom tasks only difference is you need to provide a second abort function which will be passed whatever you return from first function in case task is aborted (or process is killed):

const fetch =
  (url:string):Task<Error, String> =>
  new Task((succeed, fail):XMLHttpRequest => {
    const request = new XMLHttpRequest()
    request.open('GET', url, true)
    request.responseType = 'text'
    request.onerror = event => {
      fail(Error(`Network request to ${url} has failed: ${request.statusText}`))
    }
    request.ontimeout = event => {
      fail(Error(`Network request to ${url} timed out`))
    }
    request.onload = event => {
      succeed(request.responseText)
    }

    request.send()
    return request
  }, (request:XMLHttpRequest):void => {
    request.abort()
  })

Process <exit, message>

Process <error, value> represents an execution of Task <error, value>. Many such processes can be spawn to execute different tasks concurrently. All the undocumented fields and methods of the Process class are considered internal implementation details that may change at any point without warning so do not depend on them in any way.

fork <x, a> (onSucceed:(v:a)=>void, onFail:(e:x)=>void):Process<x, a>

To run a task you need to provide success and failure handlers to a .fork which will start a light-weight process.

const process =
  Task
  .sleep(50)
  .chain(_ => Task.succeed(5))
  .fork(console.log, console.error)

Note: Most of the time you can just ignore process when forking a task, although not always following sections will illustrate those cases.

kill: <x, y, a> (process:Process<x, a>) => Task<y, void>

Sometimes you spawn a task, but later due to some circumstances you decide to abort it. In such cases you can kill process to abort whatever task it was busy with. As in an example below HTTP request will be aborted while in flight.

const process =
  fetch('http://elm-lang.org')
  .map(JSON.parse)
  .fork(console.log, console.error)

Task
  .kill(process)
  .fork(console.log, console.error)

spawn <x, y, a> (task:Task<x, a>) => Task<y, Process<x, a>>

Run a task in its own light-weight process. In the following example, task1 and task2 will be concurrent. If task1 makes an HTTP request, then task2 will do some work while request is pending.

task1
  .spawn()
  .chain(process => task)

Install

npm install outtask

Prior art

Package Sidebar

Install

npm i @task.flow/task

Weekly Downloads

2

Version

0.1.0

License

MIT

Unpacked Size

186 kB

Total Files

73

Last publish

Collaborators

  • gozala