Quantcast
Channel: Hacker News 50
Viewing all articles
Browse latest Browse all 9433

What's The Point Of Promises?

$
0
0

Comments:" What's The Point Of Promises? "

URL:http://www.kendoui.com/blogs/teamblog/posts/13-03-28/what-is-the-point-of-promises.aspx


By this point in time, just about every JavaScript developer and their grandma has heard about Promises. If you haven't, then you're about to. The idea for promises is laid out in the Promises/A spec by the folks in the CommonJS group. Promises are generally used as a means for managing callbacks for asynchronous operations, but due to their design, they are way more useful than that. In fact, due to their many uses, I've had numerous people tell me - after I've written about promises - that I've "missed the point of promises". So what is the point of promises?

A Bit About Promises

Before I get into "the point" of promises, I think I should give you a bit of insight into how they work. A promise is an object that - according to the Promises/A spec - requires only one method: then. The then method takes up to three arguments: a success callback, a failure callback, and a progress callback (the spec does not require implementations to include the progress feature, but many do). A new promise object is returned from each call to then.

A promise can be in one of three states: unfulfilled, fulfilled, or failed. A promise will start out as unfulfilled and if it succeeds, will become fulfilled, or if it failed, will become failed. When a promise moves to the fulfilled state, all success callbacks registered to it will be called and will be passed the value that it succeeded with. In addition, any success callbacks that are registered to the promise after it has already become fulfilled will be called immediately.

The same thing happens when the promise moves to the failed state, except that it calls the failure callbacks rather than the success callbacks. For the implementations that include the progress feature, a promise can be updated on its progress at any time before it leaves the unfulfilled state. When the progress is updated, all of the progress callbacks will be immediately invoked and passed the progress value. Progress callbacks are handled differently than the success and failure callbacks; If you register a progress callback after a progress update has already happened, the new progress callback will only be called for progress updates that occur after it was registered.

We won't get into how the promise states are managed because that isn't in the spec and it differs per implementation. In later examples you'll see how it's done, but for right now this is all you need to know.

Handling Callbacks

Handling callbacks for asynchronous operations, as previously mentioned, is the most basic and most common use of promises. Let's compare a standard callback to using a promise.

Callbacks

// Normal callback usage
asyncOperation(function() {
 // Here's your callback
});
// Now `asyncOperation` returns a promise
asyncOperation().then(function(){
 // Here's your callback
});

