A big reason why performing a series of async tasks is often inconvenient in JavaScript is that you can’t attach handlers to the second task until the first one is complete. As an example, let’s GET data from one URL and then POST it to another.
| var getPromise = $.get('/query'); |
| getPromise.done(function(data) { |
| var postPromise = $.post('/search', data); |
| }); |
| // Now we'd like to attach handlers to postPromise... |
Do you see what the problem is here? We can’t bind callbacks to postPromise until our GET operation is done, because it doesn’t exist yet! It’s created by a $.post call that we can’t make until we have the data that we’re getting asynchronously from the $.get call.
That’s why jQuery 1.6 added the pipe method to Promises. Essentially, pipe says this: “Give me a callback for this Promise, and I’ll give you a Promise that represents the result of that callback.”
| var getPromise = $.get('/query'); |
| var postPromise = getPromise.pipe(function(data) { |
| return $.post('/search', data); |
| }); |
Looks like dark magic, right? Here’s a breakdown: pipe takes one argument for each type of callback: done, fail, and progress. So, in this example, we just provided a callback that gets run when getPromise is resolved. The pipe method returns a new Promise that’s resolved/rejected when the Promise returned from our callback is resolved/rejected.
Effectively, pipe is a window into the future!
You can also use pipe to “filter” a Promise by modifying callback arguments. If a pipe callback returns something other than a Promise/Deferred, then that value becomes the callback argument. For instance, if you have a Promise that emits progress notifications with a number between 0 and 1, you can use pipe to create an identical Promise that emits progress notifications with a human-readable string instead.
| var promise2 = promise1.pipe(null, null, function(progress) { |
| return Math.floor(progress * 100) + '% complete'; |
| }); |
To summarize, there are two things you can do from a pipe callback.
If you return a Promise, the Promise returned by pipe will mimic it.
If you return a non-Promise value (or nothing), the Promise returned by pipe will immediately be resolved, rejected, or notified with that value, according to what just happened to the original Promise.
pipe’s rule for whether something is a Promise is the same as $.when’s: if it has a promise method, that method’s return value is used as a Promise representing the original object. Again, promise.promise() === promise.
pipe doesn’t require you to provide every possible callback. In fact, you’ll usually just want to write
| var pipedPromise = originalPromise.pipe(successCallback); |
or the following:
| var pipedPromise = originalPromise.pipe(null, failCallback); |
We’ve seen what happens when the original Promise succeeds in the first case, or fails in the second case, so that the piped Promise’s behavior depends on the return value of successCallback or failCallback. But what about when we haven’t given pipe a callback for what the original Promise does?
It’s simple. The piped Promise mimics the original Promise in those cases. We can say that the original Promise’s behavior cascades through the piped Promise. This cascading is very handy, because it allows us to define branching logic for async tasks with minimal effort. Suppose we have a three-step process.
| var step1 = $.post('/step1', data1); |
| var step2 = step1.pipe(function() { |
| return $.post('/step2', data2); |
| }); |
| var lastStep = step2.pipe(function() { |
| return $.post('/step3', data3); |
| }); |
Here, lastStep will resolve only if all three Ajax calls succeeded, and it’ll be rejected if any of the three fail. If we care only about the process as a whole, we can omit the variable declarations for the earlier steps.
| var posting = $.post('/step1', data1) |
| .pipe(function() { |
| return $.post('/step2', data2); |
| }) |
| .pipe(function() { |
| return $.post('/step3', data3); |
| }); |
We could, equivalently, nest the second pipe inside of the other.
| var posting = $.post('/step1', data1) |
| .pipe(function() { |
| return $.post('/step2', data2) |
| .pipe(function() { |
| return $.post('/step3', data3); |
| }); |
| }); |
Of course, this brings us back to the Pyramid of Doom. You should be aware of this style, but as a rule, try to declare your piped Promises individually. The variable names may not be necessary, but they make the code far more self-documenting.
That concludes our tour of jQuery Promises. Now let’s take a quick look at the major alternative: the CommonJS Promises/A specification and its flagship implementation, Q.js.
52.15.91.44