Task

Tasks make it easy to describe asynchronous operations that may fail, like HTTP requests or writing to a database.

type alias Task x a = Task x a

Here are some common tasks:

In each case we have a Task that will resolve successfully with an a value or unsuccessfully with an x value. So Browser.Dom.focus we may fail with an Error if the given ID does not exist. Whereas Time.now never fails so I cannot be more specific than x. No such value will ever exist! Instead it always succeeds with the current POSIX time.

More generally a task is a description of what you need to do. Like a todo list. Or like a grocery list. Or like GitHub issues. So saying "the task is to tell me the current POSIX time" does not complete the task! You need perform tasks or attempt tasks.

perform : (a -> msg) -> Task Never a -> Cmd msg

Like I was saying in the Task documentation, just having a Task does not mean it is done. We must command Gren to perform the task:

import Task
import Time

type Msg
    = Click
    | Search String
    | NewTime Time.Posix

getNewTime : Cmd Msg
getNewTime =
    Task.perform NewTime Time.now

So we have changed a task like "make delicious lasagna" into a command like "Hey Gren, make delicious lasagna and give it to my update function as a Msg value."

attempt : (Result x a -> msg) -> Task x a -> Cmd msg

This is very similar to perform except it can handle failures! So we could attempt to focus on a certain DOM node like this:

-- gren install gren-lang/browser


import Browser.Dom
import Task

type Msg
    = Click
    | Search String
    | Focus (Result Browser.DomError {})

focus : Cmd Msg
focus =
    Task.attempt Focus (Browser.Dom.focus "my-app-search-box")

So the task is "focus on this DOM node" and we are turning it into the command "Hey Gren, attempt to focus on this DOM node and give me a Msg about whether you succeeded or failed."

execute : Task Never a -> Cmd msg

Sometimes we want to give a command without being told how it went. Maybe we are logging something to the screen, or changing the scroll position of the window. In either case, there's really nothing for us to do afterwards. In those cases we can use execute.

executeCmd : Task Never (Cmd msg) -> Cmd msg

This is very similar to execute except the resulting Cmd is run. Maybe we want to shut down a node program once a task is done.

import Node
import Task
import Stream.Log

Stream.Log.line stdout "Exiting program"
    |> Task.map (\{} -> Node.exit)
    |> Task.executeCmd

Chains

andThen : (a -> Task x b) -> Task x a -> Task x b

Chain together a task and a callback. The first task will run, and if it is successful, you give the result to the callback resulting in another task. This task then gets run. We could use this to make a task that resolves an hour from now:

import Process
import Time

timeInOneHour : Task x Time.Posix
timeInOneHour =
    Process.sleep (60 * 60 * 1000)
        |> Task.andThen (\_ -> Time.now)

First the process sleeps for an hour and then it tells us what time it is.

await : Task x a -> (a -> Task x b) -> Task x b

This is like andThen but the arguments are reversed. The callback is the last argument, instead of the first. This makes it easier to write imperative code where each callback involves more logic.

import Process
import Time

timeInOneHour : Task x Time.Posix
timeInOneHour =
    Task.await (Process.sleep <| 60 * 60 * 1000) <| \_ ->
        Time.now

(a)wait for an hour, then fetch the current time.

succeed : a -> Task x a

A task that succeeds immediately when run. It is usually used with andThen. You can use it like map if you want:

import Time


timeInMillis : Task x Int
timeInMillis =
    Time.now
        |> Task.andThen (\t -> Task.succeed (Time.posixToMillis t))
fail : x -> Task x a

A task that fails immediately when run. Like with succeed, this can be used with andThen to check on the outcome of another task.

type Error
    = NotFound

notFound : Task Error a
notFound =
    Task.fail NotFound
sequence : Array (Task x a) -> Task x (Array a)

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

Task.sequence [ Task.succeed 1, Task.succeed 2 ] == Task.succeed [ 1, 2 ]
concurrent : Array (Task x a) -> Task x (Array a)

Start with an array of tasks, and turn them into a single task that returns a array. The tasks will be run concurrently and if any task fails the whole array fails.

Task.concurrent [ Task.succeed 1, Task.succeed 2 ] == Task.succeed [ 1, 2 ]

Additionally if any task fails, any tasks already in-flight will be cleaned up (e.g. an in-flight HTTP request will be cancelled).

Note: Why use sequence over concurrent? Maybe you have an expensive operation, like making 100 HTTP requests. You might want to do these sequentially rather than all at once to avoid overwhelming the server.

A note on concurrency and parallelism:

Why is there only concurrent and not parallel? Because JavaScript is single-threaded, it's not possible currently to have true parallelism in gren.

  • A parallel task : multiple subtasks can be run at exactly the same time on multiple processors or CPU cores.
  • A concurrent task : multiple subtasks can be in progress at the same time but not executing at exactly the same time.

In practice this means CPU bound tasks (e.g. generate a cryptographic hash) won't see a speed boost from concurrent. IO bound tasks however (e.g. make a HTTP request and wait until the response comes back) may get a noticable speed boost from concurrent.

Maps

map : (a -> b) -> Task x a -> Task x b

Transform a task. Maybe you want to use Time to figure out what time it will be in one hour:

import Task exposing (Task)
import Time


timeInOneHour : Task x Time.Posix
timeInOneHour =
    Task.map addAnHour Time.now

addAnHour : Time.Posix -> Time.Posix
addAnHour time =
    Time.millisToPosix (Time.posixToMillis time + 60 * 60 * 1000)
map2 : (a -> b -> result) -> Task x a -> Task x b -> Task x result

Put the results of two tasks together. For example, if we wanted to know the current month, we could use Time to ask:

import Task exposing (Task)
import Time


getMonth : Task x Int
getMonth =
    Task.map2 Time.toMonth Time.here Time.now

Note: Say we were doing HTTP requests instead. map2 starts both requests concurrently and if either of them fails, the whole thing fails! If one request fails and the other is still in-flight, the in-flight task will be automatically cancelled and cleaned up.

map3 :
(a -> b -> c -> result)
-> Task x a
-> Task x b
-> Task x c
-> Task x result
andMap : Task x a -> Task x (a -> b) -> Task x b

Apply the results of two tasks together if they succeed. Both tasks are run concurrently, and if either task fails the whole task fails:

Task.succeed ((+) 2)
    |> Task.andMap (Task.succeed 3)
--> Task.succeed 5

Task.succeed ((+) 2)
    |> Task.andMap (Task.fail "oh dear")
--> Task.fail "oh dear"

This can be used to do Task.mapN for any number of arguments - useful when map2 or map3 isn't enough:

Task.succeed (\a b c d -> a + b + c + d)
    |> Task.andMap (Task.succeed 1)
    |> Task.andMap (Task.succeed 2)
    |> Task.andMap (Task.succeed 3)
    |> Task.andMap (Task.succeed 4)
-- Task.succeed 10

NOTE: for running an array of tasks at the same time see concurrent.

Errors

onError : (x -> Task y a) -> Task x a -> Task y a

Recover from a failure in a task. If the given task fails, we use the callback to recover.

Task.fail "file not found"
  |> Task.onError (\msg -> Task.succeed 42)
  -- Task.succeed 42

Task.succeed 9
  |> Task.onError (\msg -> Task.succeed 42)
  -- Task.succeed 9
mapError : (x -> y) -> Task x a -> Task y a

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

type Error
    = Http Http.Error
    | WebGL WebGL.Error

getResources : Task Error Resource
getResources =
    Task.sequence
        [ Task.mapError Http serverTask
        , Task.mapError WebGL textureTask
        ]