Hacker News new | past | comments | ask | show | jobs | submit login
Make No Promises (swannodette.github.io)
98 points by _halgari on Aug 24, 2013 | hide | past | favorite | 36 comments



This is definitely some impressive performance. In Firefox 24 the core.async example is significantly faster than When.js on average (though in repeated trials, the running times vary over wide (and overlapping) ranges).

Some commenters seem to assume David's point is "use core.async because it's faster than promises". I don't think so. My takeaway from this post is that, having been persuaded core.async is awesome and will give me joy as a programmer (and see David's other posts for evidence of that, e.g. [1]), performance concerns should not prevent me from going for it, because it's competitive.

For me personally, Clojure and ClojureScript still have a long way to go to catch up with JavaScript-based development using Node.js and say, Angular. A smaller community, fewer libraries and less mature tools makes it kind of a bummer from a social perspective. I'm also not impressed with the Clojure/ClojureScript code-sharing story, which requires hacks[2] to use the same code on the client and server. Here, Node frameworks like Derby[3], which emphasizes sharing nearly all your code, and Airbnb's Rendr[4], seem light-years ahead.

But when you consider that this is an early release of a language and async lib written by a surprisingly small team, with basically no corporate backing, it's a pretty amazing accomplishment. I love ClojureScript in theory and I'm eager for the day when I can love it in practice too.

[1] http://swannodette.github.io/2013/07/31/extracting-processes...

[2] https://github.com/lynaghk/cljx

[3] http://derbyjs.com/

[4] https://github.com/airbnb/rendr


I'm also not impressed with the Clojure/ClojureScript code-sharing story, which requires hacks[2] to use the same code on the client and server.

I think this is definitely a "the glass is half-empty" interpretation. I mean, let's point out that you're complaining about an environment where you can share code between a codebase running on the server and one on the client--and it isn't JavaScript. And it's not ideal? Of course it's not ideal. But you can do inter-op with Java (server-side) and JavaScript (client-), using the same syntax, and with cljx, you can do this in the same file. This is freaking awesome, even with the messy bits that come along with it, in my opinion.

A smaller community, fewer libraries and less mature tools makes it kind of a bummer from a social perspective.

As far as this point, there's less I can disagree with: the community, while small, is awesome, but there is a way to go before ClojureScript, at least, is going to have the ease of "install and get going" that you have with other platforms. Everything from testing to getting a reasonable console up and running is in a state of flux and cannot be favorably compared to JavaScript, other than the argument that ClojureScript provides a better enough programming experience to outweigh the ease-of-setup that JS provides. For me that's more than enough, but for others I can understand that it's not, yet.

