Using Task to send HTTP requests


#1

Hi, I’d like to know about the following things. Please give me feedback.

  • Is using Task for HTTP requests a bad practice? If so, why?
  • How do people use HTTP requests in real projects?

A bit too long, sorry!

Context

elm/http 2.0.0 came out recently, which is great for anyone who want file upload and simple requests. However, I found that using Task with the new API is quite difficult comparing with the 1.x API. Yes, it is very simple when we use Cmd, but when switching to Task for some reason, we should spend a high cost.

The doc comment on Http.task says:

Just like request, but it creates a Task. This makes it possible to pair your HTTP request with Time.now if you need timestamps for some reason. This should be quite rare.

Why? No reason is written there. Now I’m in an uncomfortable state, because I use Task Http.Error Something everywhere in a project at work but it looks like Task API is going to be deprecated or finally removed in the future. I don’t know, but at least Evan thinks tasks should be quite rare.

I remember the old elm-spa-example was also using Task.map2 for initializing some pages, but everything was completely reachitected and now uses Cmd.batch instead. package.elm-lang.com also uses Cmd.batch.

So I want to know using Tasks is a bad practice or not.

Our Project

In our project, most APIs are designed to be RESTful. One resource at one endpoint. So we often need to get multiple data at once to initialize the pages. Using Task.map2 and Task.map3 to gather the data, Task.andThen to get the related data and Task.onError to fallback or retry. So every request function is Task based.

I know functions like Task.map3 are not parallel. But Cmd.batch requires more complex code than Task.map*. It requires 3 Maybe (or Result, etc.) fields in Model, 3 branches in Msg and a bit more complex pattern matching in view function.
Yes, it has some benefit. Every request can be tracked as Msg, rendering the partial data faster even if some other part fails, etc. But …honestly, these are not the first thing for us.

One day I faced a problem where an API is unstable and sometimes failed for some reasons. Then I quickly modified a function to retry at most 10 times, without breaking anything. The reviewer had never learned Elm but quickly understand how I fixed the behavior. It was a very nice experience!
Of course, I could use Cmd to show “retrying 6…7…” to users but it was not what I needed at that time. If I needed this, I can switch to Cmd gradually. From Task to Cmd is easy, the opposite is not.

How about the other Projects?

I think Task works well in our project, but is it particular to our case? How about the majority of front-end projects?

I talked about this with @lucamug He was comfortable to use Cmd and doesn’t need Task at all. In their project, server and front is developed together, so no such complex request in frontend code.
I agree that this is a good way to reduce the number of requests. Chatty API (which often requires N+1 requests) is a bad pattern. I know, but real project is not always ideal!

I’m very interested in how other projects is doing with HTTP requests. I have not experienced so many SPA projects. If you know something, please share it here! (Also welcome if you are not doing a typical web development)
Is Cmd working well for your project? When do you use Task? I just want to know how things should be, and hopefully make Task requests live longer.

Thanks!

Experiment

If you are interested, I built an experimental library to solve my own problem. (Warning: still very WIP yet!) It is basically Task based, and steal the idea highly inspired by elm-http-builder.

It extremely cares about the ability of gradual switching. How to switch simple get to more complex request? How to switch from Task to Cmd? How to switch Http.Error to Req.Error with more information? How quickly implement features when niche information get suddenly needed? etc.


#2

i think the disclaimer is there because for many use-cases, it is preferable to build your system to require a single API request at a time, rather than three (where any of them could fail).

Of course, APIs often already exist or are outside your control, and even if you do have control over an API, besides moving to a more complex set-up such as GraphQL, doing a single-request per action might still not be advisable or possible (such as when the API is shared with other apps).

I think that in those kinds of use-cases, the task-based approach is good (and a lot better than manually going through the TEA-update loop for every partial request/response)


#3

it is preferable to build your system to require a single API request at a time, rather than three (where any of them could fail).

Yeah, the API is not out of control actually but it is not ideal for some historical reason. I will try to improve it soon.


#4

I’m very interested in how other projects is doing with HTTP requests

I actually create my Http requests as Task and attempt them; opting for the verbose api than the shorter ones. I’m not sure if it’s because I have a misguided notion that holding on to a Task will avail me to the various Task functions, buy me more flexibility as I evolve my app

hopefully make Task requests live longer.

I hope so too. I do need Http tasks to be around so I can continue exploring the server space


#5

I actually create my Http requests as Task and attempt them; opting for the verbose api than the shorter ones.

Yeah, Task is a bit verbose but more flexible when we need combining and chaining ability. But in 2.0.0, tracking and canceling are only available from the shorter API. I think Request a was not bad because we could lazily choose which way to go.


#6

I also felt this pain when updating to http 2.0.0; the API I’m working with also requires calls to be chained.

The trickiest part for me was to get stringResolver wired together with a Json.Decoder. I eventually figured out that I had to write a responseToResult : Response a -> Result Http.Error a function to make the error handling line up, but it wasn’t obvious.


#7

I wrote that function too! So now, everybody has to write the same helper functions to use Task. Of course it is possible to make libraries like I did, but my feeling is like “Is this against the direction of what Elm want to do?” or “Is this effort worth if Task will be finally removed?”.


Are all Cmd's despatched before the next update?