All Projects → athos → Kitchen Async

athos / Kitchen Async

Licence: epl-1.0
A Promise library for ClojureScript, or a poor man's core.async

Programming Languages

clojure
4091 projects
clojurescript
191 projects

Projects that are alternatives of or similar to Kitchen Async

Rubico
[a]synchronous functional programming
Stars: ✭ 133 (+3.91%)
Mutual labels:  async, asynchronous, promise
Creed
Sophisticated and functionally-minded async with advanced features: coroutines, promises, ES2015 iterables, fantasy-land
Stars: ✭ 265 (+107.03%)
Mutual labels:  async, asynchronous, promise
Tascalate Concurrent
Implementation of blocking (IO-Bound) cancellable java.util.concurrent.CompletionStage and related extensions to java.util.concurrent.ExecutorService-s
Stars: ✭ 144 (+12.5%)
Mutual labels:  async, asynchronous, promise
Vue Loadable
⏳ Improve your loading state control with pretty simple methods and helpers.
Stars: ✭ 23 (-82.03%)
Mutual labels:  async, asynchronous, promise
Metasync
Asynchronous Programming Library for JavaScript & Node.js
Stars: ✭ 164 (+28.13%)
Mutual labels:  async, asynchronous, promise
Before After Hook
wrap methods with before/after hooks
Stars: ✭ 49 (-61.72%)
Mutual labels:  async, asynchronous, promise
Cofx
A node and javascript library that helps developers describe side-effects as data in a declarative, flexible API.
Stars: ✭ 72 (-43.75%)
Mutual labels:  asynchronous, promise
Radon
Object oriented state management solution for front-end development.
Stars: ✭ 80 (-37.5%)
Mutual labels:  async, asynchronous
Taskorama
⚙ A Task/Future data type for JavaScript
Stars: ✭ 90 (-29.69%)
Mutual labels:  async, promise
Tedis
redis client with typescript and esnext for nodejs
Stars: ✭ 109 (-14.84%)
Mutual labels:  async, promise
Promised Pipe
A ramda.pipe-like utility that handles promises internally with zero dependencies
Stars: ✭ 64 (-50%)
Mutual labels:  async, promise
Base64 Async
Non-blocking chunked Base64 encoding
Stars: ✭ 98 (-23.44%)
Mutual labels:  async, asynchronous
Tas
Make it easy to develop large, complex Node.js app.
Stars: ✭ 128 (+0%)
Mutual labels:  async, promise
Write
Write data to the file system, creating any intermediate directories if they don't already exist. Used by flat-cache and many others!
Stars: ✭ 68 (-46.87%)
Mutual labels:  async, promise
Emittery
Simple and modern async event emitter
Stars: ✭ 1,146 (+795.31%)
Mutual labels:  async, promise
Datakernel
Alternative Java platform, built from the ground up - with its own async I/O core and DI. Ultra high-performance, simple and minimalistic - redefines server-side programming, web-development and highload!
Stars: ✭ 87 (-32.03%)
Mutual labels:  async, promise
Flowa
🔥Service level control flow for Node.js
Stars: ✭ 66 (-48.44%)
Mutual labels:  async, promise
Datastore
🐹 Bloat free and flexible interface for data store and database access.
Stars: ✭ 99 (-22.66%)
Mutual labels:  async, asynchronous
Async Backplane
Simple, Erlang-inspired fault-tolerance framework for Rust Futures.
Stars: ✭ 113 (-11.72%)
Mutual labels:  async, asynchronous
Aiormq
Pure python AMQP 0.9.1 asynchronous client library
Stars: ✭ 112 (-12.5%)
Mutual labels:  async, asynchronous

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.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].