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, JavaScript’s 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?  It’s  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 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’ve done at that step is initiate 
the database lookup; the result of the lookup simply isn’t 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 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",  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 there’s 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, it’s possible to build  higherlevel  abstractions  to  make  asynchronous  control  flow  simpler  and more concise. Item  68 describes one particularly popular approach. Beyond that, it’s 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.

时间: 2024-12-20 03:23:32

Item 62: Use Nested or Named Callbacks for Asynchronous Sequencing的相关文章

Effective JavaScript Item 62 在异步调用中使用嵌套或者命名的回调函数

在一开始,理解异步程序的调用顺序会有些困难.比如,下面的程序中,starting会先被打印出来,然后才是finished: downloadAsync("file.txt", function(file) { console.log("finished"); }); console.log("starting"); downloadAsync方法在执行之后会立即返回,它只是为下载这个行为注册了一个回调函数而已. 由于JavaScript"

PL/SQL : Procedural Language / Structual Query Language and it is an exrension to SQL.

SQL is not very flexible and it cannot be made to react differently to differing sutuations easily. In  SQL queries we normally tell database what we want but not tell it how to do it. SQL : give commands, commands complete with ; PL/SQL : follow the

android:themes.xml

1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- Copyright (C) 2006 The Android Open Source Project 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance wi

Swift语法快速索引

在WWDC的演示中就可以看出来Swift这个更接近于脚本的语言可以用更少的代码量完成和OC同样的功能.但是对于像我一样在战争中学习战争的同学们来说,天天抱着笨Swift Programming Language Reference之类的大部头看不实际.毕竟还是要养家糊口的.而且,那么1000+页内容讲的东西不是什么都要全部在平时工作中用到的.咱们就把平时用到的全部都放在一起,忘记了立马翻开看看,不知不觉的就学会了之后变成习惯.这样多省事. 变量 1 // Variable 2 var int_v

Yii源码阅读笔记(三十三)

ServiceLocator,服务定位类,用于yii2中的依赖注入,通过以ID为索引的方式缓存服务或则组件的实例来定位服务或者组件: 1 namespace yii\di; 2 3 use Yii; 4 use Closure; 5 use yii\base\Component; 6 use yii\base\InvalidConfigException; 7 8 /** 9 * ServiceLocator implements a [service locator](http://en.wi

&lt;Effective C++&gt;读书笔记--Ctors、Dtors and Assignment Operators

<Item 5> Know what functions C++ silently writes and calls 1.If you don't declare them yourself, compilers will declare their own versions of a copy constructor, a copy assignment operator, and a destructor. Furthermore, if you declare no constructo

树 - 二叉树

读了Robert Sedgewick的<算法:C语言实现>(第三版)的第五章,了解了许多关于树,特别是二叉树的知识.这里总结一下.直接看代码(C++)吧. 1 #include <cstdio> 2 #include <cstdlib> 3 #include <queue> 4 #include <stack> 5 6 #define null 0 7 #define NEXTLINE printf("\n") 8 #defi

Android 学习笔记之切换主题

首先要有主题颜色 theme_color.xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <resources> 3 4 <!--红--> 5 <color name="red">#FF6347</color> 6 <color name="dark_red">#F4511E</color> 7 &

模拟ajax的同异步

今天突然想到那只在app中,如果请求数据时用的是app提供的接口,如果该接口没有同异步的话,怎么办. 所以就捣腾了下. 模拟ajax同异步. 1 var VshopDataApi = { 2 queryArr : [], // { code : [{code, data, callback, is_sync}]}//系统队列 3 is_sync : false, //是否同步 默认false 不同步 系统变量 4 ajax : function(item, callback){ //自定义 5