From 9adb7c5acecfe5d2b942aca8d1975bc568bb3063 Mon Sep 17 00:00:00 2001 From: Maarten van Vliet Date: Tue, 26 Feb 2019 14:14:43 +0100 Subject: [PATCH] Add support for passing tasks to async middleware (#680) The docs mentioned the option but the code did not support it. --- lib/absinthe/middleware/async.ex | 9 ++++-- lib/absinthe/resolution/helpers.ex | 27 +++++++++++++++--- test/absinthe/middleware/async_test.exs | 37 ++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/lib/absinthe/middleware/async.ex b/lib/absinthe/middleware/async.ex index ef12d0f158..f5003ce17f 100644 --- a/lib/absinthe/middleware/async.ex +++ b/lib/absinthe/middleware/async.ex @@ -50,8 +50,11 @@ defmodule Absinthe.Middleware.Async do # This function inserts additional middleware into the remaining middleware # stack for this field. On the next resolution pass, we need to `Task.await` the # task so we have actual data. Thus, we prepend this module to the middleware stack. - def call(%{state: :unresolved} = res, {fun, opts}) do - task_data = {Task.async(fun), opts} + def call(%{state: :unresolved} = res, {fun, opts}) when is_function(fun), + do: call(res, {Task.async(fun), opts}) + + def call(%{state: :unresolved} = res, {task, opts}) do + task_data = {task, opts} %{ res @@ -61,6 +64,8 @@ defmodule Absinthe.Middleware.Async do } end + def call(%{state: :unresolved} = res, %Task{} = task), do: call(res, {task, []}) + # This is the clause that gets called on the second pass. There's very little # to do here. We just need to await the task started in the previous pass. # diff --git a/lib/absinthe/resolution/helpers.ex b/lib/absinthe/resolution/helpers.ex index 3d42021322..6537121088 100644 --- a/lib/absinthe/resolution/helpers.ex +++ b/lib/absinthe/resolution/helpers.ex @@ -16,9 +16,27 @@ defmodule Absinthe.Resolution.Helpers do This is a helper function for using the `Absinthe.Middleware.Async`. Forbidden in mutation fields. (TODO: actually enforce this) + + ## Options + - `:timeout` default: `30_000`. The maximum timeout to wait for running + the task. + + ## Example + + Using the `Absinthe.Resolution.Helpers.async/1` helper function: + ```elixir + field :time_consuming, :thing do + resolve fn _, _, _ -> + async(fn -> + {:ok, long_time_consuming_function()} + end) + end + end + ``` """ @spec async((() -> term)) :: {:middleware, Middleware.Async, term} - @spec async((() -> term), Keyword.t()) :: {:middleware, Middleware.Async, term} + @spec async((() -> term), opts :: [{:timeout, pos_integer}]) :: + {:middleware, Middleware.Async, term} def async(fun, opts \\ []) do {:middleware, Middleware.Async, {fun, opts}} end @@ -29,10 +47,11 @@ defmodule Absinthe.Resolution.Helpers do Helper function for creating `Absinthe.Middleware.Batch` ## Options - - `:timeout` default: `5_000`. The maximum timeout to wait for running + - `:timeout` default: `5_000`. The maximum timeout to wait for running a batch. - - # Example + + ## Example + Raw usage: ```elixir object :post do diff --git a/test/absinthe/middleware/async_test.exs b/test/absinthe/middleware/async_test.exs index 5bfe7935c3..fa2157f3f5 100644 --- a/test/absinthe/middleware/async_test.exs +++ b/test/absinthe/middleware/async_test.exs @@ -26,6 +26,24 @@ defmodule Absinthe.Middleware.AsyncTest do {:ok, nil} end) end + + field :async_bare_thing_with_opts, :string do + resolve fn _, _, _ -> + task = Task.async(fn -> + {:ok, "bare task"} + end) + {:middleware, Elixir.Absinthe.Middleware.Async, {task, []}} + end + end + + field :async_bare_thing, :string do + resolve fn _, _, _ -> + task = Task.async(fn -> + {:ok, "bare task"} + end) + {:middleware, Elixir.Absinthe.Middleware.Async, task} + end + end end def cool_async(fun) do @@ -37,7 +55,24 @@ defmodule Absinthe.Middleware.AsyncTest do end end - test "can resolve a field using the normal async helper" do + test "can resolve a field using the bare api with opts" do + doc = """ + {asyncBareThingWithOpts} + """ + + assert {:ok, %{data: %{"asyncBareThingWithOpts" => "bare task"}}} == Absinthe.run(doc, Schema) + end + + test "can resolve a field using the bare api" do + doc = """ + {asyncBareThing} + """ + + assert {:ok, %{data: %{"asyncBareThing" => "bare task"}}} == Absinthe.run(doc, Schema) + end + + + test "can resolve a field using the normal test helper" do doc = """ {asyncThing} """