37

Can you explain me the following phrase (taken from an answer to Stack Overflow question What are the differences between Deferred, Promise and Future in Javascript?)?

What are the pros of using jQuery promises against using the previous jQuery callbacks?

Rather than directly passing callbacks to functions, something which can lead to tightly coupled interfaces, using promises allows one to separate concerns for code that is synchronous or asynchronous.

Community
  • 1
  • 1
Revious
  • 7,816
  • 31
  • 98
  • 147
  • 2
    `...normal Javascript code` like what? JavaScript promises are only part of ES6 natively. See here for some more details: http://www.html5rocks.com/en/tutorials/es6/promises/ In relation to callbacks vs promises in a general sense, as more abstraction and decoupling you have the better you can test your code and separate the concerns (mind you one can go overboard with abstraction but that is another story). Promises allow you to have a single focuses action not having to know what happened before or will happen after. – Nope Jan 15 '14 at 15:48
  • 1
    You are less dependent on the parameter order when you register a promise. Consider when you want to change a function's declaration from `func(arg1, arg2, callback)` to `func(arg1, arg2, [optional]arg3, callback`) – megawac Jan 15 '14 at 15:50

5 Answers5

51

A promise is an object that represents the result of an asynchronous operation, and because of that you can pass it around, and that gives you more flexibility.

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.

Here's an example, imagine you want to load some data via ajax and while doing that you want to display a loading page.

With callbacks:

void loadData = function(){
  showLoadingScreen();
  $.ajax("http://someurl.com", {
    complete: function(data){
      hideLoadingScreen();
      //do something with the data
    }
  });
};

The callback that handles the data coming back has to call hideLoadingScreen.

With promises you can rewrite the snippet above so that it becomes more readable and you don't have to put the hideLoadingScreen in the complete callback.

With promises

var getData = function(){
  showLoadingScreen();
  return $.ajax("http://someurl.com").promise().always(hideLoadingScreen);
};

var loadData = function(){
  var gettingData = getData();
  gettingData.done(doSomethingWithTheData);
}

var doSomethingWithTheData = function(data){
 //do something with data
};

UPDATE: I've written a blog post that provides extra examples and provides a clear description of what is a promise and how its use can be compared to using callbacks.

Community
  • 1
  • 1
Rui
  • 4,847
  • 3
  • 29
  • 35
  • In the callback example `hideLoadingScreen` executes after `doSomethingWithTheData`, though in promise example `hideLoadingScreen` executes before `doSomethingWithTheData`. I would assume that if data is returned to be injected into the DOM that the hiding of the loading screen should happen after, just as with the callback. the promise example should probably be updated to reflect the same result as the callback example. – Nope Jan 15 '14 at 17:13
  • 17
    Those examples aren't really equivalent, by extracting out getData for the callback version you can get the same benefit, https://gist.github.com/opsb/9413093 – opsb Mar 07 '14 at 15:05
  • @opsb But you can't take advantage of the composability of promises with the callback, like getData does with the show/hidding of the loading screen – Rui Mar 07 '14 at 15:37
  • 1
    @Rui by composability are you referring to the chainability of promises? You can compose callback functions in the same way, the only difference is that you'll end up with the dreaded pyramid of callbacks(each call indented inside the other). – opsb Mar 07 '14 at 15:55
  • @opsb Yes, I was referring to the chainability of promises. I used the term compose because that's the terminology that Martin Fowler used in his [article about promises/futures/deferreds](http://martinfowler.com/bliki/JavascriptPromise.html) – Rui Mar 07 '14 at 15:57
  • 1
    @Rui Martin's referring to something slightly different, something which is in fact a huge advantage of promises, you can parallelise them, his example is: composedPromise = $.when(anAsyncFunction(), anotherAsyncFunction()); – opsb Mar 07 '14 at 15:59
  • @opsb You're right, chainability is a more appropriate term, since composability actually refers to composing several promises. – Rui Mar 07 '14 at 16:04
  • 1
    @opsb: Chaining promises has one (huge) advantage over nested callbacks: You can chain functions which are defined somewhere else. This means you can use the same functions in several locations. – Jørgen Fogh May 20 '14 at 07:48
27

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.

harpo
  • 41,820
  • 13
  • 96
  • 131
  • "When you use callbacks, the asynchronous operation actually has a reference to its continuation, which is not its business."
    Only if you write the code that way. The "proper" way to do this in a old-fashioned language like C++ is to have the showLoadingScreen code wait on a condition variable that gets notified when the loading is complete. More code perhaps, but it' certainly possible to have low coupling with traditional languages if you choose the right idiom.
    – BillT Mar 07 '14 at 21:30
  • @BillAtHRST, "wait on a condition variable that gets notified when the loading is complete" — I'm not sure I understand. How would you do this in javscript, if not essentially by callbacks/promises? – harpo Mar 08 '14 at 04:21
12

I don't think promises are more or less coupled than callbacks, just about the same.

Promises however have other benefits:

  • If you expose a callback, you have to document whether it will be called once (like in jQuery.ajax) or more than once (like in Array.map). Promises are called always once.

  • There's no way to call a callback throwing and exception on it, so you have to provide another callback for the error case.

  • Just one callback can be registered, more than one for promises, and you can register them AFTER the event and you will get called anyway.

  • In a typed declaration (Typescript), Promise make easier to read the signature.

  • In the future, you can take advantage of an async / yield syntax.

  • Because they are standard, you can make reusable components like this one:

     disableScreen<T>(promiseGenerator: () => Promise<T>) : Promise<T>
     {
         //create transparent div
         return promiseGenerator.then(val=>
         {
            //remove transparent div
            return val;
         }, error=>{
             //remove transparent div
             throw error;
         });
     }
    
     disableScreen(()=>$.ajax(....));
    

