[Effective JavaScript 笔记]第64条:对异步循环使用递归
假设需要有这样一个函数,接收一个URL的数组并尝试依次下载每个文件直到有一个文件被成功下载。如果API是同步的,使用循环很简单实现。
function downloadOneSync(urls){
for(var i=0,n=urls.length;i< n;i++){
try{
return downloadSync(urls[i]);
}catch(e){}
}
throw new Error('all downloads failed.');
}
在异步情况下,上面的这种方式就无法正确工作。因为不能在回调函数中暂停循环并恢复。如果尝试使用循环,它将启动所有的下载,这不是等待完成一个再进行下一个。
function downloadOneAsync(urls,onsucess,onerror){
for(var i=0,n=urls.length;i < n;i++){
downloadAsync(urls[i],onsucess,function(error){
//?
});
//loop continues
}
throw new Error('all downloads failed');
}
这里我们要实现一个类似循环的东西,我们需要显式地说继续执行,它才会继续执行。解决方案是将循环实现为一个函数,可以决定何时开始每次迭代。
function downloadOneAsync(urls,onsucess,onfailure){
var n=urls.length;
function tryNextURL(i){
if(i>=n){
onfailure('all downloads failed');
return;
}
downloadAsync(urls[i],onsuccess,function(){
tryNextURL(i+1);
});
}
tryNextURL(0);
}
局部函数tryNextURL是一个递归函数。它的实现调用了其自身。典型的javascript环境中一个递归函数同步调用自身过多次会导致失败。例如,下例中的递归函数试图调用自身10万次,在大多数的js环境中会产生一个运行时错误。
function countdown(n){
if(n===0){
return 'done';
} else {
return countdown(n-1);
}
}
当n太大时countdown函数会执行失败,那么如何确保downloadOneAsync函数是安全的呢?查看一下countdown函数提供的错误信息。
VM58:1 Uncaught RangeError: Maximum call stack size exceeded(…)
js环境通常在内存中保存一块固定的区域,称为调用栈,用于记录函数调用返回前下一步该做什么。执行下面的小程序。
function negative(x){
return abs(x)*-1;
}
function abs(x){
return Math.abs(x);
}
console.log(negative(42));
当程序使用参数42调用Math.abs方法时,有几个其他的函数调用也在进行,每个都在等待另一个的调用返回。在每个函数调用时,项目符号(.)描述了在程序中已经发生的函数调用地方及这次调用完成后将返回哪里。就像传统的栈数据结构,这个信息遵循“先进后出”协议。最新的函数调用将信息推入栈(被表示为栈的最底层的帧),该信息也将首先从栈中弹出。当Math.abs执行完毕,将会返回给abs函数,其将返回给negative函数,然后将返回到最外面的脚本。
当一个程序执行中有太多的函数调用,它会耗尽栈空间,最终抛出异常。这种情况被称为栈溢出。在此例中,调用countdown(10万次)需要countdown调用自身10万次,每次推入一个栈桢。存储这么多栈帧需要的空间量会耗尽大多数js环境分配空间,导致运行时错误。
现在再看看downloadOneAsync函数。不像countdown直到递归调用返回后才会返回,downloadOneAsync只在异步回调函数中调用自身。记住异步API在其回调函数被调用前会立即返回。所以downloadOneAsync返回,导致其栈帧在任何递归调用将新的栈帧推入栈前,会从调用栈中弹出。(事实上,回调函数总在事件循环的单独轮次中被调用,事件循环的每个轮次中调用其他事件处理程序的调用栈最初是空的。)所以无论downloadOneAsync需要多少次迭代,都不会耗尽栈空间。
提示
循环不能是异步的
使用递归函数在事件循环的单独轮次中执行迭代
在事件循环的单独轮次中执行递归,并不会导致调用栈溢出
[Effective JavaScript 笔记]第64条:对异步循环使用递归的更多相关文章
- [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 笔记]第67条:绝不要同步地调用异步的回调函数
设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...
- [Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列
第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列.使用下面代码,可以很容易使一个应用程序陷入泥潭. while(true){} 而且它并不需要一个无限循环来写一个缓慢的程序.代码 ...
- [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑
构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...
- [Effective JavaScript 笔记]第62条:在异步序列中使用嵌套或命名的回调函数
异步程序的操作顺序 61条讲述了异步API如何执行潜在的代价高昂的I/O操作,而不阻塞应用程序继续处理其他输入.理解异步程序的操作顺序刚开始有点混乱.例如,下面的代码会在打印"finishe ...
- [Effective JavaScript 笔记]第66条:使用计数器来执行并行操作
第63条建议使用工具函数downloadAllAsync接收一个URL数组并下载所有文件,结果返回一个存储了文件内容的数组,每个URL对应一个字符串.downloadAllAsync并不只有清理嵌套回 ...
随机推荐
- petapoco定制,比较SQL事务,存储过程,分布式事务(MSDTC)的区别和场景
使用分布式事务时 就锁死了,而且是只锁编辑的行 使用.netSQL事务一定要执行了一个CUD的SQL才会锁死,而且也是锁行,但是也锁读的行 .netSQL事务要在这里才锁死 结论,对于产品要求细粒度的 ...
- 1022. D进制的A+B (20)
1022. D进制的A+B (20) 时间限制 100 ms 内存限制 32000 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 输入两个非负10进制整数A和 ...
- 已知树的前序、中序,求后序的java实现&已知树的后序、中序,求前序的java实现
public class Order { int findPosInInOrder(String str,String in,int position){ char c = str.charAt(po ...
- 重命名PDF打印文件名
Odoo系统默认打印出来的PDF文件都是以当前文档模型对象对应的模板文件名命名的,对用户来说,这样的命名很不友好. 我们希望能够将打印出来的文件名以单号命名,下面是实现这种目的的方法. 在report ...
- Git分布式项目管理
Git简介 Git是什么? Git和SVN一样都是一种高效的管理代码的系统. Git是目前世界上最先进的分布式版本控制系统(没有之一). 创建版本库 什么是版本库呢?版本库又名仓库,英文名 ...
- jQuery ui autocomplete下拉列表样式失效解决,三种获取数据源方式,
jQuery有很多很多的已经实现,很漂亮的插件,autocomplete就是其中之一.jQuery ui autocomplete主要支持字符串Array.JSON两种数据格式,jQuery ui b ...
- 织梦DEDECMS网站后台安全检测提示 加一个开关
1.进入后台后,点击 系统->系统基本参数->添加变量: 变量名称:cfg_safecheck_switch 变量值:N 变量类型:布尔(Y/N) 参数说明:启用安全监测系统: 2.找到系 ...
- 重新启动 Apache 以加载上面安装的模块
尽管Ubuntu 是一种新兴的Linux分支,但Ubuntu 组织却为Apache提供了丰富的支持软件,这些软件都可以从发行版的光盘获取,也可以从官方站点轻松下载.所以,Ubuntu非常适合作为Web ...
- 用户、组或角色 '' 在当前数据库中已存在。 (Microsoft SQL Server,错误: 15023)
SQLServer2008用户组或角色'*****'在当前数据库中已存在问题的解决办法 在迁移数据库的过程中SQLServer SDE的问题 为一个数据库添加一个用户时,提示以下信息:用户.组或角色 ...
- php实现实现代码多主从,切换,轮询,健康检查
现在很多框架现在都提供数据库读写分离,比如CI,TP,YII,一般使用正则表达书判断sql语句是读操作,还是写操作,但是有个缺点,没有给用主动判断,比如写入操作必须去立即读取主数据库的,如果不能立即判 ...