To Task.async or not

I have a disease of over complicating my code and would appreciate a suggestion to keep myself in check.

My program needs to make a call to a third party service. I felt I should use Task.async to ensure I am not blocked by waiting on the response. However, my flow does not perform multiple of these at one time nor have anything to do in between the call and the response. Should I just do a single HTTP get and wait for the response? Or, is there other processing benefits I am gain from doing the async call, e.g. other request will process.

Code below to help illustrate what I am doing.

  def subscribe(params) do
    hub = struct(Hub, params)
    Logger.debug "Processing subscription request for #{hub.topic} to #{hub.callback}"

    with {worker} <- Task.async(verify_intent(hub)),
         {:ok, hub} <- Task.await(worker)
      do
      {:ok} -> persist_subscription(hub)

      else
        {:error, %{reason: reason}} ->
          Logger.info(reason)
    end
  end

  defp verify_intent(hub) do
    case HTTPoison.get(hub.callback, [], [options: query]) do
      {:ok, %HTTPoison.Response{status_code: 200, body: _body}} ->
        Logger.debug "Subscription intent verified"
        {:ok, hub}
      {:ok, %HTTPoison.Response{status_code: 404}} ->
        {:error, %{reason: "Subscription intent not verified"}}
      _ ->
        {:error, %{reason: "Failed to verify intent on #{hub.callback}"}}
    end
  end

  def persist_subscription(hub) do
    Logger.debug "Persisting subscription in ETS..."
  end

When you use Task.await it becomes a blocking call. Which means that you just created the overhead of a Task then are waiting for it to get done.

So no, the structure of your code doesn’t benefit from Task at all.

Thanks. Kinda of how I was feeling.Then, I started looking at building a GenServer to isolate it all. But, I need the actual response to return. Thought I check before I fell off the deep end.

If you want async web requests. Then I’ve used Task.async then instead of using Task.await just handled the message myself. Which is a way to do non-blocking Task spawns.

I think it’s something like {task_ref, reply}. But then you need to demonitor the Task pid and such too.

If you need to reply, then you can store the Task and from then reply after they response comes back and you process it.

one of the things I’ve done, is used async_stream to call a function that handles the requests

list |> Task.async_stream(mod, func, [params], [opts]) |> Enum.to_list()

So in the above example my function takes a url for instance, makes the call and handles to return value, which is returned. async_stream handles the concurrency, and Enum.to_list colllects all the return values into a new list

So is this a method for when you have multiple requests to make at once? My original question concerns a single request and whether its overkill. Although I can certainly use this on the eventuality I need to make multiple requests in the future.

Ah ok, I didn’t realize it was just a single request. No my example was for handling multiple requests. In my project I had maybe hundreds of urls to send, so to keep things from overloading a I set a limit to make 8 or 10 calls at a time. My list was just a list of urls to process

1 Like