设想有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. hdu 1312

    原题链接 题意:“@”为起点,“.”为路,求可以走的格子有多少个(包括起点) 水题 bfs搜一发 思路:只有可以走的节点才能进入队列,所以每次出队列时ans+1就可以了(没有退出条件,所有可进入的节点 ...

  2. [LintCode] Create Maximum Number 创建最大数

    Given two arrays of length m and n with digits 0-9 representing two numbers. Create the maximum numb ...

  3. [zt]摄像机标定(Camera calibration)笔记

    http://www.cnblogs.com/mfryf/archive/2012/03/31/2426324.html 一 作用建立3D到2D的映射关系,一旦标定后,对于一个摄像机内部参数K(光心焦 ...

  4. php引用&符号详解——————给变量起小名

    学习了这篇博客[http://blog.csdn.net/jiedushi/article/details/6428585] php中引用采用的是“写时拷贝”的原理,就是除非发生写操作,指向同一个地址 ...

  5. MySQL启动错误排查

    一般情况下mysql的启动错误还是很容易排查的,但是今天我们就来说一下不一般的情况.拿到一台服务器,安装完mysql后进行启动,启动错误如下: 有同学会说,哥们儿你是不是buffer pool设置太大 ...

  6. Linux常用文件介绍

    您的一举一动都会记录在/var/log/secure 和/var/log/messages文件中

  7. java 正则表达式获取匹配和非获取匹配

    package test1; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TestExp ...

  8. ios计算内容的高度 (含7.0前及以后的版本的用法)

    + (CGFloat)heightForContent:(MyMsgTextModel *)content withWidth:(CGFloat)width { CGSize contentSize; ...

  9. 树莓派wiringPi库详解

    wiringPi是一个很棒的树莓派IO控制库,使用C语言开发,提供了丰富的接口:GPIO控制,中断,多线程,等等.java 的pi4j项目也是基于wiringPi的,我最近也在看源代码,到时候整理好了 ...

  10. 《Linux内核分析》第二周 操作系统是如何工作的?

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK TWO(2 ...