Async

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}))
important

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)))

Async ENV

When using the async helpers, Pathom also supports the env being async.

This is useful when the environment build requires async operations. For example, in a CLJS client, to integrate a remote Pathom service we need to request the index async:

(defn transit-request [url body]
(p/let [resp (js/fetch url
#js {:method "POST"
:body (tt/write-str body {:handlers pcot/write-handlers})})
text (.text resp)]
(tt/read-str text {:handlers pcot/read-handlers})))
(defn pathom-remote [request]
(transit-request "https://name-server.com" request))
(def users
{1 {:person/first-name "Alex"
:person/last-name "French Guy Cooking"}
2 {:person/first-name "Denise"
:person/last-name "Mascarenhas"}})
; note now we make env a promise instead of a map
(def async-env
(p/let [rem (pcf/foreign-register pathom-remote)]
(pci/register
[rem
(pbir/static-table-resolver :person/id users)])))
; use directly as env, supported by Pathom
(p/let [res (p.a.eql/process async-env
{:person/id 2}
[:person/full-name])]
(js/console.log "!! " 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.