Item 62: Use Nested or Named Callbacks for Asynchronous Sequencing

Item  61  shows  how  asynchronous  APIs  perform  potentially  expensive  I/O  operations  without  blocking  the  application  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 two actions appear in the opposite order in the program source: 

downloadAsync("file.txt",  function(file)  { 
  console.log("finished"); 
}); 
console.log("starting"); 

The downloadAsync call returns immediately, without waiting for the file to finish downloading. Meanwhile, JavaScripts run-to-completion guarantee ensures that the next line executes before any other event handlers are executed. This means that "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?  Its  impossible  to  initiate  both  requests 
back-to-back: 

db.lookupAsync("url",  function(url)  { 
  //  ? 
}); 
downloadAsync(url,  function(text)  {  //  error:  url  is  not  bound 
  console.log("contents  of  "  +  url  +  ":  "  +  text); 
}); 

This cant possibly work, because the URL resulting from the data-
base lookup is needed as the argument to downloadAsync, but its not 
in scope. And with good reason: All weve done at that step is initiate 
the database lookup; the result of the lookup simply isnt available 
yet. 

The  most  straightforward  answer  is  to  use  nesting.  Thanks  to  the power of closures  (see Item  11), 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  +  ":  "  +  text);
  });
}); 

There are still two callbacks, but the second is contained within the first, creating a closure that has access to the outer callbacks 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",  downloadURL); 

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 variable as arguments to showContents. We can eliminate this last nested callback with bind (see Item 25): 

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 leads to more sequential-looking code, but at the cost of having to name each intermediate step of the sequence and copy bindings from step to step. This can get awkward in cases like the longer example above: 

db.lookupAsync("url",  downloadURLAndFiles); 

function  downloadURLAndFiles(url)  { 
downloadAsync(url,  downloadABC.bind(null,  url));
}

//  awkward  name 
function  downloadABC(url,  file)  { 
    downloadAsync("a.txt", 
//  duplicated  bindings 
downloadFiles23.bind(null,  url,  file));
}

//  awkward  name 
function  downloadBC(url,  file,  a)  { 
    downloadAsync("b.txt", 
//  more  duplicated  bindings 
downloadFile3.bind(null,  url,  file,  a));
}

//  awkward  name
function  downloadC(url,  file,  a,  b)  {
downloadAsync("c.txt",
//  still  more  duplicated  bindings
finish.bind(null,  url,  file,  a,  b));
}
function  finish(url,  file,  a,  b,  c)  { 
    //  ... 
} 

Sometimes a combination of the two approaches strikes a better balance, albeit still with some nesting: 

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)  { 
          //  ... 
      }); 
    });
  });
}

Even better, this last step can be 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  =  all[2]; //  ... 
  }); 
} 

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 
are inherently sequential, like downloading the URL we fetched from 
a  database  lookup.  But  if  we  have  a  list  of  filenames  to  download, 
chances are theres no reason to wait for each file to finish download-
ing before requesting the next. Item  66 explains how to implement 
concurrent abstractions such as downloadAllAsync. 

Beyond nesting and naming callbacks, its possible to build  higherlevel  abstractions  to  make  asynchronous  control  flow  simpler  and more concise. Item  68 describes one particularly popular approach. Beyond that, its worth 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. 

progress every day !
原文地址:https://www.cnblogs.com/hghrpg/p/4593811.html