Just by looking at this example, I doubt anyone would really care to use promises. There seems to be no benefit, except maybe that "then" makes it more obvious that the callback function is called afterasyncOperation has completed. Even with this benefit, though, we now have more code (abstractions should make our code shorter, shouldn't they?) and the promise will be slightly less performant than the standard callback.

Don't let this deter you, though. If this was the best that promises could do, this article wouldn't even exist.

Pyramid of Doom

Many articles that you find on the web will cite what is called the "Pyramid of Doom" as their primary reason for using promises. This is referring to needing to perform multiple asynchronous operations in succession. With normal callbacks, we'd end up nesting calls within each other; with this nesting comes more indentation, creating a pyramid (pointing to the right), hence the name "Pyramid of Doom". If you only need to perform a couple asynchronous operations in succession, then this isn't too bad, but once you need to do 3 or more, it becomes difficult to read, especially when there's a decent amount of processing that needs to be done at each step. Using promises can help the code flatten out and become more readable again. Take a look.

Pyramid Of Doom

// Normal callback usage => PYRAMID OF DOOOOOOOOM
asyncOperation(function(data){
 // Do some processing with `data`
 anotherAsync(function(data2){
 // Some more processing with `data2`
 yetAnotherAsync(function(){
 // Yay we're finished!
 });
 });
});
// Let's look at using promises
asyncOperation()
.then(function(data){
 // Do some processing with `data`
 return anotherAsync();
})
.then(function(data2){
 // Some more processing with `data2`
 return yetAnotherAsync();
})
.then(function(){
 // Yay we're finished!
});

As you can see, the use of promises makes things much flatter and more readable. This works, because - as mentioned earlier - then returns a promise, so you can chain then calls all day long. The promise returned by then is fulfulled with the value that is returned from the callback. If the callback returns a promise (as is the case in this example), the promise that then returns is fulfilled with the same value that the promise your callback returned is fullfilled with. That's a mouthful, so I'll walk step-by-step to help you understand.

asyncOperation returns a promise object. So we call then on that promise object and pass it a callback function; then will also return a promise. When the asyncOperation finishes, it'll fulfill the promise with data. The callback is then invoked and data is passed in as an argument to it. If the callback doesn't have a return value, the promise that was returned by then is immediately fulfilled with no value. If the callback returns something other than a promise, then the promise that was returned by then will be immediately fulfilled with that value. If the callback returns a promise (like in the example), then the promise that was returned by then will wait until the promise that was returned by our callback is fulfilled. Once our callback's promise is fulfilled, the value that it was fulfilled with (in this case, data2) will be given to then's promise. Then the promise from then is fulfilled with data2. And so on. It sounds a bit complicated, but it's actually pretty simple. If what I've told you doesn't sink in, I'm sorry. I guess I'm just not the best person to talk about it.

Using Named Callbacks Instead

Apparently promises aren't the only way to flatten out this structure though. After writing a post that mentioned promises solving the Pyramid of Doom problem, a man commented on the post saying...

I think promises are sometimes useful, but the issue of "nested" callbacks (christmas tree syndrome) can be trivially solved by just passing a named function as an argument instead of anonymous functions: asyncCall( param1, param2, HandlerCallback ); function HandlerCallback(err, res){ // do stuff }

His example only gave an example of going one level deep, but it is still true. Let's expand my previous example to make this easier to see.

Named Callbacks

// Normal callback usage => PYRAMID OF DOOOOOOOOM
asyncOperation(handler1);
function handler1(data) {
 // Do some processing with `data`
 anotherAsync(handler2);
}
function handler2(data2) {
 // Some more processing with `data2`
 yetAnotherAsync(handler3);
}
function handler3() {
 // Yay we're finished!
}

Would you look at that! He's absolutely right! It did flatten out the stucture. But there's a problem here that also existed in the old callback example that I didn't mention yet: dependency and reusability. Dependency and reusability are closely tied to one another in an inverse fashion. The fewer dependencies something has, the more reusable it can become. In the above example, handler1 depends on handler2 and handler2 depends on handler3. This means that handler1 can't be reused for any purpose unless handler2 is also present. What's the point of naming your functions if you're not going to reuse them?

The worst part is that handler1 doesn't care at all what happens in handler2. It doesn't need handler2 at all except to make anotherAsync work. So, let's remove those dependencies and make the functions more reusable by using promises.

Chained Callbacks

asyncOperation().then(handler1).then(handler2).then(handler3);
function handler1(data) {
 // Do some processing with `data`
 return anotherAsync();
}
function handler2(data2) {
 // Some more processing with `data2`
 return yetAnotherAsync();
}
function handler3() {
 // Yay we're finished!
}

Isn't this so much better? Now handler1 and handler2 couldn't care less if any other functions existed. Wanna see something really great? Now handler1 can be used in situations that don't need handler2. Instead, after handler1 is done, we'll use anotherHandler.

Reusing Functions

asyncOperation().then(handler1).then(anotherHandler);
function handler1(data) {
 // Do some processing with `data`
 return anotherAsync();
}
function anotherHandler(data2) {
 // Do some really awesome stuff that you've never seen before. It'll impress you
}

Now that handler1 is decoupled from handler2 it becomes able to be used in more situations, specifically ones where we don't want the functionality that handler2 provides. This is reusability! The only thing that the commenter's "solution" did was to make the code more readable by eliminating the deep indentation. We don't want to eliminate indentation merely for the sake of eliminating indentation. The numerous levels of indentation is merely a sign that something is wrong, it is not the problem itself. It's like a headache that is caused by dehydration. The real problem is the dehydration, not the headache. The solution is to get hydrated, not to take some pain medicine.

Parallel Asynchronous Operations

In the article that I mentioned earlier, I had compared promises to events for handling asynchronous operations. Sadly, I didn't do a very good comparison, as noted by a couple people in the comments. I showed the strengths of promises, then moved onto events where I showed their strengths as they were being used in my particular project. There was no comparison and contrasting. One commenter wrote this (with a few grammar fixes):

I think using the example in the post is a bad comparison. An easy demonstration of the value of promises would be if pressing the imaginary "Start Server Button" had to start not only a web-server but also a database server and then update the UI only when they both were running. Using the .when method of promises would make this "multiple-asynchronous-operations" example trivial, whereas reacting to multiple asynchronous events requires a non-trivial amount of code.

He was absolutely right. I didn't actually compare the two. The point of the article was actually to show that promises aren't the only mechanism for asynchronous operations and in some situations, they aren't necessarily the best either. In the situation that the commenter pointed out, promises would definitely be the best solution. Let's take a look at what he's talking about.

jQuery has a method named when that will take an arbitrary number of promise arguments and return a single promise. If any of the promises passed in fails, then the promise that when returned will fail. If all of the promises are fulfilled, then each of their values will be passed to the attached callbacks in the order that the promises were specified.

It's very useful for doing numerous asyncronous operations in parallel and then only moving on to the callback after every one of them is finished. Let's take a look at a simple example.

jQuery.when

// Each of these async functions return a promise
var promise1 = asyncOperation1();
var promise2 = asyncOperation2();
var promise3 = asyncOperation3();
// The $ refers to jQuery
$.when(promise1, promise2, promise3).then(
 function(value1, value2, value3){
 // Do something with each of the returned values
 }
);

This is often referred to as one of the best things about promises, and is partially the point of promises. I agree that it's a wonderful feature that greatly simplifies some things, but this mechanism for the when method isn't even described in any of the Promises specs, so I doubt it's the point of promises. There is a spec that details a method named when, but it is entirely different. jQuery is the only library that I can find that contains this version of the when method. Most promises libraries, such as Q, Dojo, and when implement the when method according to the Promises/B spec, but don't implement the when method that is being talked about by the commenter. However, the Q library uses a method named all and when.js has a method named parallel that works the same way except that they take an array, rather than an arbitrary number of arguments.

Value Representation

Another commenter left me this note:

A Promise is a better way to handle the following scenario: "I want to find an user in this database, but the find method is asynchronous." So, here we have a find method which doesn't immediately return a value. But it does eventually "return" a value (by the means of a callback), and you want to handle that value in some way. Now, by using callback you can define a continuation, or "some code that will deal with that value at a later time." Promises change that "hey, here's some code to deal with the value you'll find". They are something that allow the "find" method to say "hey, I'll be busy finding you the information you asked for, but on the mean time you can hang onto this Thing that represents that value, and you can handle it in any way you want in the mean time, just like the actual thing!" Promises represent real values. That's the catch. And they work when you can deal with the Promises in the same way you'd do with the actual thing. That Promise implementations in JavaScript expect you to pass a callback function to it is just an "accident", it's not the important thing.

I believe this really is the point of promises. Why? Read the first sentence of the Promises/A spec: "A promise represents the eventual value returned from the single completion of an operation." Makes it kinda obvious doesn't it? Well, even if that is the point, that won't stop me from presenting other people's views later in this article. Anyway, let's talk about this idea a bit.

This concept is wonderful, but how does it flesh itself out in practice? What does using promises as a representation of values look like? First let's take a look at some synchronous code:

Synchronous Code

// Search a collection for a list of "specific" items
var dataArray = dataCollection.find('somethingSpecific');
// Map the array so that we can get just the ID's
var ids = map(dataArray, function(item){
 return item.id;
});
// Display them
display(ids);

Ok, this works great if dataCollection.find is synchronous. But what if it's asynchronous? I mean look at this code; it's totally written in a synchronous way. There's no way this could work exactly the same if find is asynchronous, right? WRONG. We just need to change map and display to accept promises as arguments to represent the values they'll be working on. Also, find and map need to return promises. So let's assume that dataCollection doesn't actually contain data: it just makes AJAX calls to retrieve the data. So now it'll return a promise. Now, let's rewrite map and display to accept promises, but we'll give them different names: pmap and pdisplay.

Accept Promises As Parameters

function pmap (dataPromise, fn) {
 // Always assume `dataPromise` is a promise... cuts down on the code
 // `then` returns a promise, so let's use that instead of creating our own
 return dataPromise.then(
 // Success callback
 function(data) {
 // Fulfill our promise with the data returned from the normal synchronous `map`
 return map(data, fn);
 }
 // Pass errors through by not including a failure callback
 );
}
function pdisplay(dataPromise) {
 dataPromise.then(
 // Success callback
 function(data) {
 // When we have the data, just send it to the normal synchronous `display`
 display(data);
 },
 // Failure callback
 function(err) {
 // If it fails, we'll just display the error
 display(err);
 }
 );
}

That wasn't too difficult, was it? Now let's rewrite the example from above using these new functions:

Asynchronous Code

// Search a collection for a list of "specific" items
var dataArray = dataCollection.find('somethingSpecific');
// Map the array so that we can get just the ID's
var ids = pmap(dataArray, function(item){
 return item.id;
});
// Display them
pdisplay(ids);

All I had to do was change the names of the functions to the new ones. If you're ambitious, you could write the functions with the same name, and have them able to accept either a promise or a normal value and react accordingly. In this new code, the promises are used to represent the eventual value that they'll return, so we can write the code in a way that makes it look like the promises are the real values.

I lied a little bit. Above I said "this could work exactly the same", but there is a catch. Other than the name changes for some of the functions, there is something else here that is different: any code that appears after the call to pdisplay will probably be called before the actual displaying has happened. So you either need to make that the code that comes after it doesn't depend on the displaying being finished, or you need to return a promise from pdisplay and have the rest of the code run after that promise is fulfilled. Part of the reason I didn't make pdisplay return a promise in my example is that it didn't have a return value, so the promise wouldn't be used to represent a value, which is what we're talking about in this section.

In any case, you can see how making your functions accept promises, rather than just normal values, can make your code look much cleaner and much closer to working like synchronous code. That's one of the beauties of promises. There's another post about this concept, from a slightly different perspective on Jedi Toolkit's blog.

Using Internally For Fluent APIs

One person who commented on my article said this:

I think those of us writing promises implementations are doing a bad job at explaining what promises are for. My opinion is that you as a user should never have to interact with promises using then(). then() should a way for promise-consuming libraries to interact with each other and to provide fluent APIs. You should still use callbacks and events as usual.

His comment matches pretty nicely with the previous section about using promises to represent values. By using promises to represent values, we can create simpler APIs, as seen above, but we're talking about a chaining API here. Basically, this commenter is telling us to use callbacks at the end of a chain of asynchronous commands, so we're still doing what we're used to doing (using callbacks in the end) and no one can tell that we're using promises. He wants to keep promises away from the normal users and just use them internally within our own libraries. Take a look at this example that queries a database and filters the results more and more asynchronously:

Chained .then Calls

database.find()
.then(function(results1) {
 return results1.filter('name', 'Joe');
})
.then(function(results2) {
 return results2.limit(5);
})
.then(function(resultsForReal) {
 // Do stuff with the results
});

For whatever reason, filter and limit are actually asynchronous. You probably wouldn't think they would be, but that's how this works. Well, the commenter is suggesting that the API be changed so that the user can use it like this instead:

Fluent API Example

database.find().filter('name', 'Joe').limit(5, function(results){
 // Do stuff with the results
});

That seems to make more sense to me. If you have the brains to make this work in your libraries, then this is the route you should take. You could change it up a bit and instead of using a normal callback, you could still return a promise:

Returning A Promise

var results = database.find().filter('name', 'Joe').limit(5);
results.then(function(realResults){
 // Do stuff with the results
});
// OR use results like in the previous section:
doStuffWith(results);

The choice is up to you here. I think enough developers understand promises that there's nothing wrong with returning promises to your users, but it's really a matter of opinion. Either way it's better than the original situation where we needed to chain then calls.

Error Containment for Synchronous Parallelism

There's a pretty-well-known article titled You're Missing the Point of Promises, which falls in line with the topic of this post. In that article Domenic Denicola (nice alliterative name) points us to the part of the Promises/A spec that talks about the then function.

then(fulfilledHandler, errorHandler, progressHandler) Adds a fulfilledHandler, errorHandler, and progressHandler to be called for completion of a promise. The fulfilledHandler is called when the promise is fulfilled. The errorHandler is called when a promise fails. The progressHandler is called for progress events. All arguments are optional and non-function values are ignored. The progressHandler is not only an optional argument, but progress events are purely optional. Promise implementors are not required to ever call a progressHandler (the progressHandler may be ignored), this parameter exists so that implementors may call it if they have progress events to report. This function should return a new promise that is fulfilled when the given fulfilledHandler or errorHandler callback is finished. This allows promise operations to be chained together. The value returned from the callback handler is the fulfillment value for the returned promise. If the callback throws an error, the returned promise will be moved to failed state.

In his article, he points use to that last paragraph, which he refers to as "the most important" paragraph. He says this:

The thing is, promises are not about callback aggregation. That's a simple utility. Promises are about something much deeper, namely providing a direct correspondence between synchronous functions and asynchronous functions.

I completely agree with this observation. He then he goes on to focus very specifically on the final sentence: "If the callback throws an error, the returned promise will be moved to failed state." The main reason he focuses on this sentence is because jQuery's implementation, which supposedly implements the Promises/A spec, doesn't do this. If an error is thrown in a callback, it will go uncaught by the promises implementation.

This is very important for many people, though I haven't yet had a situation that it was a real issue since I don't throw errors very often. The promises equivalant to an error or exception is a failed promise, so if an error occurs, there should be a failure rather than an actual error being thrown. This way we can continue to use promises to parallel synchronous operations. I think we should all go report this bug to jQuery. The more people who report it, the more likely it'll get fixed soon. jQuery's promises are one of the most often used implementations, so we need to make sure that they do it correctly.

Conclusion

DANG! This is definitely the longest post I've ever written. I was expecting it to be half this long! Anyway, the point of promises is to represent the eventual resulting value from an operation, but the reason to use them is to better parallel synchronous operations. Ever since asynchronous programming hit the scene, callbacks have been popping up all over the place, obscuring our code in strange ways. Promises are a means of changing that. Promises allow us the synchronous way of writing code while giving us asynchronous execution of code.

About the Author
Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word. You can follow him @joezimjs.


Viewing all articles
Browse latest Browse all 9433

Trending Articles