Async process allows Pathom to process resolvers that require async processing.
In JS environments are limited without async support, you can't trigger HTTP or database
requests.
In this section, you will learn how to use the async features from Pathom 3.
Promesa#
Pathom 3 uses the Promesa library under the hood to manage the async process.
Promesa uses native Promises
in Javascript environments, and CompletableFuture
on the JVM.
I'll use the term future
to refer to both Promises
and CompletableFuture
in the
rest of this page.
Async resolvers#
Async resolvers are not unique. They use the same constructs as the other resolvers
you know so far.
The difference when you write an async resolver is that it may return a future
from
the resolver, for example:
(ns com.wsscode.pathom3.docs.demos.core.async
(:require [com.wsscode.pathom3.connect.indexes :as pci]
[com.wsscode.pathom3.connect.operation :as pco]
[promesa.core :as p]))
(defn json-get [url]
(p/let [resp (js/fetch url)
json (.json resp)]
(js->clj json :keywordize-keys true)))
(pco/defresolver age-from-name [{::keys [first-name]}]
{::pco/output [::age]}
(p/let [{:keys [age]} (json-get (str "https://api.agify.io/?name=" first-name))]
{::age age}))
The future
must be around the return map in the resolver. Returning futures as
values for the keys won't work. This is a bad example:
(pco/defresolver age-from-name [{::keys [first-name]}]
{::pco/output [::age]}
; return map
{::age
; with key value as a promise, won't work
(p/let [{:keys [age]} (json-get (str "https://api.agify.io/?name=" first-name))]
age)})
Async EQL#
To async, there is only the EQL interface. It behaves similar to the EQL
standard interface, but returns a promise in the end.
Example usage:
(ns com.wsscode.pathom3.docs.demos.core.async
(:require [com.wsscode.pathom3.connect.indexes :as pci]
[com.wsscode.pathom3.connect.operation :as pco]
[com.wsscode.pathom3.interface.async.eql :as p.a.eql]
[promesa.core :as p]))
(defn json-get [url]
(p/let [resp (js/fetch url)
json (.json resp)]
(js->clj json :keywordize-keys true)))
(pco/defresolver age-from-name [{::keys [first-name]}]
{::pco/output [::age]}
(p/let [{:keys [age]} (json-get (str "https://api.agify.io/?name=" first-name))]
{::age age}))
(def env
(pci/register
age-from-name))
(comment
(p/let [res (p.a.eql/process env
{::first-name "Ada"}
[::age])]
(cljs.pprint/pprint res)))
Using core.async#
To use core.async we can extend the channel protocol to implement the conversion from
a core.async channel to a future.
I have made a library to share the implementation of this extension, this way we can
avoid issues when multiple people try to extend the same type.
First add promesa-bridges to your dependencies:
{:deps {com.wsscode/promesa-bridges {:mvn/version "2021.01.20"}}}
Then include and use it:
(ns com.wsscode.pathom3.docs.demos.core.async-extend-core-async
(:require
[clojure.core.async :as async :refer [go <!]]
[com.wsscode.promesa.bridges.core-async]
[com.wsscode.pathom3.connect.indexes :as pci]
[com.wsscode.pathom3.connect.operation :as pco]
[com.wsscode.pathom3.interface.async.eql :as p.a.eql]
[promesa.core :as p]))
(pco/defresolver slow-resolver []
{::pco/output [::slow-response]}
; returning a channel from resolver
(go
(<! (async/timeout 400))
{::slow-response "done"}))
(def env (pci/register slow-resolver))
(comment
(p/let [res (p.a.eql/process env [::slow-response])]
(cljs.pprint/pprint res)))
If you like to add more extensions to promesa-bridges
, please send a pull request.