But it absolutely is improving, very quickly. We really need more people to be using CLJS and hyping it (as David Nolen is doing) and putting in the time to shave the yaks that need shaving--as people like David Nolen, Kevin Lynagh (http://keminglabs.com/blog/cljs-app-designs/ as well as the aforementioned cljx), Chas Emerick (many contributions, but recently https://github.com/cemerick/austin), Mimmo Cosenza (the fantastic Modern CLJS series: https://github.com/magomimmo/modern-cljs) and many others are doing--so that we can get to that point where the advantages clearly outweigh the disadvantages. I think that time will come, but it's going to require some serious work.

But even now there are compelling reasons to use ClojureScript, including libs like core.async. I think you'd find that, with a bit of patience, there is a lot there to keep you invested in the tools and the community.

EDIT: I just want to add that cljx is not the only or oldest or simplest way to share code. Crossovers using lein-cljsbuild have provided this for a while: https://github.com/emezeske/lein-cljsbuild/blob/master/doc/C...


Thanks; great response. I had forgotten about crossovers.

I did a little project in CLJS and found the additional power of the language nice, but not revolutionary. I've found getting into Node.js that despite the well-known warts of JavaScript, the community has done an amazing job working around those problems, building powerful abstractions, and shipping simple, un-complected code. I hardly feel like I'm compromising on expressivity.

So for me, language like "callback hell" and "wasteful" rings a bit hollow, as it doesn't reflect my experience writing JavaScript. In fact, if I were a contributor to When.js I could imagine it being pretty insulting.

Maybe we can win more people to ClojureScript by recognizing, not diminishing, the great stuff folks have made with vanilla JS, and showing how CLJS provides interop while making JavaScript programming even better. Kevin Lynagh's post here[1] is a nice example of that.

[1] http://keminglabs.com/blog/angular-cljs-weather-app/

As for my "glass is half-empty" interpretation of code-sharing, I'm thinking of how you can trivially require jQuery from a Node.js application and use it for scraping[2]. Or how you can require('http') in the browser and effortlessly make HTTP requests in browser or server with the same code[3]. I'd love to be proven wrong, but ClojureScript seems far away from this kind of stuff. You can't use Compojure or HTTP-Kit in ClojureScript. So you could say that while Clojure has a decent client/server code-sharing story, there's this other thing, JavaScript, that has an amazing one.

[2] http://blog.nodejitsu.com/jsdom-jquery-in-5-lines-on-nodejs

[3] https://github.com/substack/http-browserify


I think this article is comparing apples to oranges.

The promise pattern is not meant to speed up callback-based code, or to eliminate callbacks altogether.

The purpose of a promise is to allow an asynchronous operation to be cancelled. In the old way, there'd be no standard way to cancel something like this,

  xhr.get(url, function (data) { 
    // do something with the data
  });
If we have a promise, we can cancel it,

  getPromise = xhr.get(...);
  
  // later...
  getPromise.cancel();
By creating a standard promise pattern, you can have any number of heterogeneous asynchronous operations chained or canceled together uniformly.


> The purpose of a promise is to allow an asynchronous operation to be cancelled.

It's not, and the javascript Promise/A spec has no such facility.

Promises — at least in javascript which is the language observed here — are used to return asynchronous results and be able to combine them in various manners (chain, multiplex, select), but not to cancel them.

The purpose of promises in javascript is very much to improve upon callbacks-based systems in order to make them easier to understand and reason about, and more composable.


That's a really good point. I hadn't been completely sold on the value of promises over callbacks before, but this seems like a very unambiguous advantage. I can't think of an obvious, clean way to do this with callbacks that doesn't go a long way toward simply re-implementing promises.

I imagine an advocate of core.async would argue that they have a good analog for canceled promises, though, namely closing channels. On the other hand, closed channels will still yield nil when read from; the procedures trying to read from a closed channel would need to check for that.


> I can't think of an obvious, clean way to do this with callbacks that doesn't go a long way toward simply re-implementing promises.

I may be misunderstanding your point, but you could name the callback function, and give the function object itself a "cancel()" method which alters its behavior to do nothing.


Yes, but that doesn't give you a great way to deal with attaching and canceling multiple callbacks simultaneously. If you want to perform several independent asynchronous actions after some other operation finishes, you have to collect them into a single giant callback in one location. And as you mention, the function has to keep track of some piece of mutable state in order to decide whether it's cancelled or not before firing off those operations. So why not give the function a mutable array of asynchronous operations to perform to begin with, which you can then add to at any time? And at that point, you already have a something very close to a promise.


As graue and David wrote, the main point is not to win on this microbenchmark, but that core.async is competitive. Since a bare-bones version of promises is easy to code, though, I tried it out for myself:

https://gist.github.com/darius/6326461

In Firefox Nightly it's usually slower than both of the OP's examples, while in Chrome it's substantially faster than both. So I'm curious how when.js goes faster on Firefox while supporting more features, and whether it could be tuned to catch up with my naive code on Chrome.


JS promise libraries are slow because they're poorly factored and constrained by the limitations of the JS runtime environment and the low quality of typical JS code. Don't extrapolate from the miserable experience of using promises in JS to assume that promises are useless as a programming construct.

core.async looks interesting though; I assume I can't just consume it as a library and instead must rewrite my whole application in (clojurescript? you literally never mention in this post what language that is)? Too bad, but I can see how for this type of thing you need compiler support.


I haven't tried it yet but you can use clojurescript functions from javascript. The more common direction of interop (from using js from clojurescript) is quite nice and it looks like the opposite is also quite nice.

Best writeup about that I've seen so far is here: http://lukevanderhart.com/2011/09/30/using-javascript-and-cl... (in the exporting section)

I'm not sure if there are any implications I'm not aware of that would make it less useful to use core.async from js land.


> (clojurescript? you literally never mention in this post what language that is)?

For clarity, core.async is a library that can be used in both Clojure and Clojurescript. There are internal implementation and performance differences, but the behavior of the library is the same for both.


core.async is a Clojure library and is built with no compiler support.


To be fair, a lot of the syntax magic of core.async happens in its macros -- in js that is supported by the cljs compiler. As I understand it, because of this it'd be impossible for core.async to be used as a js "library" (a global namespace with a public interface).


In terms of performance there is huge room of improvement in when.js.

    $ node --trace_inlining go_when.js
    Did not inline fulfilled called from coerce (target contains unsupported syntax [early]).
    Did not inline NearFulfilledProxy called from fulfilled (target requires context change).
    Did not inline near called from fulfilled (target contains unsupported syntax [early]).
    Did not inline Promise called from near (target requires context change).
    Did not inline enqueue called from scheduleConsumers (target requires context change).
    Did not inline b called from enqueue (target not inlineable).
    Did not inline fn called from NearFulfilledProxy.when (target requires context change).
Also some functions are constantly being recompiled and try catch is corrupting (making them not optimizable under current v8) some large functions instead of being isolated.


Author's shouldn't assume that every reader can identify every language by sight! Thanks HN commenters for identifying core.async as a Clojure library.


I find this comparison weird. To me this is effectively a promise library in the general sense. That promises in js require you to take the promised value in a callback is a limitation of the js environment, not the concept of promises themselves. And it's a limitation I assume core.async is no more able to escape at its lowest levels than When.js is.

I grant that go/core.async-style channels are a superset of promise/future kind of functionality, but to me they are definitely part of the same family. And you can definitely implement promises in terms of them.


To paraphase Rich -- "promises are the one-night stand of asynchronous programming, core.async is aiming for a longer term relationship."

The big win isn't that you have the ability to await and fulfill messages, but rather that you have first class entities that can be used to send and receive discrete and coordinated messages.

What's better than simply having these communication channels (think event emitters) is having the ability through clear syntax to synchronize not only the receipt of these messages, but also to coordinate message sending through the same uniform style.

To me, this seems just about as related to promises as promises are related to callbacks, in that they are all patterns for asynchronous programming.

To drive the point home: "I grant that promises are a superset of callbacks kind of functionality, but to me they are definitely part of the same family. And you can definitely implement promises in terms of them."


I don't think that it's true that promises are a superset of callbacks. Although, again, owing to the particular nature of javascript the implementations may be. A promise is like a go channel that can only be sent to once. A callback has utterly arbitrary behaviour: it can be called many times with many values in many contexts.

Promises and channels are synchronization primitives. Callbacks are not.


> Promises are all the rage in the JavaScript world even though they don't actually eliminate callback hell and their design emphasizes coarse grained asychronous operations leaving much to be desired for the vast universe of possible interactive applications (cough, user interfaces).

Bold (and interesting) claims. I await the blog post substantiating these claims.


One way to test the claim: use promises to code his example http://swannodette.github.io/2013/08/17/comparative/ . Is it necessarily hellish? I might get around to looking into this tomorrow if nobody else does.


That's nice and all, but can I improve upon promises without using another language on top of JS? I don't have any issue with such languages in principle, but I spend most of my time working in plain JS codebases these days, and I'm wondering if I can improve upon promise-based asynchronicity.


Fascinating stuff, however, as a clojure user, I just don't care yet. JS UI frameworks are made of dev ease of use, not speed. I'm glad this stuff exists, I just hope that useable frameworks are the next phase in the development of clojurescript.


Well, the start of it still applies. Promises aren't better than callbacks. Some people prefer them, but they aren't better.


Does anyone know to what extend the new yield operator replaces the need for promises? Can it be used for most of promises' purposes or do the latters have their particularly nice use cases? It's still confusing after a while.


Promises and generators actually work magnificently well together. I would recomend taking a look at an implementation of Task.js [1] [2] and, for example, some of its (increasing) usage across the Mozilla codebase [3].

[1] https://github.com/mozilla/mozilla-central/blob/master/toolk...

[2] https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_...

[3] http://dxr.mozilla.org/mozilla-central/search?tree=mozilla-c...


These comparisons bring up memories of when we used to benchmark empty for loops. I think today’s microbenchmarks are starting to feel just like that.

Comparing the execution time of a hundred thousand (sync or not) callbacks vs. a hundred thousand (sync or not) promises is, in many cases, not a good or deciding factor for what tool you should pick from the toolbox. I would assert that in realistic scenarios, it really doesn't matter.


To be clear, this is benchmarking a go-like channel abstraction, which is nicer than callbacks or promises imo, which is why it's nice to see it's performant.


Strangely I find the promises benchmark faster than the core.async one on the Firefox OS browser.


The author's point is that core.async is competitive in speed, not necessarily faster, but better abstractions than promises.


What a confusing title.


Guess what - in the time that it took me to read this post, Moore's Law solved the problem.

Next.


"Software efficiency halves every 18 months, compensating Moore's Law." - May's Law


How's that? JS is single-threaded.


Wouldn't that just mean the event loop cycles faster, lending credence to the gp's snark?


Why would having multiple cores make the event loop cycle faster? Maybe I misunderstood and he was using "Moore's law" to refer to exponential increase in processor speed rather than transistor density (which is what I understand Moore's law to be talking about). But commercial processors aren't really getting much faster anymore.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: