[Effective JavaScript 笔记]第62条:在异步序列中使用嵌套或命名的回调函数
异步程序的操作顺序
61条讲述了异步API如何执行潜在的代价高昂的I/O操作,而不阻塞应用程序继续处理其他输入。理解异步程序的操作顺序刚开始有点混乱。例如,下面的代码会在打印"finished"之前打印“starting”,即使这两个动作的程序源文件中以相反的顺序呈现。
downloadAsync('file.txt',function(file){
console.log('finished');
});
console.log('starting');
downloadAsync调用会立即返回,不会等待文件完成下载。同时,js的运行到完成机制确保下一行代码会在处理其他事件处理程序之前被执行。也就是说"starting"一定会在"finished"之前被打印。
理解操作序列的最简单的方式是异步API是发起操作而不是执行操作。上面的代码发起了一个文件的下载然后立即打印了“starting”。当下载完成后,在事件循环的某个单独的轮次中,被注册的事件处理程序才会打印出“finished”。
如何串联异步操作
如果你需要在发起一个操作后做一些事情,如果只能在一行中放置好几个声明,那么如何串联已完成的异步操作呢?例如,如果我们需要在异步数据库中查找一个URL,然后下载这个URL的内容?不可能发起两个连续的请求。
db.lookupAsync('url',function(url){
});
downloadAsync(url,function(text){//error:url is bound
console.log('contents of ' + url +':'+text);
});
以上代码不可能工作,因为从数据库查询到的URL结果需要作为downloadAsync方法的参数。但是它并不在作用域内。我们所做的这一步只是发起数据库查找,查找的结果还不可用。
回调函数处理
最简单的处理方法使用嵌套。借助于闭包的魔力,将第二个动作嵌套在第一个动作的回调函数中。
db.lookupAsync('url',function(url){
downloadAsync(url,function(text){
console.log('contents of ' + url +':'+text);
});
})
这里有两个回调函数,但第二个被包含在第一个中,创建闭包能够访问外部回调函数的变量。
嵌套的异步操作很容易,但当扩展到更长的序列时会很快变得麻烦。
db.lookupAsync('url',function(url){
downloadAsync(url,function(file){
downloadAsync('a.txt',function(a){
downloadAsync('b.txt',function(b){
downloadAsync('c.txt',function(c){
//....
});
});
});
});
});
回调命名的函数
减少过多的嵌套的方法之一是将嵌套的回调函数作为命名的函数,并将它们需要附加数据作为额外的参数传递。以上代码可以改写为:
db.lookupAsync('url',downloadURL);
function downloadURL(url){
downloadAsync(url,function(text){
showContents(url,text);
});
}
function showContents(url,text){
console.log('contents of ' + url +':'+text);
}
使用bind方法消除嵌套
为了合并外部的url变量和内部的text变量作为showContents方法的参数,在downloadURL方法中仍然使用了嵌套的回调函数。这里可以使用bind方法消除最深层的嵌套回调函数。
db.lookupAsync('url',downloadURL);
function downloadURL(url){
downloadAsync(url,showContents.bind(null,url));
}
function showContents(url,text){
console.log('contents of ' + url +':'+text);
}
这种做法可以使代码看起来很有顺序性,但需要为操作序列的每个中间步骤命名,并且一步步地使用绑定。这可能导致尴尬的情况,如多层嵌套时。
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){
//....
}
结合两种方法
结合这两种方法,会使代码更易理解。
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){
//...
});
});
});
}
最后一步可以使用一个额外的抽象来简化,可以下载多个文件并将它们存储在数组中。
function downloadFiles(url,file){
downloadAllAsync(['a.txt','b.txt','c.txt'],function(all){
var a=all[0],b=all[1],c=all[2];
});
}
使用downloadAllAsync函数允许我们同时下载多个文件。排序意味着每个操作只有等前一个操作完成后才能启动。一些操作本质上是连续的,比如下载我们从数据库查询到的URL。但如果我们有一个文件列表要下载,没理由等每个文件完成下载后才请求接下来的一个。
除了嵌套和命名回调,还可以建立更高层的抽象使异步控制流更简单、更简洁。
提示
使用嵌套或命名的回调函数按顺序地执行多个异步操作
尝试在过多的嵌套的回调函数和尴尬的命名的非嵌套回调函数之间取得平衡
避免将可被并行执行的操作顺序化
[Effective JavaScript 笔记]第62条:在异步序列中使用嵌套或命名的回调函数的更多相关文章
- [Effective JavaScript 笔记]第21条:使用apply方法通过不同数量的参数调用函数
apply()方法定义 函数的apply()方法和call方法作用相同,区别在于接收的参数的方式不同.apply()方法接收两个参数,一个是对象,一个是参数数组. apply()作用 1.用于延长函数 ...
- [Effective JavaScript 笔记]第34条:在原型中存储方法
js中完全有可能不借助原型进行编程.不用在其原型中定义任何的方法. 创建对象 构造函数法 所有属性和方法都在构造函数中定义 function User(name,pwd){ this.name=nam ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑
构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...
- [Effective JavaScript 笔记]第64条:对异步循环使用递归
假设需要有这样一个函数,接收一个URL的数组并尝试依次下载每个文件直到有一个文件被成功下载.如果API是同步的,使用循环很简单实现. function downloadOneSync(urls){ f ...
- [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数
设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...
随机推荐
- Linux下线程池的理解与简单实现
首先,线程池是什么?顾名思义,就是把一堆开辟好的线程放在一个池子里统一管理,就是一个线程池. 其次,为什么要用线程池,难道来一个请求给它申请一个线程,请求处理完了释放线程不行么?也行,但是如果创建线程 ...
- VS 2010配置Qt 4.8.5
由于早上偶然的一个念想,今天的宝贵时光就奉献给了Qt的安装及其和VS 2010的配置上了. 说实话,开始时真的查找了资料,包括博客.文库.知道等等,作者们写得都很全面,细致. 但自己动手尝试就是错误百 ...
- 在ubuntu server上安装沸腾时刻环境
1. 安装php5.6 http://phpave.com/upgrade-to-php-56-on-ubuntu-1404-lts/ 按照这篇文章的顺序来做,可以安装最新5.6版本php 安装好了以 ...
- 改善C#程序的建议3:在C#中选择正确的集合进行编码
要选择正确的集合,我们首先要了解一些数据结构的知识.所谓数据结构,就是相互之间存在一种或多种特定关系的数据元素的集合.结合下图,我们看一下对集合的分类. 集合分类 在上图中,可以看到,集合总体上分为线 ...
- ASP.NET MVC实现POST方式的Redirect
我们知道,在ASP.NET MVC中,要从一个Action跳转到另一个Action,通常是用一系列以“Redirect”开头的方法 Redirect RedirectToAction Redirect ...
- 在CentOS上安装SQLServer
Install SQL Server on Red Hat Enterprise Linux 参考上面这篇文章即可,需要注意的是内容大于3.25G,然后设置Sa密码的时候需要至少一个大写字母.一个小写 ...
- ActiveMQ 入门使用实例
1.下载ActiveMQ 去官方网站下载:http://activemq.apache.org/download-archives.html 2.运行ActiveMQ 解压缩apache-active ...
- Java 缓存技术
以下仅是对map对方式讨论.没有对点阵图阵讨论.作缓存要做以下2点: 1:清理及更新缓存时机的处理: . 虚拟机内存不足,清理缓存 .. 缓存时间超时,或访问次数超出, 启动线程更新 2:类和方法的 ...
- 【Matplotlib】绘图常见设置说明
说明:此贴会不定期进行更新! 设置1:图像的大小设置. 如果已经存在figure对象,可以通过以下代码设置尺寸大小: f.set_figheight(15) f.set_figwidth(15) 若果 ...
- BIEEE 创建多维钻取分析(4)
在上一节时,我们创建了一个基于部门号的工资分类汇总. 这里就引出了一个概念:维度 专业的解释大家自行百度,这里就不班门弄斧了.从数据的使用角度看,维度可以简单的理解成“数据分类汇总的一种依据”. 按“ ...