TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Callbacks are imperative, promises are functional

353 pointsby timcraftabout 12 years ago

17 comments

tomdaleabout 12 years ago
James does a good job of articulating why promises are such a useful abstraction, especially in JavaScript land. I've been working on a project recently that relies heavily on coordinating many asynchronously-populated values, and I don't even want to think about what the code would look like if we were wrangling callbacks manually.<p>We actually extracted our promises implementation from the work we've been doing, and released it as RSVP.js[1]. While other JavaScript promises libraries are great, we specifically designed RSVP.js to be a lightweight primitive that can be embedded and used by other libraries. Effectively, it implements only what's needed to pass the Promises/A+ spec[2]. For a comparison of RSVP.js with other promises-based JavaScript asynchrony libraries, see this previous discussion on Hacker News[3].<p>1: <a href="https://github.com/tildeio/rsvp.js" rel="nofollow">https://github.com/tildeio/rsvp.js</a><p>2: <a href="https://github.com/promises-aplus/promises-spec" rel="nofollow">https://github.com/promises-aplus/promises-spec</a><p>3: <a href="https://news.ycombinator.com/item?id=4661620" rel="nofollow">https://news.ycombinator.com/item?id=4661620</a>
mbostockabout 12 years ago
Not to focus too myopically on the given example, but I can’t help but wonder why it’s a requirement that the first file be handled specially? A less contrived example would make the argument more convincing.<p>If I wanted to compute the size of one file relative to a set, I’d probably do something like this:<p><pre><code> queue() .defer(fs.stat, "file1.txt") .defer(fs.stat, "file2.txt") .defer(fs.stat, "file3.txt") .awaitAll(function(error, stats) { if (error) throw error; console.log(stats[0].size / stats.reduce(function(p, v) { return p + v.size; }, 0)); }); </code></pre> Or, if you prefer a list:<p><pre><code> var q = queue(); files.forEach(function(f) { q.defer(fs.stat, f); }); q.awaitAll(…); // as before </code></pre> This uses my (shameless plug) queue-async module, 419 bytes minified and gzipped: <a href="https://github.com/mbostock/queue" rel="nofollow">https://github.com/mbostock/queue</a><p>A related question is whether you actually want to parallelize access to the file system. Stat'ing might be okay, but reading files in parallel would presumably be slower since you'd be jumping around on disk. (Although, with SSDs, YMMV.) A nice aspect of queue-async is that you can specify the parallelism in the queue constructor, so if you only want one task at a time, it’s as simple as queue(1) rather than queue(). This is not a data dependency, but an optimization based on the characteristics of the underlying system.<p>Anyway, I actually like promises in theory. I just feel like they might be a bit heavy-weight and a lot of API surface area to solve this particular problem. (For that matter, I created queue-async because I wanted something even more minimal than Caolan’s async, and to avoid code transpilation as with Tame.) Callbacks are surely the minimalist solution for serialized asynchronous tasks, and for managing parallelization, I like being able to exercise my preference.
评论 #5467555 未加载
评论 #5467770 未加载
评论 #5467241 未加载
评论 #5471921 未加载
评论 #5467240 未加载
steveklabnikabout 12 years ago
<p><pre><code> &#62; If foo takes many arguments we add more arrows, i.e. foo :: a -&#62; b -&#62; c &#62; means that foo takes two arguments of types a and b and returns something of &#62; type c. </code></pre> Nitpick alert: since everything is curried in Haskell, it's actually more like `foo takes an argument a and returns a function that takes one b and returns one c`.<p>Other than that teeny thing, this article is awesome, and I fully agree. Promises are an excellent thing, and while I'm just getting going with large amounts of JavaScript, they seem far superior to me.
评论 #5467265 未加载
评论 #5467283 未加载
crazygringoabout 12 years ago
This is an interesting perspective. But to me, even having spent a year on a large node.js project, I just don't see how promises would have simplified things at all.<p>If you have some crazy graph of dependencies, I can see how breaking out promises could help simplify things. But I don't feel like that's a super-common scenario.<p>The author says:<p>&#62; * [Promises] are easier to think about precisely because we’ve delegated part of our thought process to the machine. When using the async module, our thought process is:*<p>&#62; <i>A. The tasks in this program depend on each other like so,</i><p>&#62; <i>B. Therefore the operations must be ordered like so,</i><p>&#62; <i>C. Therefore let’s write code to express B.</i><p>&#62; <i>Using graphs of dependent promises lets you skip step B altogether.</i><p>But in most cases, I don't <i>want</i> to skip B. As a programmer, I generally find myself <i>preferring</i> to know what order things are happening in. At most, I'll parallelize a few of database calls or RPC's, but it's never that complex. (And normal async-helper libraries work just fine.)<p>I swear I want to wrap my head around how this promises stuff could be useful in everyday, "normal" webserver programming, but it just always feels like over-abstraction to me, obfuscating what the code is actually doing, hindering more than helping. I want to know, specifically, if one query is running before another, or after another, or in parallel -- web programming is almost entirely about side effects, at least in my experience, so these things often matter an awful lot.<p>I'm still waiting for a real-world example of where promises help with the kind of everyday webserver (or client) programming which the vast majority of programmers actually do.<p>&#62; <i>Getting the result out of a callback- or event-based function basically means “being in the right place at the right time”. If you bind your event listener after the result event has been fired, or you don’t have code in the right place in a callback, then tough luck, you missed the result. This sort of thing plagues people writing HTTP servers in Node. If you don’t get your control flow right, your program breaks.</i><p>I have literally never had this problem. I don't think it really plagues people writing HTTP servers. I mean, you really don't know what you're doing if you try to bind your event listener after a callback has fired. Remember, callbacks only ever fire AFTER your current imperative code has finished executing, and you've returned control to node.js.
评论 #5467863 未加载
评论 #5467160 未加载
评论 #5467248 未加载
评论 #5467171 未加载
评论 #5467192 未加载
评论 #5467164 未加载
评论 #5470532 未加载
ww520about 12 years ago
I feel this is twisting the meaning of functional programming. Excel is not functional. It is declarative. You declare the relationships between the cells and Excel uses those to propagate changes. Just like a makefile is not functional but declarative. The dependency of the relationships are enforced to produce action. SQL is another example of declarative language and it is nowhere near as functional.
评论 #5467157 未加载
评论 #5467143 未加载
评论 #5467162 未加载
评论 #5467117 未加载
ricardobeatabout 12 years ago
Ryan Dahl in February 2010, when Promises were removed from core:<p><pre><code> Because many people (myself included) only want a low-level interface to file system operations that does not necessitate creating an object, while many other people want something like promises but different in one way or another. So instead of promises we'll use last argument callbacks and consign the task of building better abstraction layers to user libraries. </code></pre> Those libraries do exist. There still isn't a canonical Promises specification. Node trying to force promises onto the ecosystem early on would've been like applying brakes and slow down adoption enormously.
评论 #5467832 未加载
SeanDavabout 12 years ago
I don't agree that there is any fundamental difference in functionality between callbacks and promises.<p>Promises don't somehow magically make asynchronous code easy to write while leaving callbacks out in the cold. They have very similar strengths and weaknesses and I didn't find any of the OP's arguments compelling.<p>In fact, if I had to choose, I would take the opposite view and say callbacks are neater, cleaner and more consistent than promises.
评论 #5468530 未加载
spullaraabout 12 years ago
It is hard for me to fathom the negative feelings towards Promises. They are quite clearly a great way to perform async programming in a civilized way (see Twitter's Future/Promise in Finagle on github). JDK 8 will even have the equivalent in CompletableFuture. The only thing better is to combine Promises with coroutines for a more linear programming style like in Flow: <a href="http://www.foundationdb.com/white-papers/flow/" rel="nofollow">http://www.foundationdb.com/white-papers/flow/</a>
评论 #5468445 未加载
评论 #5467697 未加载
graueabout 12 years ago
This code doesn't look right to me:<p><pre><code> // list :: [Promise a] -&#62; Promise [a] var list = function(promises) { var listPromise = new Promise(); for (var k in listPromise) promises[k] = listPromise[k]; </code></pre> Perhaps the assignment is supposed to be the other way around?<p><pre><code> for (var k in promises) listPromise[k] = promises[k];</code></pre>
评论 #5468368 未加载
评论 #5467390 未加载
评论 #5467506 未加载
eldudeabout 12 years ago
Unfortunately, in practice promises end up making your code more difficult to reason about by adding cruft and <i>unnecessary</i> abstraction. They're also very limiting from a control-flow perspective.<p>This is especially noticeable when you have branching behavior / want to resolve a promise early[1]:<p>Branching with promises:<p><pre><code> function doTask(task, callback) { return Q.ncall(task.step1, task) .then(function(result1) { if (result1) { return result1; } else { return continueTasks(task); } }) .nodeify(callback) } function continueTasks(task) { return Q.ncall(task.step2, task); .then(function(result2) { return Q.ncall(task.step3, task); }) } </code></pre> As opposed to with stepdown[2]:<p><pre><code> function doTask(task, callback) { $$([ $$.stepCall(task.step1), function($, result1) { if (result1) return $.end(null, result1) }, $$.stepCall(task.step2), $$.stepCall(task.step3) ], callback) } </code></pre> I would really love for a post to include a non-trivial problem implemented with promises, vanilla callbacks, and async (and I'd be happy to add a stepdown equivalent), and allow people to see for themselves (how in my opinion promises make code harder to read).<p>[1] <a href="http://stackoverflow.com/questions/11302271/how-to-properly-abort-a-node-js-promise-chain-using-q" rel="nofollow">http://stackoverflow.com/questions/11302271/how-to-properly-...</a><p>[2] <a href="https://github.com/Schoonology/stepdown" rel="nofollow">https://github.com/Schoonology/stepdown</a> (docs need updating, view tests for documentation)
just2nabout 12 years ago
Promises are just tools for managing a list of callbacks with less boilerplate. I wouldn't call one imperative and the other functional. Both are functional. You might dislike callback patterns, but through one of the beautiful parts of JS, you can trivially wrap any callback-oriented API you want and have it become a promise based one. I've done this before when I had a very complex dependency graph at the start of a program and a few API calls were callback related. It looks something like this:<p><pre><code> SomeClass.prototype.someActionPromise = function(){ var deferred = makeADeferred(); SomeClass.prototype.someAction.call(this, function(err){ err ? deferred.reject() : deferred.resolve(); }); return deferred.promise(); }; </code></pre> Now you have a promise-based version that makes your code a little cleaner and easier to read.
评论 #5467774 未加载
nigglerabout 12 years ago
" the decision, made quite early in its life, to prefer callback-based APIs to promise-based ones."<p>Rewind to the point when nodejs was being designed. In that world, in the context of javascript, callbacks were the only real pattern that existed. XHR? callback. Doing something in the future? callback.<p>If you imagine node trying to leverage the javascript ecosystem, callbacks were a no-brainer.
评论 #5467083 未加载
评论 #5467084 未加载
评论 #5467058 未加载
评论 #5467060 未加载
评论 #5467407 未加载
ilakshabout 12 years ago
Promises seem cool but if you are not liking callbacks very much you should take a look at just using CoffeeScript indenting two spaces, specifying functions instead of inline, he async module, icedcoffeescript with await and defer, and Livescript with backcalls. All of that is more useful and straightforward than promises.
richoabout 12 years ago
So, calling magic subroutines is more functional than passing about first class functions?
评论 #5467784 未加载
arianvanpabout 12 years ago
This kind of programming certainly is promising &#60;/pun&#62;<p>It's one of the reasons why i started learning haskell.
pk11about 12 years ago
there is also a third approach for those who want to write composable, functional javascript <a href="http://dfellis.github.com/queue-flow/2012/09/21/tutorial/" rel="nofollow">http://dfellis.github.com/queue-flow/2012/09/21/tutorial/</a>
jQueryIsAwesomeabout 12 years ago
All the code and such a big abstraction for the first example when it could have done like this:<p><pre><code> var result = []; paths.forEach(function (i, file){ fs.stat(file, function (err, data){ result.push(data); if (i === 0) { // Use stat size } if (result.length === paths.length) { // Use the stats } }); }); </code></pre> Fairly understandable, more efficient and without introducing logic patterns foreign to many. It also meet his requirements (It is parallel and we only hit every file once)
评论 #5467140 未加载
评论 #5467145 未加载
评论 #5467109 未加载
评论 #5481784 未加载
评论 #5467094 未加载