Here is the output.
OutputStep 1 finished.
Step 2 finished.
Step 3 finished.
Step 4 finished.
Step 5 finished.
All steps done.
Although dead simple, the example shows a clear-cut separation between the work logic and the control logic. The worker function, doTask, does not know or care anything about how it is called; while the main function focuses on execution flow.
Now let's make the worker function asynchronous.
The asynchronous version of doTask initiates a task, and returns immediately. The task will finish after a random delay. The asynchronicity distorts execution flow. The execution flow does not follow code flow any more. The final report is always executed first; and then the 5 steps are finished in random order. Below is the output of a typical run.
OutputAll steps done.
Step 4 finished.
Step 2 finished.
Step 3 finished.
Step 1 finished.
Step 5 finished.
In order to restore the original execution flow using old-fashioned callbacks, the code flow has to be re-structured. There are two options to re-structure the code. The first option is to use nested callbacks:
Listing 1: http://jsfiddle.net/dingjing/prCDL/2/
Two observations for nested callbacks:
- The worker function is no longer independent of control logic. It needs to know what to do next.
- The main function is not scalable any more. It is trivial to scale the synchronous version to 100 steps or even 1 million steps. Try that on the nested callbacks!
- The other option is to use recursive callbacks:
Listing 2: http://jsfiddle.net/dingjing/YRZn7/2/
Now, the work logic and the control logic are completely mixed together. Although this version is shorter than the nested callback version, it is actually harder to READ the code to figure out what it does.
One way or the other, callbacks break the separation between the work logic and the control logic, mix them together, and make spaghetti-like code. If we add task dependency into the control logic, callbacks will make the code even more tangled, as shown in the next code example.
Branch example
In this example, the first task (doTask1) returns a random integer between 0 and 10. If the return value is greater than or equal to 5, then task 2 (addTask) is executed; otherwise, task 3 (multiplyTask) is executed. First, let's have a look at the synchronous version. Please note again the separation between work logic and control logic.
Listing 3: http://jsfiddle.net/dingjing/SLLCu/2/
Here is a sample output.
OutputTask 1 returned 6
addTask finished with 6
All tasks finished with 12
Next, let's look at the asynchronous version.
Listing 4: http://jsfiddle.net/dingjing/Zzq8F/2/
Once again, the work logic and the control logic are tangled all over the place. Just reading the code, are you able to figure out what it does without the hint from the synchronous version?
Now it's time to see how Deferred/Promise objects can untangle the spaghetti code.
Deferred/Promise untangle spaghetti code
First, the loop example.
Listing 5: http://jsfiddle.net/dingjing/sPBv6/3/
Then, the branch example.
Listing 6: http://jsfiddle.net/dingjing/aaaVT/1/
Please appreciate how Deferred/Promise objects restore the separation between the work logic and the control logic, making the code very much similar to their synchronous versions.
Paradigm shift
In old-fashioned callback paradigm, asynchronous methods are void black holes. The first time you call an asynchronous method, you pass the point of no return. You lose the control of code flow, giving it up to the asynchronous method.
In the new paradigm, asynchronous methods return Deferred/Promise objects. A Deferred/Promise object is an abstraction of an ongoing process. You can observe the status of the process, attach callbacks to be executed when the process is done or failed, or chain another process to the current process. Most importantly, an asynchronous method returns code flow to its calling method, so you are still in control.
NoteTechnically, traditional asynchronous methods also return to their calling methods. However, those returns are meaningless in terms of code flow control.
Conclusion
jQuery's Deferred/Promise objects return asynchronous methods as observable processes, untangling spaghetti-like callbacks.
You might also be interested in the following related blog posts
jQuery and ASP.NET Article Part 2 Posted
read more
jQuery Intellisense in VS 2008
read more
jQuery Intellisense Updates from Microsoft
read more
Inclusion of JavaScript Files
read more
Download AjaxPro Beta with jQuery Support
read more
|
|
Please login to rate or to leave a comment.