[Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数
设想有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条:绝不要同步地调用异步的回调函数的更多相关文章
- [Effective JavaScript 笔记]第22条:使用arguments创建可变参数的函数
第21条讲述使用可变参数的函数average.该函数可处理任意数量的参数并返回这些参数的平均值. 如何创建可变参数的函数 1.实现固定元数的函数 书上的版本 function averageOfArr ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [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 笔记]第66条:使用计数器来执行并行操作
第63条建议使用工具函数downloadAllAsync接收一个URL数组并下载所有文件,结果返回一个存储了文件内容的数组,每个URL对应一个字符串.downloadAllAsync并不只有清理嵌套回 ...
- [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑
构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...
- [Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列
第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列.使用下面代码,可以很容易使一个应用程序陷入泥潭. while(true){} 而且它并不需要一个无限循环来写一个缓慢的程序.代码 ...
- [Effective JavaScript 笔记]第64条:对异步循环使用递归
假设需要有这样一个函数,接收一个URL的数组并尝试依次下载每个文件直到有一个文件被成功下载.如果API是同步的,使用循环很简单实现. function downloadOneSync(urls){ f ...
随机推荐
- Working with HTTP
A WebClient façade class for simple download/upload operations via HTTP or FTP WebRequest and WebRes ...
- css问题 ie7兼容性问题
1.响应式网站用百分比时ie7不兼容:试试将margin:0 10% 改为margin-left:15%; 左边是可以的,但是右边就不行了 2.ie7的span 里的字会出现白色背景 ,是因为没有定高 ...
- 理解Oracle TM和TX锁
在Oracle中有很多锁,通过v$lock_type视图可以查看Oracle中所有类型的锁,在本篇文章中我们熟悉一下TM和TX锁的类型 SQL> select * from v$lock_typ ...
- Codeforces Round #196 (Div. 2)
A 题意:O(-1) 思路:排个序搞定. B 题意:O(-1) 思路:坑了我好久,这个框框水平垂直比例固定,分两种情况即可,不能旋转,我想多了,分了四种情况. C 题意:一列n个位置,让你填m个数,当 ...
- jquery 取消绑定事件
$(".TreeView1_0").unbind("mouseover"); $("#id").attr("onclick&quo ...
- 【转】手把手教你把Vim改装成一个IDE编程环境(图文)
手把手教你把Vim改装成一个IDE编程环境(图文) By: 吴垠 Date: 2007-09-07 Version: 0.5 Email: lazy.fox.wu#gmail.com Homepage ...
- php吧字符串直接转换成数组处理
$str ='123456'; echo strlen($str) - 1; echo '<br>'; echo $str{strlen($str) - 1}; echo '<br& ...
- JavaScript 入门教程二 在HTML中使用 JavaScript
一.使用 <script> 元素的方式有两种:直接在页面中嵌入 JavaScript 代码和引用外部 JavaScript 文件. 二.使用内嵌方式,一般写法为: <script t ...
- canvas 线条不清楚的问题
对于canvas 画出的1px线条不清楚的问题, 一般是坐标点+0.5像素的问题, 但是有时要考虑viewpoint的问题,让canvas的width = 980,同时viewpoint = 980 ...
- 1920.154s 0.309s 30817
MyISAM HASH restart-buffer ; ; ; SELECT * FROM grab_sales_rank_month; ; 受影响的行: 时间: .002s [SQL] ; 受影 ...