Async parsing
The parallel parser is also an async parser. But for the most users the parallel parser adds too much overhead. |
If you want to write parsers to run in Javascript environments, then async operations are the norm. The async
parser is a version of the parser were you can return core.async
channels from the readers instead of raw
values. This allows for the creation of parsers that do network requests or any other async operation.
The async parser is still semantically a serial parser, and it will have the same flow characteristics
of the regular parser (the order or resolution is preserved).
To write an async parser we use the p/async-parser
function. Here is an example:
(ns com.wsscode.pathom.book.async.intro
(:require [com.wsscode.pathom.core :as p]
[cljs.core.async :as async :refer [go <!]]
[com.wsscode.pathom.trace :as pt]
[com.wsscode.pathom.connect :as pc]))
(pc/defresolver async-info [_ _]
{::pc/output [:async-info]}
(go
(<! (async/timeout (+ 100 (rand-int 1000))))
{:async-info "From async"}))
(pc/defresolver foo [_ _]
{::pc/output [:foo]}
{:foo "Regular"})
(def register [async-info foo])
(def parser
(p/async-parser {::p/env {::p/reader [p/map-reader
pc/async-reader2]}
::p/plugins [(pc/connect-plugin {::pc/register register})
pt/trace-plugin]}))
Try the example:
The core plugins work normally with the async parser, so error and profiling will work as expected.
Error propagation
When an exception occurs inside a core.async
channel the error is triggered as part of the channel exception handler.
That doesn’t compose very well and for the parser it’s better if we have something more like the async/await
pattern available within the JS environments. Pathom provides some macros to facilitate this; instead of using
go
and <!
, use the provided go-catch
and <?
macros, as shown in the following example:
(ns com.wsscode.pathom.book.async.error-propagation
(:require [cljs.core.async :as async]
[com.wsscode.common.async-cljs :refer [go-catch <?]]
[com.wsscode.pathom.connect :as pc]
[com.wsscode.pathom.core :as p]
[com.wsscode.pathom.trace :as pt]))
(pc/defresolver async-info [_ _]
{::pc/output [:async-info]}
(go-catch
(<? (async/timeout (+ 100 (rand-int 1000))))
{:async-info "From async"}))
(pc/defresolver async-error [_ _]
{::pc/output [:async-error]}
; go catch will catch any exception and return then as the channel value
(go-catch
; <? macro will re-throw any exception that get read from the channel
(<? (async/timeout (+ 100 (rand-int 1000))))
(throw (ex-info "Error!!" {}))))
(pc/defresolver foo [_ _]
{::pc/output [:foo]}
{:foo "Regular"})
(def register [async-info async-error foo])
(def parser
(p/async-parser {::p/env {::p/reader [p/map-reader
pc/async-reader2]}
::p/plugins [(pc/connect-plugin {::pc/register register})
p/error-handler-plugin
pt/trace-plugin]}))
Use com.wsscode.common.async-clj
for Clojure and com.wsscode.common.async-cljs
for ClojureScript. If you are writing a
cljc file, use the following:
[#?(:clj com.wsscode.common.async-clj
:cljs com.wsscode.common.async-cljs)
:refer [go-catch <?]]
JS Promises
In JS world most of the current async responses come as promises, you can use the <!p
macro to read from promises
inside the go
blocks as if they were channels. Example:
(ns com.wsscode.pathom.book.async.js-promises
(:require [com.wsscode.common.async-cljs :refer [go-catch <!p]]
[com.wsscode.pathom.connect :as pc]
[com.wsscode.pathom.core :as p]
[goog.object :as gobj]))
(pc/defresolver random-dog [env {:keys []}]
{::pc/output [:dog.ceo/random-dog-url]}
(go-catch
{:dog.ceo/random-dog-url
(-> (js/fetch "https://dog.ceo/api/breeds/image/random") <!p
(.json) <!p
(gobj/get "message"))}))
(def parser
(p/parallel-parser
{::p/env {::p/reader [p/map-reader
pc/parallel-reader
pc/open-ident-reader
p/env-placeholder-reader]
::p/placeholder-prefixes #{">"}}
::p/plugins [(pc/connect-plugin {::pc/register [random-dog]})
p/error-handler-plugin
p/trace-plugin]}))