设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件。在文件已经被缓存的情况下,立即调用回调函数是最优选择。

var cache=new Dict();
function downloadCachingAsync(url,onsuccess,onerror){
if(cache.has(url)){
onsuccess(cache.get(url));
return;
}
return downloadAsync(url,function(file){
cache.set(url,file);
onsuccess(file);
},onerror);
}

通常情况下,它会立即提供数据,但这种方式是违反了异步API客户端的期望。首先,它改变了操作的预期顺序。第62条显示了下面的例子,对于循规蹈矩的异步API应该总是以一种可预测的顺序来记录日志消息。

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

使用上面的downloadCachingAsync实现,这样的客户端代码可能最终会以任意的顺序记录事件,这取决于文件是否已被缓存起来。

downloadCachingAsync('file.txt',function(file){
console.log('finished');
});
console.log('starting');

日志消息的顺序是一回事。更一般的是,异步API的目的是维持事件循环中每轮的严格分离。正如第61条解释的,这简化了并发,通过减轻每轮事件循环的代码量而不必担心其他代码并发地修改共享的数据结构。同步地调用异步的回调函数违反了这一分离,导致在当前轮完成之前,代码用于执行一轮隔离的事件循环。
例如,应用程序可能会持有一个剩余的文件队列给用户下载和显示消息。

downloadCachingAsync(remaining[0],function(file){
remaining.shift();
});
status.display('Downloading '+remaining[0]+'...');

如果同步地调用该回调函数,那么将显示错误的文件名的消息(或者更糟糕的是,如果队列为空会显示“undefined”)。
同步的调用异步的回调函数甚至可能会导致一些微妙的问题。第64条解释了异步的回调函数本质上是以空的调用栈来调用,因此将异步的循环实现为递归函数是安全的,完全没有累积超越调用栈空间的危险。同步的调用不能保障这一点,因而使得一个表面上的异步循环很可能会耗尽调用栈空间。另一种问题是异常。对于上面的downloadCachingAsync实现,如果回调函数抛出一个异常,它将会在每轮的事件循环中,也就是开始下载时而不是期望的一个分离的回合中抛出该异常。
为了确保总是异步地调用回调函数,我们可以使用已存在的异步API。就像我们在第65条和第66条中所做的一样,我们使用通用的库函数setTimeout在每隔一个最小的超时时间后给事件队列增加一个回调函数。可能有比setTimeout函数更完美的替代方案来调度即时事件,这取决于特定平台。

var cache=new Dict();
function downloadCachingAsync(url,onsuccess,onerror){
if(cache.has(url)){
var cache=cache.get(url);
setTimeout(onsuccess.bind(null,cached),0);
return;
}
return downloadAsync(url,function(file){
cache.set(url,file);
onsuccess(file);
},onerror);
}

这里使用bind函数将结果保存为onsuccess回调函数的第一个参数。

提示

  • 即使可以立即得到数据,也绝不要同步地调用异步回调函数

  • 同步地调用异步的回调函数扰乱了预期的操作序列,并可能导致意想不到的交错代码

  • 同步地调用异步的回调函数可能导致栈溢出或错误地处理异常

  • 使用异步的API,比如setTimeout函数来调度异步回调函数,使其运行于另一回合

[Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数的更多相关文章

  1. [Effective JavaScript 笔记]第22条:使用arguments创建可变参数的函数

    第21条讲述使用可变参数的函数average.该函数可处理任意数量的参数并返回这些参数的平均值. 如何创建可变参数的函数 1.实现固定元数的函数 书上的版本 function averageOfArr ...

  2. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  3. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  4. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  5. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  6. [Effective JavaScript 笔记]第66条:使用计数器来执行并行操作

    第63条建议使用工具函数downloadAllAsync接收一个URL数组并下载所有文件,结果返回一个存储了文件内容的数组,每个URL对应一个字符串.downloadAllAsync并不只有清理嵌套回 ...

  7. [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑

    构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...

  8. [Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列

    第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列.使用下面代码,可以很容易使一个应用程序陷入泥潭. while(true){} 而且它并不需要一个无限循环来写一个缓慢的程序.代码 ...

  9. [Effective JavaScript 笔记]第64条:对异步循环使用递归

    假设需要有这样一个函数,接收一个URL的数组并尝试依次下载每个文件直到有一个文件被成功下载.如果API是同步的,使用循环很简单实现. function downloadOneSync(urls){ f ...

随机推荐

  1. Qt 控件随窗口缩放

    在Qt的界面设计中,我们有时候希望窗口在最大化的时候,上面的控件也跟着缩放,那么我们就需要调整控件的SizePolicy属性,关于这个属性的讲解请参见我之前的博客Qt SizePolicy 属性,由于 ...

  2. 关于VS打包程序无法弹出主界面的问题

    代码中的程序很正常,VS打包之后,无法弹出主界面的问题. 这种问题,一般是缺少程序加载所必须的东西,包括dll,配置文件等. (1)程序主界面使用的是DevExpress,DevExpress的相关类 ...

  3. c#面向对象基础 静态成员、构造函数、命名空间与类库

    静态成员 属性.方法和字段等成员是对象实例所特有的,即改变一个对象实例的这些成员不影响其他的实例中的这些成员.除此之外,还有一种静态成员(也称为共享成员),例如静态方法.静态属性或静态字段.静态成员可 ...

  4. SQL server 语言基础

    数据库: 1. 结构化查询语言(Structured Query Language)简称SQL: 数据库管理系统(Database Management System)简称DBMS: 数据库管理员(D ...

  5. 了解C++默认编写并调用哪些函数

    概念:编译器可以暗自为class创建default构造函数.copy构造函数.copy assignmengt构造函数,以及析构函数. 比如你写下:struct Node { }; 这就好像你写下这样 ...

  6. iOS 导入第三方文件夹时右侧出现问号

    首先,和版本库有关. a代表add,m代表modify,?代表未能识别,通常如果使用git之类的版本控制器,添加文件后没有进行提交,就会出现? 1.遇到引用文件夹为蓝色的情况,是你以为勾了copy项, ...

  7. XML真正强大的功能是来自其元素与封装的内容

    创建文档类型声明 一般而言,XML声明放在文档顶部.在PHP中声明十分简单:只需实例化一个DOM文档类的对象并赋予它一个版本号.查看程序清单A: 程序清单 A <?php// create do ...

  8. CNUOJ 2104 Day6-例3

    http://privateoj.cnuschool.org.cn/oj/home/problem.htm?problemID=2104 题的网址 没什么好解释的,用DFS= = 最开始记着把f数组下 ...

  9. wordpress 导航相关的函数

    上一篇文章.下一篇文章 previous_post_link( $format = '« %link', $link = '%title', $in_same_term = false, $exclu ...

  10. Java 实现MapReduce函数

    明白了MapReduce程序的工作原理之后,下一步就是写代码来实现它.我们需要三样东西:一个map函数.一个reduce函数和一些用来运行作业的代码.map函数由Mapper类来表示,后者声明一个ma ...