More on that: http://www.html5rocks.com/en/tutorials/es6/promises/

EDIT:

  • Another benefit is writing a sequence of N async calls without N levels of indentation.

Also, while I still don't think it's the main point, now I think they are a little bit more loosely coupled for this reasons:

  • They are standard (or at least try): code in C# or Java that uses strings are more lousy coupled than similar code in C++, because the different implementations of strings there, making it more reusable. Having an standard promise, the caller and the implementation are less coupled to each other because they don't have to agree on a (pair) of custom callbacks with custom parameters orders, names, etc... The fact that there are many different flavors on promises doesn't help thought.

  • They promote a more expression-based programming, easier to compose, cache, etc..:

      var cache: { [key: string] : Promise<any> };
    
      function getData(key: string): Promise<any> {
          return cache[key] || (cache[key] = getFromServer(key)); 
      }
    

you can argue that expression based programming is more loosely coupled than imperative/callback based programming, or at least they pursue the same goal: composability.

Community
  • 1
  • 1
Olmo
  • 4,257
  • 3
  • 31
  • 35
  • 1
    Those benefits are either trivial or not specific to promises. The point is to have an internal DSL for writing async code mostly the same way sync code is written. – Esailija Mar 07 '14 at 16:04
  • Of course, but is true that, without yield / async syntax, the solution is half backed. I find them useful writing Typescript, where there are type declaratons, not so much on javascript. – Olmo Mar 07 '14 at 16:11
  • 2
    Not really, with `yield` you are stuck with syntactic try-catch while with arrow functions the lines of code and verbosity stay the same level. – Esailija Mar 07 '14 at 16:14
  • 1
    Here's an example of what I mean by being stuck with syntactic try-catch and how yield isn't all that great when compared to promises: http://pastebin.com/h7aD54vQ – Esailija Mar 07 '14 at 16:18
  • for(var i = 0; i<10; i++) yield $.ajax("image/" + i); How you do that without yield? (take the images 1 by 1, not all together) – Olmo Mar 07 '14 at 16:23
  • Your example only reminds me how easy it is to throw literally all benefits of asynchronity away when using `yield`. – Esailija Mar 07 '14 at 16:26
  • 1
    var current = $.when(null); images.forEach(i => current = current.then(_ => $.ajax("image/" + i))); – Gjorgi Kjosev Mar 25 '14 at 16:30
  • @GorgiKosev luckily promises encourage to do the right thing which is to load the images in parallel =) – Esailija Mar 25 '14 at 16:47
  • I've created this function: export function promiseForeach(array: T[], action: (elem: T) => Promise): Promise { return array.reduce>( (prom, val) => prom.then(() => action(val)), Promise.resolve(null)); } But may point is that they are non-trivial transformations. Add a Try/Catch and some conditions and we can be waiting another 20 days, so async / await does make sense. – Olmo Mar 27 '14 at 07:08
  • This answer is confusing. Is that C# you're using as code examples? –  Apr 24 '14 at 05:03
  • Is TypeScript, basically JavaScript with type annotations – Olmo Apr 24 '14 at 08:09
5

Promises reify the concept of delayed response to something. They make asynchronous computation a first-class citizen as you can pass it around. They allow you to define structure if you want - monadic structure that is - upon which you can build higher order combinators that greatly simplify the code.

For example you can have a function that takes an array of promises and returns a promise of an array(usually this is called sequence). This is very hard to do or even impossible with callbacks. And such combinators don't just make code easier to write, they make it much easier to read.

Now consider it the other way around to answer your question. Callbacks are an ad-hoc solution where promises allow for clearer structure and re-usability.

edofic
  • 727
  • 8
  • 15
4

They aren't, this is just a rationalization that people who are completely missing the point of promises use to justify writing a lot more code than they would write using callbacks. Given that there is obviously no benefit in doing this, you can at least always tell yourself that the code is less coupled or something.

See what are promises and why should I use them for actual concrete benefits.

Esailija
  • 138,174
  • 23
  • 272
  • 326
  • I think the coupling argument is valid for the browser, where there's no convention for a callback's arguments like nodes `cb(err,result)`. If all your code uses the same callback convention (unlike, say, Backbone or browser APIs in general) then yes, it's no less coupled than promises. – timruffs Mar 07 '14 at 19:44
  • @timruffles it's just so insignificant compared to other arguments for promises... that's what Petka is saying here. – Benjamin Gruenbaum Mar 20 '14 at 16:48
  • -1: The answer is flame bate, in addition to being incorrect. As harpo has pointed out above, it lets you decouple a callback from the callback's continuation. – Jørgen Fogh May 20 '14 at 07:36
  • @JørgenFogh it's very trivial with callbacks as well, that's why it's not a good point of promises – Esailija May 20 '14 at 13:21
  • Mocking other people for being mistaken is inappropriate, even if they are actually mistaken. That's why I called it flame bait. – Jørgen Fogh May 20 '14 at 13:53