zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

async.ex (3460B)


      1 defmodule Absinthe.Middleware.Async do
      2   @moduledoc """
      3   This plugin enables asynchronous execution of a field.
      4 
      5   See also `Absinthe.Resolution.Helpers.async/1`
      6 
      7   # Example Usage:
      8 
      9   Using the `Absinthe.Resolution.Helpers.async/1` helper function:
     10   ```elixir
     11   field :time_consuming, :thing do
     12     resolve fn _, _, _ ->
     13       async(fn ->
     14         {:ok, long_time_consuming_function()}
     15       end)
     16     end
     17   end
     18   ```
     19 
     20   Using the bare plugin API
     21   ```elixir
     22   field :time_consuming, :thing do
     23     resolve fn _, _, _ ->
     24       task = Task.async(fn ->
     25         {:ok, long_time_consuming_function()}
     26       end)
     27       {:middleware, #{__MODULE__}, task}
     28     end
     29   end
     30   ```
     31 
     32   This module also serves as an example for how to build middleware that uses the
     33   resolution callbacks.
     34 
     35   See the source code and associated comments for further details.
     36   """
     37 
     38   @behaviour Absinthe.Middleware
     39   @behaviour Absinthe.Plugin
     40 
     41   # A function has handed resolution off to this middleware. The first argument
     42   # is the current resolution struct. The second argument is the function to
     43   # execute asynchronously, and opts we'll want to use when it is time to await
     44   # the task.
     45   #
     46   # This function suspends resolution, and sets the async flag true in the resolution
     47   # accumulator. This will be used later to determine whether we need to run resolution
     48   # again.
     49   #
     50   # This function inserts additional middleware into the remaining middleware
     51   # stack for this field. On the next resolution pass, we need to `Task.await` the
     52   # task so we have actual data. Thus, we prepend this module to the middleware stack.
     53   def call(%{state: :unresolved} = res, {fun, opts}) when is_function(fun),
     54     do: call(res, {Task.async(fun), opts})
     55 
     56   def call(%{state: :unresolved} = res, {task, opts}) do
     57     task_data = {task, opts}
     58 
     59     %{
     60       res
     61       | state: :suspended,
     62         acc: Map.put(res.acc, __MODULE__, true),
     63         middleware: [{__MODULE__, task_data} | res.middleware]
     64     }
     65   end
     66 
     67   def call(%{state: :unresolved} = res, %Task{} = task), do: call(res, {task, []})
     68 
     69   # This is the clause that gets called on the second pass. There's very little
     70   # to do here. We just need to await the task started in the previous pass.
     71   #
     72   # Finally, we apply the result to the resolution using a helper function that ensures
     73   # we handle the different tuple results.
     74   #
     75   # The `put_result` function handles setting the appropriate state.
     76   # If the result is an `{:ok, value} | {:error, reason}` tuple it will set
     77   # the state to `:resolved`, and if it is another middleware tuple it will
     78   # set the state to unresolved.
     79   def call(%{state: :suspended} = res, {task, opts}) do
     80     result = Task.await(task, opts[:timeout] || 30_000)
     81 
     82     res
     83     |> Absinthe.Resolution.put_result(result)
     84   end
     85 
     86   # We must set the flag to false because if a previous resolution iteration
     87   # set it to true it needs to go back to false now. It will be set
     88   # back to true if any field uses this plugin again.
     89   def before_resolution(exec) do
     90     put_in(exec.acc[__MODULE__], false)
     91   end
     92 
     93   # Nothing to do after resolution for this plugin, so we no-op
     94   def after_resolution(exec), do: exec
     95 
     96   # If the flag is set we need to do another resolution phase.
     97   # otherwise, we do not
     98   def pipeline(pipeline, exec) do
     99     case exec.acc do
    100       %{__MODULE__ => true} ->
    101         [Absinthe.Phase.Document.Execution.Resolution | pipeline]
    102 
    103       _ ->
    104         pipeline
    105     end
    106   end
    107 end