• 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 !
  • 相关阅读:
    junit基础学习之-多线程测试(6)
    junit基础学习之-参数初始化(5)
    junit基础学习之-junit3和4的区别(4)
    junit基础学习之-断言注解(3)
    junit基础学习之-测试service层(3)
    java 实例 货币格式
    java md5 数据加密
    java 选择一个类,返回该实例对象
    java 输出为2的倍数的方法
    java 实例 设计一个方法,计算一个数的n次幂
  • 原文地址:https://www.cnblogs.com/hghrpg/p/4593811.html
Copyright © 2020-2023  润新知