• Stars
    star
    162
  • Rank 232,284 (Top 5 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created over 7 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Promise library for ClojureScript, or a poor man's core.async

kitchen-async

Clojars Project CircleCI

A Promise library for ClojureScript, or a poor man's core.async

Features

  • syntactic support for writing asynchronous code handling Promises as easily as with async/await in ECMAScript
  • also available on self-hosted ClojureScript environments, such as Lumo/Planck
  • seamless (opt-in) integration with core.async channels

kitchen-async focuses on the ease of Promise handling, and is not specifically intended to be so much performant. If you would rather like such a library, promesa or core.async might be more suitable.

Example

Assume you are writing some Promise-heavy async code in ClojureScript (e.g. Google's Puppeteer provides such a collection of APIs). Then, if you only use raw JavaScript interop facilities for it, you would have to write something like this:

(def puppeteer (js/require "puppeteer"))

(-> (.launch puppeteer)
    (.then (fn [browser]
             (-> (.newPage browser)
                 (.then (fn [page]
                          (-> (.goto page "https://clojure.org")
                              (.then #(.screenshot page #js{:path "screenshot.png"}))
                              (.catch js/console.error)
                              (.then #(.close browser)))))))))

kitchen-async provides more succinct, "direct style" syntactic sugar for those things, which you may find similar to async/await in ECMAScript 2017:

(require '[kitchen-async.promise :as p])

(def puppeteer (js/require "puppeteer"))

(p/let [browser (.launch puppeteer)
        page (.newPage browser)]
  (p/try
    (.goto page "https://clojure.org")
    (.screenshot page #js{:path "screenshot.png"})
    (p/catch :default e
      (js/console.error e))
    (p/finally
      (.close browser))))

Installation

Add the following to your :dependencies:

Clojars Project

Or, if you'd rather use an unstable version of the library, you can do that easily via deps.edn as well:

athos/kitchen-async {:git/url "https://github.com/athos/kitchen-async.git" :sha <commit sha hash>}

Usage

kitchen-async provides two major categories of APIs:

You can use all these APIs once you require kitchen-async.promise ns, like the following:

(require '[kitchen-async.promise :as p])

Thin wrapper APIs for JS Promise

* p/promise macro

p/promise macro creates a new Promise:

(p/promise [resolve reject]
  (js/setTimeout #(resolve 42) 1000))  
;=> #object[Promise [object Promise]]

This code is equivalent to:

(js/Promise. 
 (fn [resolve reject]
   (js/setTimeout #(resolve 42) 1000)))

* p/then & p/catch*

p/then and p/catch* simply wrap Promise's .then and .catch methods, respectively. For example:

(-> (some-promise-fn)
    (p/then (fn [x] (js/console.log x)))
    (p/catch* (fn [err] (js/console.error err))))

is almost equivalent to:

(-> (some-promise-fn)
    (.then (fn [x] (js/console.log x)))
    (.catch (fn [err] (js/console.error err))))

* p/resolve & p/reject

p/resolve and p/reject wraps Promise.resolve and Promise.reject, respectively. For example:

(p/then (p/resolve 42) prn)

is equivalent to:

(.then (js/Promise.resolve 42) prn)

* p/all & p/race

p/all and p/race wraps Promise.all and Promise.race, respectively. For example:

(p/then (p/all [(p/resolve 21)
                (p/promise [resolve]
                  (js/setTimeout #(resolve 21) 1000))])
        (fn [[x y]] (prn (+ x y))))

is almost equivalent to:

(.then (js/Promise.all #js[(js/Promise.resolve 42)
                           (js/Promise.
                             (fn [resolve]
                               (js/setTimeout #(resolve 42) 1000)))])
       (fn [[x y]] (prn (+ x y))))

* Coercion operator and implicit coercion

kitchen-async provides a fn named p/->promise, which coerces an arbitrary value to a Promise. By default, p/->promise behaves as follows:

  • For Promises, acts like identity (i.e. returns the argument as is)
  • For any other type of values, acts like p/resolve

In fact, most functions defined as the thin wrapper API (and the macros that will be described below) implicitly apply p/->promise to their input values. Thanks to that trick, you can freely mix up non-Promise values together with Promises:

(p/then 42 prn) 
;; it will output 42 with no error

(p/then (p/all [21 (p/resolve 21)])
        (fn [[x y]] (prn (+ x y))))
;; this also works well

Moreover, since it's defined as a protocol method, it's possible to extend p/->promise to customize its behavior for a specific data type. For details, see the section "Extension of coercion operator". Also, the section "Integration with core.async channels" may help you grasp how we can utilize this capability.

Idiomatic Clojure style syntactic sugar

kitchen-async also provides variant of several macros (including special forms) in clojure.core that return a Promise instead of returning the expression value.

p/do

p/do conjoins the expressions of the body with p/then ignoring the intermediate values. For example:

(p/do
  (expr1)
  (expr2)
  (expr3))

is equivalent to:

(p/then (expr1)
        (fn [_]
          (p/then (expr2)
                  (fn [_] (expr3)))))

p/let

p/let is almost the same as p/do except that it names each intermediate value with the corresponding name. For example:

(p/let [v1 (expr1)
        v2 (expr2)]
  (expr3))

is equivalent to:

(p/then (expr1)
        (fn [v1]
          (p/then (expr2)
                  (fn [v2] (expr3)))))

Note that the body of the p/let is implicitly wrapped with p/do when it has multiple expressions in it. For example, when you write some code like:

(p/let [v1 (expr1)]
  (expr2)
  (expr3))

the call to expr3 will be deferred until (expr2) is resolved. To avoid this behavior, you must wrap the body with do explicitly:

(p/let [v1 (expr1)]
  (do
    (expr2)
    (expr3)))

Threading macros

kitchen-async also has its own ->, ->>, some-> and some->>. For example:

(p/-> (expr) f (g c))

is equivalent to:

(-> (expr)
    (p/then (fn [x] (f x)))
    (p/then (fn [y] (g y c))))

and

(p/some-> (expr) f (g c))

is equivalent to:

(-> (expr)
    (p/then (fn [x] (some-> x f)))
    (p/then (fn [y] (some-> y (g c))))

Loops

For loops, you can use p/loop and p/recur:

(defn timeout [ms v]
  (p/promise [resolve]
    (js/setTimeout #(resolve v) ms)))
    
(p/loop [i (timeout 1000 10)]
  (when (> i 0)
    (prn i)
    (p/recur (timeout 1000 (dec i)))))
    
;; Count down the numbers from 10 to 1

Note that the body of the p/loop is wrapped with p/do, as in the p/let.

p/recur cannot be used outside of the p/loop, and also make sure to call p/recur at a tail position.

Error handling

For error handling, you can use p/try, p/catch and p/finally:

(p/try
  (expr)
  (p/catch js/Error e
    (js/console.error e))
  (p/finally
    (teardown)))

is almost equivalent to:

(-> (expr)
    (p/catch* 
     (fn [e]
       (if (instance? js/Error e)
         (js/console.error e)
         (throw e))))
    (p/then (fn [v] (p/do (teardown) v))))

Note that the body of the p/try, p/catch and p/finally is wrapped with p/do, as in the p/let.

p/catch and p/finally (if any) cannot be used outside of the p/try, and also make sure to call them at the end of the p/try's body.

Extension of coercion operator

(TODO)

Integration with core.async channels

(TODO)

License

Copyright © 2017 Shogo Ohta

Distributed under the Eclipse Public License 1.0.

More Repositories

1

Postmortem

A simple debug library for Clojure(Script) that features data-oriented logging and tracing
Clojure
156
star
2

japanese-clojure-companies

日本で Clojure/ClojureScript を利用している会社一覧
110
star
3

JiSE

JiSE: Java in S-Expression
Clojure
104
star
4

Pinpointer

Pinpointer is yet another clojure.spec error reporter based on a precise error analysis
Clojure
94
star
5

pogonos

Yet another Clojure(Script) implementation of the Mustache templating language
Clojure
74
star
6

sweet-array

Array manipulation library for Clojure with "sweet" array type notation and more safety by static types
Clojure
65
star
7

trenchman

A standalone nREPL/prepl client written in Go and heavily inspired by Grenchman
Go
63
star
8

power-dot

Clojure library for enhanced Java interop that helps you make friends with Java's functional interfaces 😍
Clojure
52
star
9

stacktracer

A small development utility to print stack trace with code context
Clojure
49
star
10

Drains

A new abstraction for flexible and efficient sequence aggregation in Clojure(Script)
Clojure
34
star
11

clj-check

lein-check alternative for Clojure CLI tool
Clojure
23
star
12

type-infer

A Clojure utility to inspect static types inferred by the Clojure compiler
Clojure
21
star
13

genman

Generator management utility for clojure.spec
Clojure
20
star
14

spectrace

clojure.spec (spec.alpha) library aiming to be a fundamental tool for analyzing spec errors
Clojure
16
star
15

prejenter

Presentation framework for Clojurians
Clojure
12
star
16

syntactic-closure

syntactic closures built on top of Clojure's macro system
Clojure
12
star
17

symbol-analyzer

Clojure code analyzer that tells us how each symbol is being used in the code
Clojure
11
star
18

Bigmouth

Clojure framework to build delivery-only Mastodon-compatible web apps
Clojure
11
star
19

lein-with-env-vars

A Leiningen plugin for performing a task with environment variable settings loaded from project.clj
Clojure
11
star
20

genuine-highlighter

a macro-aware syntax highlighter for Clojure
Clojure
10
star
21

admin-kit

Admin site as data
Clojure
9
star
22

aintegrant

Aintegrant ain't Integrant, it's Async Integrant!
Clojure
9
star
23

parabola

Hubot-like chatbot framework powered by Clojure/core.async
Clojure
7
star
24

tetrlang

an esoteric language inspired by Tetris
Clojure
7
star
25

emsh

An embedded shell built on top of Clojure
Clojure
7
star
26

intro-to-prejenter

Clojure
5
star
27

igv-client

Clojure implementation of IGV client to control IGV via its Port Commands
Clojure
4
star
28

iasm

inline assembly macros for Clojure
Clojure
4
star
29

mekki

Clojure-flavored Alloy language implemented on top of Clojure macro system
Clojure
4
star
30

maeni

A toy implementation of the programming language Forth written in Clojure
Clojure
4
star
31

clojars-rss

Unofficially generated RSS feeds for libraries recently updated on Clojars
Clojure
3
star
32

reading-clojurescript

A project for reading ClojureScript source code
Clojure
3
star
33

SimpleLisp

a simple Lisp implementation in Java as a study
Java
3
star
34

AR-sample

AR sample project using AR.js from ClojureScript
Clojure
3
star
35

reading-clojure

A project for reading Clojure's source code
Java
3
star
36

nrepl-revolver

Docker-backed nREPL server aiming for secure execution and fast startup
Clojure
2
star
37

applicosan

My personal Slack bot built with Duct framework
Clojure
2
star
38

clj-skkserv

An skkserv server framework in Clojure, heavily inspired by Ring architecture
Clojure
2
star
39

white-scheme

an implementation of Scheme-to-Whitespace compiler
Scheme
2
star
40

pwa-in-cljs

Clojure
2
star
41

code-marker

A versatile code marker for Clojure
Clojure
2
star
42

clj-wiki

a wiki engine written in Clojure
Clojure
2
star
43

athos.github.io

https://athos.github.io/
HTML
2
star
44

advent-of-code-2018

My solutions for Advent of Code 2018 http://adventofcode.com/2018
Clojure
2
star
45

land-of-clojurescript

A ClojureScript port of the code from "Land of Lisp", mainly for the "Dice of Doom" game
Clojure
2
star
46

OCheme

OCheme is a toy implementation of Scheme written in OCaml.
OCaml
2
star
47

twtr2mvc

A Twitter Mixi-Voice bridge
Clojure
2
star
48

silly

A silly personal assistant for demonstrating the combination of Web Speech API and Microsoft LUIS
Clojure
2
star
49

spec-example

Clojure
2
star
50

c-compijer

Clojure
2
star
51

gauchey

A Clojure library ported from Gauche's bundled library.
Clojure
2
star
52

rusp

A SECD-based Lisp implementation in Rust
Rust
2
star
53

task-jockey

Pueue-like task management tool implemented in Clojure
Clojure
2
star
54

abstract-machine-collection

A collection of abstract machine implementations
Clojure
1
star
55

mario-brojures

A port implementation of MariOCaml in ClojureScript
Clojure
1
star
56

bfmaker

Clojure
1
star
57

clj-callgraph

A call graph generator for Clojure (with some fun features)
Clojure
1
star
58

Lustered

This project has been moved to https://github.com/athos/admin-kit.
Clojure
1
star
59

experimental-core-specs

An experimental project aimed at investigating how it would be once core.specs were fully introduced
Clojure
1
star
60

as2twtr

AsakusaSatellite Twitter Bridge
Clojure
1
star
61

specium

Inverse of s/form, reducing eval calls as much as possible
Clojure
1
star
62

Project-Euler

athos' solutions for problems of Project Euler
Clojure
1
star
63

kodamacast

Clojure
1
star
64

lein-highlight

Leiningen plugin for highlighting Clojure code powered by symbol-analyzer, a fine-grained Clojure code analyzer
Clojure
1
star
65

TIL

Today I Learned
1
star
66

alexa-skill-sample

Clojure
1
star
67

goclojure

Brings goruby-like abbreviation to Clojure
Clojure
1
star