Sequence of operations for asynchronous programs
61 describes how the asynchronous API performs potentially costly I/O operations without blocking the application from continuing to process other inputs. Understanding the sequence of operations of asynchronous programs is just beginning to get a little confusing. For example, the following code prints "starting" before printing "finished", even if the program source files for the two actions are rendered in reverse order.
downloadAsync(‘file.txt‘,function(file){ console.log(‘finished‘);});console.log(‘starting‘);
The Downloadasync call returns immediately without waiting for the file to complete the download. At the same time, JS's run-to-completion mechanism ensures that the next line of code is executed before other event handlers are processed. In other words, "starting" will be printed before "finished".
The simplest way to understand the sequence of operations is for an asynchronous API to initiate an operation rather than perform an operation. The above code initiates a download of a file and then immediately prints "starting". When the download is complete, the registered event handler prints "finished" in a separate turn in the event loop. How to concatenate asynchronous operations
If you need to do something after initiating an action, how do you concatenate a completed asynchronous operation if you can only put several declarations in one row? For example, what if we need to find a URL in an asynchronous database and then download the contents of the URL? It is not possible to initiate two successive requests.
db.lookupAsync(‘url‘,function(url){ });downloadAsync(url,function(text){//error:url is bound console.log(‘contents of ‘ + url +‘:‘+text);});
The above code is unlikely to work because the URL results queried from the database need to be the parameters of the Downloadasync method. But it's not within scope. The only step we make is to initiate a database lookup, and the results of the lookup are not yet available. callback function processing
The simplest method of handling is to use nesting. With the magic of closures, the second action is nested in the callback function of the first action.
db.lookupAsync(‘url‘,function(url){ downloadAsync(url,function(text){ console.log(‘contents of ‘ + url +‘:‘+text); });})
There are two callback functions, but the second one is included in the first, creating a variable that the closure can access to the external callback function.
Nested asynchronous operations are easy, but can quickly become cumbersome when extended to a longer sequence.
db.lookupAsync(‘url‘,function(url){ downloadAsync(url,function(file){ downloadAsync(‘a.txt‘,function(a){ downloadAsync(‘b.txt‘,function(b){ downloadAsync(‘c.txt‘,function(c){ //.... }); }); }); });});
Callback-named functions
One way to reduce the number of nesting methods is to use nested callback functions as named functions and to pass them with additional data as additional parameters. The above code can be rewritten as:
db.lookupAsync(‘url‘,downloadURL);function downloadURL(url){ downloadAsync(url,function(text){ showContents(url,text); });}function showContents(url,text){ console.log(‘contents of ‘ + url +‘:‘+text);}
Use the Bind method to eliminate nesting
In order to merge external URL variables and internal text variables as parameters of the Showcontents method, nested callback functions are still used in the DownloadURL method. Here you can use the Bind method to eliminate the deepest nested callback functions.
db.lookupAsync(‘url‘,downloadURL);function downloadURL(url){ downloadAsync(url,showContents.bind(null,url));}function showContents(url,text){ console.log(‘contents of ‘ + url +‘:‘+text);}
This approach makes the code look very sequential, but it needs to be named for each intermediate step in the sequence of operations, and the binding is used step-by-step. This can lead to embarrassing situations, such as when multiple layers are nested.
db.lookupAsync(‘url‘,downloadURLAndFiles);function downloadURLAndFiles(url){ downloadAsync(url,downloadABC.bind(null,url));}function downloadABC(url,file){ downloadAsync(‘a.txt‘,downloadBC.bind(null,url,file));}function downloadBC(url,file,a){ downloadAsync(‘b.txt‘,downloadC.bind(null,url,file,a));}function downloadC(url,file,a,b){ downloadAsync(‘c.txt‘,finish.bind(null,url,file,a,b));}function finish(url,file,a,b,c){ //....}
Combine two methods
Combining these two methods makes the code easier to understand.
db.lookupAsync(‘url‘,function(url){ downloadURLAndFiles(url); });function downloadURLAndFiles(url){ downloadAsync(url,downloadFiles.bind(null,url));}function downloadFiles(url,file){ downloadAsync(‘a.txt‘,function(a){ downloadAsync(‘b.txt‘,function(b){ downloadAsync(‘c.txt‘,function(c){ //... }); }); });}
The final step can be simplified by using an additional abstraction that can download multiple files and store them in an array.
function downloadFiles(url,file){ downloadAllAsync([‘a.txt‘,‘b.txt‘,‘c.txt‘],function(all){ var a=all[0],b=all[1],c=all[2]; });}
Using the Downloadallasync function allows us to download multiple files at the same time. Sorting means that each operation can only be started after the previous operation has completed. Some operations are inherently sequential, such as downloading the URLs we query from the database. But if we have a list of files to download, there is no reason to wait for each file to complete the download before requesting the next one.
In addition to nesting and naming callbacks, you can create higher-level abstractions that make asynchronous control flows simpler and more concise. Tips
Use nested or named callback functions to execute multiple asynchronous operations sequentially
Try to strike a balance between too many nested callback functions and awkward named non-nested callback functions
Avoid sequencing operations that can be executed in parallel
[Effective JavaScript note] 62nd: using nested or named callback functions in an asynchronous sequence