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:

[:foo :async-info]

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]}))
[:foo :async-info :async-error]

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]}))
[:dog.ceo/random-dog-url]