Why are callbacks more “tightly coupled” than promises?
stackoverflow.com
stackoverflow.com
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.
That said, I still much prefer promises as they make it much more explicit.
> 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.
With a promise, I can do two things, I can pass it into some other function, or I can return it to the caller of foo
with a callback, I can do two things, I can supply a callback which passes the result into some function, or I can require that the caller of foo provide a callback, which I pass along to the asynchronous operation I'm calling
these seem roughly isomorphic to me -- I guess a difference is that if I pass the promise to a method I'm allowing the method to decide when to deref it, but that seems different from the "tightly coupled" claim.
If you want to do this with callbacks you'll need to implement a way to cache the value and the result will be equivalent to a promise.
By using an object (in this case a promise) you could cache the result and make it available to anyone adding a listener in the future.
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.
I think the answer is unfortunately simple: callback are typically written inline, and promises are written as separate named functions that are wired up after the fact. You can do whatever you want with both, but the way jQuery implements both encourages better code from promises than callbacks.
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
};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.
This sounds a lot like traditional async Model/View programming. I've traditionally passed a model around, and use subscriptions to listen for "data." That has the added benefit of being reusable, with the model dispatching change events. Promises in this case sound like they are fulfilling a similar role by your description.
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)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.
Actually, they're not. Get ready: https://github.com/promises-aplus/promises-spec/issues/94
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
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".
> 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.
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. 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.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.)
I don't see this as world-changing either, but I understand why people like it.
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'.
// 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); });Callbacks vs. Promises vs. Futures - they can all express the same things - personally I like promises, but YMMV