Task
Tasks make it easy to describe asynchronous operations that may fail, like HTTP requests or writing to a database.
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.
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."
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."
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
.
Chains
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)
|> andThen (\_ -> Time.now)
First the process sleeps for an hour and then it tells us what time it is.
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.
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
|> andThen (\t -> succeed (Time.posixToMillis t))
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 =
fail NotFound
Start with a list of tasks, and turn them into a single task that returns a list. The tasks will be run in order one-by-one and if any task fails the whole sequence fails.
sequence [ succeed 1, succeed 2 ] == succeed [ 1, 2 ]
Maps
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)
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
does each task in
order, so it would try the first request and only continue after it succeeds.
If it fails, the whole thing fails!
Errors
Recover from a failure in a task. If the given task fails, we use the callback to recover.
fail "file not found"
|> onError (\msg -> succeed 42)
-- succeed 42
succeed 9
|> onError (\msg -> succeed 42)
-- succeed 9
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 =
sequence
[ mapError Http serverTask
, mapError WebGL textureTask
]