{"id":16482418,"url":"https://github.com/jamesmacaulay/cljs-promises","last_synced_at":"2025-03-16T18:31:39.961Z","repository":{"id":15652743,"uuid":"18390042","full_name":"jamesmacaulay/cljs-promises","owner":"jamesmacaulay","description":"A ClojureScript library for using JS promises with core.async","archived":false,"fork":false,"pushed_at":"2016-11-15T06:53:21.000Z","size":176,"stargazers_count":90,"open_issues_count":2,"forks_count":6,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-02-27T12:14:12.356Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesmacaulay.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-04-03T03:28:21.000Z","updated_at":"2024-02-21T12:59:03.000Z","dependencies_parsed_at":"2022-09-21T20:35:18.863Z","dependency_job_id":null,"html_url":"https://github.com/jamesmacaulay/cljs-promises","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesmacaulay%2Fcljs-promises","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesmacaulay%2Fcljs-promises/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesmacaulay%2Fcljs-promises/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesmacaulay%2Fcljs-promises/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesmacaulay","download_url":"https://codeload.github.com/jamesmacaulay/cljs-promises/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243826783,"owners_count":20354220,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T13:10:39.050Z","updated_at":"2025-03-16T18:31:39.545Z","avatar_url":"https://github.com/jamesmacaulay.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cljs-promises\n\nA ClojureScript library for working with JavaScript promises.\n\n### Overview\n\nThis library leverages the power of [core.async](https://github.com/clojure/core.async) to let you write promise code like this:\n\n```clojure\n(cljs-promises.async/extend-promises-as-pair-channels!)\n\n(go\n (let [user-promise (get-user \"jamesmacaulay\")]\n   (try\n     (println (:blog (\u003c? user-promise)))\n     (catch js/Error e\n       (println (str \"Could't get user data: \" (ex-message e))))))\n```\n\nThe first line globally extends `Promise` instances to act like **read-only channels** which, once resolved, **endlessly produce the same value or error to anyone who takes from them**. Because promises have built-in error semantics which don't have any direct corollary in core.async, there are different ways that we can represent promise results. In this example we've extended promises in a way that values taken from them with core.async are actually `[value error]` pairs, where you get `[value nil]` from a fulfilled promise and `[nil error]` from a rejected promise.\n\n`\u003c?` is a macro provided by `cljs-promises.async` which takes one of these pairs from a promise with core.async's `\u003c!`. If the `error` slot of the pair is non-nil, it throws the error. Otherwise it returns whatever is in the `value` slot.\n\nIf you want to customize how a promise's resolution translates to values you get from `\u003c!`, you can use the more general `extend-promises-as-channels!` which lets you provide your own transform functions for fulfilled values and rejected errors.\n\nIf you don't want to globally extend promises like this at all (a good idea if you're writing a library or for some other reason don't want to mess with the global scope), the `cljs-promises.async` namespace also provides functions which wrap promises on an ad-hoc basis:\n\n* `cljs-promises.async/pair-port` takes a promise and gives you an object which acts like a read-only channel of `[value error]` pairs as described above.\n* `cljs-promises.async/value-port` gives you a `ReadPort` of only resolved values, or `nil` values if a promise is rejected.\n* `cljs-promises.async/error-port` is the reverse of `value-port` and gives you just the errors.\n\n### Requirements\n\nSome functions in this library require that you've already got an ES6 Promise implementation present, either natively or through a [polyfill](https://github.com/jakearchibald/es6-promise). Other functions only require that you're giving them [Promises/A+](http://promises-aplus.github.io/promises-spec/)-compliant promise objects.\n\n### Rationale\n\nWe have [CSP](http://en.wikipedia.org/wiki/Communicating_sequential_processes) with core.async, so [why bother with promises](http://swannodette.github.io/2013/08/23/make-no-promises/)? Promises provide very different concurrency semantics compared with CSP channels. While channels provide a powerful abstraction for _passing messages and synchronizing execution_, a promise simply represents a single \"eventual value\".\n\nThis means that HTTP and most filesytem operations are very well suited for promises. Meanwhile, promises are _not_ appropriate for coordinating streams of spontaneous events like key presses or data coming in on a WebSocket. Channels are still the bees' knees when it comes to that kind of thing.\n\nPromises excel at representing single values because they are an **immutable** reference type:\n\n* after a promise is created, the ability to resolve it to a value is not part of its public interface\n* a promise only ever resolves to a single fulfilled value or rejected error\n* once resolved, a promise will continue to provide its value or error to anyone who asks for it\n\n(Sounds a lot like [futures](http://clojuredocs.org/clojure_core/clojure.core/future) in Clojure, doesn't it?)\n\nChannels in core.async are very different: they are inherently mutable. When you take a value from a channel, it is gone from that channel and no one else will see it. Here's how you might do a JSONP request in core.async:\n\n```clojure\n;; jsonp function lifted from http://swannodette.github.io/2013/11/07/clojurescript-101/\n(defn jsonp [uri]\n  (let [out (chan)\n        req (Jsonp. (Uri. uri))]\n    (.send req nil (fn [res] (put! out res)))\n    out))\n\n(let [uri \"http://en.wikipedia.org/w/api.php?action=opensearch\u0026format=json\u0026search=clojure\"]\n  (go (println (\u003c! (jsonp uri)))))\n```\n\nThis is fine, but it means that you have to treat the return value of `jsonp` as a single-use wrapper which becomes useless after its value has been taken from it.\n\nIf you want to be able to _share_ representations of delayed or partially-delayed values (e.g. nested data structures with some values yet to arrive), then promises are a better fit. Because they are immutable, you can share them between different parts of your code and not have to worry about how they'll be used.\n\nHere's some code which builds a map of promises for different kinds of data about a GitHub user:\n\n```clojure\n(defn build-view-context\n  [username]\n  {:user (github-get (str \"/users/\" username))\n   :gists (github-get (str \"/users/\" username \"/gists\"))\n   :repos (github-get (str \"/users/\" username \"/repos?sort=created\"))\n   :events (github-get (str \"/users/\" username \"/events\"))})\n```\n\nThis map can be shared among multiple view functions, and each of those functions can depend on any subset of the included promises. The code which builds a context of eventual values doesn't need to know how those values are going to be used.\n\nI'm sure there are various possibilities available with core.async channels to decouple things in similar ways. There is [mult](http://clojure.github.io/core.async/#clojure.core.async/mult), and [pub](http://clojure.github.io/core.async/#clojure.core.async/pub)/[sub](http://clojure.github.io/core.async/#clojure.core.async/sub), and probably other tools that would help. I would argue, however, that the simplicity of promises makes them a better tool for this particular job.\n\nOne of the great things about core.async, though, is that its channels are based on a simple interface composed of a handful of very granular ClojureScript protocols! This means that it's actually really easy to make custom types which act like channels _just enough_ to play nice with the rest of core.async.\n\n### Development\n\nI've been developing cljs-promises with [Light Table](http://www.lighttable.com/), mostly by playing with the examples:\n\n* run `lein cljsbuild auto examples` in the project root to watch the filesystem and compile-on-save\n* add a Light Table connection to an external browser and copy the port number from the script tag\n* edit `examples/index.html` so that the script tag matches the port\n* open up `examples/index.html` in a browser\n\nThen you can eval individual forms from Light Table and save-and-refresh whenever you make big changes.\n\n### Thanks\n\nSpecial thanks to [David Nolen](http://swannodette.github.io/) for writing so much fantastic code and introductory material for newcomers to ClojureScript and especially core.async. I've adapted or copied some code from his blog in this library (especially the examples), and I've tried to make attribution notes in the code comments wherever appropriate.\n\n### License\n\nThis code is released under an MIT license.\n\nCopyright 2014 James MacAulay.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesmacaulay%2Fcljs-promises","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesmacaulay%2Fcljs-promises","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesmacaulay%2Fcljs-promises/lists"}