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

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.