Item shows how asynchronous APIs perform potentially expensive I/O operations without blocking the Applic Ation from continuing doing work and processing other input. Understanding the order of operations of asynchronous programs can be a little confusing at first. For example, this program prints out "starting" before it prints "finished", even though the both actions appear in the OPP Osite order in the program source:
Downloadasync ("file.txt", function(file) {console.log ("finished");}); Console.log (
The Downloadasync call returns immediately, without waiting for the file to finish downloading. Meanwhile, JavaScript's run-to-completion guarantee ensures that's next line executes before any other Event handlers is executed. This means, "starting" is sure to print before "finished".
The easiest way to understand this sequence of operations is to think of an asynchronous api as initiating rather than performing an operation. the code above first initiates the download of a file and then immediately prints out " Starting ". when the download completes, in some separate turn of the event loop, the registered event handler prints "finished".
So, if placing several statements in a row only works if you need
to do something after initiating an operation how do you sequence
Completed asynchronous operations? for example, what if we need
To look up a url in an asynchronous database and then download
the Contents of that url? it s impossible to initiate both requests
back-to-back:
db.lookupasync ("url", function (URL) { // ? }); Downloadasync (URL, function (text) {//< /span> Error:url is not bound console.log (" contents of "+ URL +": " + text); });
This can " t possibly work, because the url resulting from the data-
Base lookup is needed as the argument to downloadasync, but it s not
In scope. and with good reason: all we ' t available
yet.
The most straightforward answer are to use nesting. Thanks to the power of closures (see Item one), we can embed the second action in the callback to the first:
Db.lookupasync ("url", function(URL) { downloadasync (URL),function( Text) {console.log ("contents of " + URL + ": " +
There is still and callbacks, but the second was contained within the first, creating a closure that had access to the out ER callback's variables. Notice How the second callback refers to URL.
Nesting asynchronous operations is easy, but it quickly gets unwieldy when scaling up to longer sequences:
db.lookupasync ("url", function (URL) { Downloadasync (URL, function (file) { Downloadasync ( "A.txt", function (a) { Downloadasync ( "B.txt", function (b) { Downloadasync ( "C.txt", function (c) { // ... }); }); }); });});
one way to mitigate excessive nesting is to lift nested callbacks back
out as named functions and pass them any additional data they need
as extra arguments. the two-step example above could be rewritten as:
db.lookupasync ("url" function DownloadURL (URL) {downloadasync (URL, function (text) {// still nested showcontents (URL, text); }); function showcontents (URL, text) { Console.log ( contents of "+ URL +": "+ text);}
This still uses a nested callback inside DownloadURL in order to combine the outer URL variable with the inner text Variab Le as arguments to showcontents. We can eliminate this last nested callback with BIND (see Item 25):
Db.lookupasync ("url", function downloadurl (URL) {downloadasync (URL), Showcontents.bind (null, URL));} function showcontents (URL, text) {console.log ("contents of " + URL + ": " + text);}
This approach leads-to-more sequential-looking code, but at the cost of have to name each intermediate step of the Seque NCE and copy bindings from step to step. This can get awkward in cases like the longer example above:
Db.lookupasync ("url", Downloadurlandfiles); functiondownloadurlandfiles (URL) {downloadasync (URL, downloadabc.bind (NULL, URL));}//awkward namefunctiondownloadabc (URL, file) {Downloadasync ("A.txt", //Duplicated BindingsDownloadfiles23.bind (NULL, URL, file));}//awkward namefunctiondownloadbc (URL, file, a) {Downloadasync ("B.txt", //More duplicated bindingsDownloadfile3.bind (NULL, url, file, a));//awkward namefunctiondownloadc (URL, file, a, b) {Downloadasync ("C.txt",//still more duplicated bindingsFinish.bind (NULL, url, file, a, b));}functionFinish (URL, file, a, B, c) {// ... }
Sometimes a combination of the approaches strikes a better balance, albeit still with some nesting:
Db.lookupasync ("url",function(URL) {downloadurlandfiles (URL);}); functiondownloadurlandfiles (URL) {downloadasync (URL, downloadfiles.bind (NULL, URL));}functiondownloadfiles (URL, file) {Downloadasync ("A.txt",function(a) {Downloadasync ("B.txt",function(b) {Downloadasync ("C.txt",function(c) {// ... }); }); });}
Even better, this last step can is improved with an additional abstrac-
tion for downloading multiple files and storing 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 = //
Using Downloadallasync also allows us to download multiple files
concurrently. Sequencing means that each operation cannot even
be initiated until the previous one completes. and some operations
is inherently sequential, like downloading the URL of We fetched from
A database lookup. But if we had a list of filenames to download,
Chances is there's no reason to wait for each file to finish download-
ING before requesting the next. Item explains how to implement
Concurrent abstractions such as Downloadallasync.
Beyond nesting and naming callbacks, it's possible to build higherlevel abstractions to make Asynch Ronous control flow simpler and more concise. Item describes one particularly popular approach. Beyond that, it'sworth exploring Asynchrony libraries or experimenting with abstractions of your own.
Things to Remember
? Use nested or named callbacks to perform several asynchronous operations in sequence.
? Try to strike a balance between excessive nesting of callbacks and awkward naming of non-nested callbacks.
? Avoid sequencing operations that can be performed concurrently.
Item 62:use Nested or Named callbacks for asynchronous sequencing