5 ms·
Why are callbacks more “tightly coupled” than promises?
- taeric 13y agoI'm not sure this makes a strong case. Especially since in this case the callback could be "simplified" by having it such that the callback example looks like: void loadData = function(){ showLoadingScreen(); $.ajax("http://someurl.com", { complete: hideLoadingScreen(function(data){ //do something with the data }) }); }; Where hideLoadingScreen is simply: function hideLoadingScreen(f) { return function(e) { f(e); doHide(); }; } You could probably take this further. Though, I'm not sure how valuable that is.
- brlewis 13y agoIn your code, loadData() function knows that the data is being fetched asynchronously. In the posted code, loadData() doesn't have to know whether getData() works synchronously or asynchronously.
- taeric 13y agoMeh. With higher order functions, this could be achieved as well. I was focusing on the "hide the loading screen" portion of the example. And, honestly, it is not obvious to me when the loading screen gets hidden. In the callback one, that is much more obvious. (Granted, this is as much from familiarity.)
- brlewis 13y agoHiding synchronous vs. asynchronous with higher-order functions means doing it with callbacks. It's generally hard to take a function that was originally written to run synchronously and change it to run asynchronously without changing the way it's called. I don't see this as world-changing either, but I understand why people like it.
- ollysb 13y agoAs I mentioned in the comments, those examples aren't actually equivalent, the callback version could be: var getData = function(callback){ showLoadingScreen(); $.ajax("http://someurl.com", { complete: function(data){ hideLoadingScreen(); callback(data); } }); }; var loadData = function(){ getData(doSomethingWithTheData); } var doSomethingWithTheData = function(data){ //do something with data };
- tyleregeto 13y agoExactly. This is the problem I have with the hype around the promises API, and if you're a Dart developer, Futures. They claim to solve "callback hell" but they can't. Callback hell is the result of poor programming style, not a limitation of the language. Callback hell is just as possible with promises, and there are plenty of examples of it on the internet. I'm not saying promises don't have benefits, and can't result in some nice programming patterns, but generally they are just a different way of doing the same thing. It's not that callbacks are more tightly couples then promises, its that people write their code that way. Their API's have some other nice benefits though.
- rubiquity 13y agoI think Callbacks, Promises and Futures are all more or less the same in terms of coupling. However, I do find Futures to produce the most readable code.
- woah 13y agoI would agree. I've found, in my own code at least, that callback hell only happens with code that is not modular enough. If you separate your logic out into small self-contained modules that do one thing, they never get too deep. Promises allow you to have monolithic code that's not superficially ugly.
- icambron 13y agoYup. I actually adore promises, but I don't buy the decoupling argument at all. And the thing I keep seeing about how promises let you decide what to do with the data after you make the call as opposed to before seems...totally silly, since I had to make that decision when I wrote the code.
- jasallen 13y agoThis is not a very good or complete answer as others are pointing out. Promises allow you to make your async call in a context where you don't know what has to be done next. You pass the promise around and attach to it as needed (and chained if necessary), that is the lack of coupling that is so desirable.
- rdfi 13y agoAnd that is what I pointed out in the answer, although worded a little differently: "If you use a callback, at the time of the invocation of the asynchronous operation you have to specify how it will be handled, hence the coupling. With promises you can specify how it will be handled later."
- taeric 13y agoBut... when that "later" is literally the next clause, I'm not sure the wins. It also gets dangerously close to the reported virtues of try/catch. Since you don't have to specify "how errors are handled until later." Seems the sooner you specify some things the better.
- RyanZAG 13y agoYou can technically achieve this from callbacks as well. Simply pass in the callback as a function to the method. This way you can still make your async call in a context where you don't know what has to be done next - what will be done next can be passed in dynamically to the function. That said, I still much prefer promises as they make it much more explicit.
- sanderjd 13y agoI'm confused by your comment. > Simply pass in the callback as a function to the method. This way you can still make your async call in a context where you don't know what has to be done next - what will be done next can be passed in dynamically to the function. That sounds like you're just describing how callbacks work in general... The point is that you have to know what function to pass in at the time you make the asynchronous call. With promises, you can figure that out later.
- lhorie 13y agoHere's a contrived example: Promises: //model User.get = function() { return getData(); }; User.getOdd = function() { return User.get().then(filterOdd); }; //controller someControllerAction = function() { User.getOdd().then(redirectToHomepageIfEmpty); }; Callbacks: //model User.get = function(callback) { getData(callback) } User.getOdd = function(callback) { User.get(function(data) { if (typeof callback == "function") { callback(filterOdd(data)); } }); }; //controller someControllerAction = function() { User.getOdd(redirectToHomepageIfEmpty) }; Notice how there's a little bit of noise required in the callback version of User.getOdd - i.e. if (typeof callback == "function"). Ideally this kind of noise is something that should be hidden at a framework level, and not show up in application code. Noise can get quite significant when the code is predominantly dealing with asynchrony, e.g. try implementing this in callback style: jQuery.when(User.getOdd(), Pinterest.search("something")).then(doSomething) It's doable, but it's messy, because the code that is supposed to be focused on pinterest items and odd users now also needs to deal with flags indicating which http request is done at which point.
- batterseapower 13y agoThinking about this from a Haskell perspective, it seems that with promises the lambda waiting for the callback is basically encapsulated into the promise: type Promise a = (a -> IO ()) -> IO () getData :: Promise String Whereas with callbacks you pass that around explicitly: getData :: (String -> IO ()) -> IO () Promises are a monad, so you get the usual monad advantages of being able to "hide the plumbing" behind the abstraction, hence no tower of explicit callbacks in your JS code. instance Monad Promise where return x = \k -> k x fmap f p = \k -> p (k . f) mx >>= fxmy = \k -> mx (\x -> fxmy x k)
- steveklabnik 13y ago> Promises are a monad, Actually, they're not. Get ready: https://github.com/promises-aplus/promises-spec/issues/94 https://github.com/promises-aplus/promises-spec/issues/94
- jdonaldson 13y agoPromise A+ is scary bad. They're trying to shoehorn all of the functionality of a monad based implementation into a single method: http://promises-aplus.github.io/promises-spec/#the__method http://promises-aplus.github.io/promises-spec/#the__method Of course, this relies on a lot of dynamic inspection and runtime checks. Of course, this means that you can't compose with A+ promise methods in a reliable way. Of course, this means that their implementation is not going to be compatible with anyone else's. Of course, this means that everyone else's version of promises is "broken". If you're interested in approaches that follow algebraic/monad patterns, check out fantasyland spec: https://github.com/fantasyland/fantasy-land https://github.com/fantasyland/fantasy-land The name of the spec is a joke, but the spec itself is not. It's referencing a very real sentiment: That developers that want to use formal composition techniques in their js are living in a "fantasy land".
- jerf 13y agoThinking about this from a Haskell perspective isn't very helpful, because what you actually get is simply an "IO String", and it doesn't behave much like any Node option. If you want concurrency, you use forkIO, or one of the many things built on top of it (async, concurrent pipes, whatever), and if you don't, you don't. Regardless, the IO type is always "async" in the way that Node types define it (or, alternatively, transcends the way Node uses the term entirely); there is no distinction between an IO String that is simply 'return "a string"' and an IO String that actually hits a web server API to figure out what other web server to hit for a file which will then be fed to an API which will return a string... that's still just an "IO String". Haskell does have a thing from the promise tradition, but it's a different definition of "promise" than JS is using here, and it manifests in Control.Concurrent.MVar. (And, again, let me be clear, if that doesn't look like what the JS is doing here, correct. The wikipedia page is helpful. [1] AFAIK, JS is not wrong in its usage of the term promise, but there's a lot of other academic study behind the term and there's a lot of different variants in that history.) You don't need a promises monad, IO already does it, combined with the scheduler. [1]: http://en.wikipedia.org/wiki/Futures_and_promises http://en.wikipedia.org/wiki/Futures_and_promises
- lightblade 13y agoI like harpo's answer better than the accepted answer. I'll help generate more upvote for that answer from here :) > The coupling is looser with promises because the operation doesn't have to "know" how it continues, it only has to know when it is ready. > When you use callbacks, the asynchronous operation actually has a reference to its continuation, which is not its business. > With promises, you can easily create an expression over an asynchronous operation before you even decide how it's going to resolve. > So promises help separate the concerns of chaining events versus doing the actual work.
- fleitz 13y agoIt's about reification... Callbacks are an implementation of asynchronous processing each slightly different sort of like 'function calls / calling convention' in assembler, while promises provide a reified interface that is easier to think about conceptually because you're thinking about what to do, not how to do it. The contrived example in SO can just as easily be decoupled while still using callbacks, however the promise based code 'looks' better because it more concisely describes what's going on in terms of 'english'.
- y1426i 13y agoHere's an example which might explain the decoupling a bit: // You may call this syntactic sugar var p1 = new Promise(function(response, reject) { setTimeout(function(){ response(Math.random() * 1000); }); }).then(function(val){ console.log(val); return val; }); // But here's the magical decoupled call p1.then(function(val) { console.log(val); });
- pacaro 13y agoI can't work out if this is an “Async Tarpit” or an instance of the “Blub Paradox” Callbacks vs. Promises vs. Futures - they can all express the same things - personally I like promises, but YMMV