I recently started my first Elm project, and I’m yet to fully wrap my head around Task
s, and specifically, how to set off another Task
if the previous one fails. I finally managed to get it working after some trial and error, so I’m sharing it here case someone else (or my future self) finds it useful!
(I broke it down in detail but if you’re an experienced Elm developer you can probably just skip to the “My solution” section )
The problem
Sometimes, I need to know where in the page a user has scrolled. I can do this using Browser.Dom.getViewport
like this:
type Msg
= GotViewport Browser.Dom.Viewport
getViewport : Cmd Msg
getViewport =
Task.perform GotViewport Browser.Dom.getViewport
However, most of the time I’m not scrolling the body directly, but rather a div
that’s the size of a screen. That means I need to use Browser.Dom.getViewportOf
instead:
type Msg
= GotViewport
| GotDivViewport (Result Browser.Dom.Error Browser.Dom.Viewport)
getDivViewport : Cmd Msg
getDivViewport =
Task.attempt GotDivViewport (Browser.Dom.getViewportOf "terrain")
-- (also getViewport code as before)
…and then keep juggling both functions to call whichever is appropriate. That includes logic to match getDivViewport
against Ok
and Error
values, and in the latter case to run the getViewport
function (which will later be processed).
To avoid all this, I was searching for a way to have just one getViewport
function that tries the div-specificgetViewportOf "terrain"
first, and falls back to the full-body getViewport
if that fails.
My solution
It took me a while to get it working, and I’m still new enough to types that this final form was more guesswork and following Elm’s hints rather than independently working out how this is to be done;
type Msg
= GotViewport Browser.Dom.Viewport
getViewport : Cmd Msg
getViewport =
Browser.Dom.getViewportOf "terrain"
|> Task.onError (\_ -> Browser.Dom.getViewport)
|> Task.andThen (\result -> Task.succeed result)
|> Task.perform GotViewport
The way I understand this is that onError
runs the task, and, if it fails, returns a new task (which in this case is guaranteed to succeed). On the other hand, if the task succeeds, andThen
returns a dummy task that is again guaranteed to succeed. Finally, Task.perform
collects the newly generated task (real or dummy) and processes it, sending the result via GotViewport
.
Update: just realised as I was typing this that the andThen
line is superfluous; the code still works even if I remove it!
Clarifications
Am I right in thinking that andThen
and onError
don’t actually “run” the tasks but merely modify them?
Also, does this code (with the andThen
line removed) look fine, or did I do something weird?
I was a bit confused because neither the andThen
nor onError
docs mentioned Task.attempt
or Task.perform
. Now that I look at it, I notice the examples’ return types are tasks too, so that explains why. Perhaps a more complete example in the docs will help? I don’t mind raising a PR if someone can give me feedback…