JS多异步之间的协作方案
场景:使用工具函数downloadAllAsync接收一个URL数组并下载所有文件,结果返回一个存储了文件内容的数组,每个URL对应一个字符串。
好处:downloadAllAsync并不只有清理嵌套回调函数的好处,其主要好处是并行下载文件。我们可以在同一个事件循环中一次启动所有文件的下载,而不用等待每个文件完成下载。
并行逻辑是微妙的,很容易出错。下面的实现有一个隐蔽的缺陷。
function downloadAllAsync(urls, onsuccess, onerror) {
var result = [],
len = urls.length;
if(len === 0) { // 如果请求路径为空, 不执行下面的程序
// 绝不要同步地调用异步的回调函数
setTimeout(onsuccess.bind(null, result), 0);
return;
}
urls.forEach(function(url) {
downloadAsync(url, function(r) {
if(result) {
result.push(r); // race condition
// 根据提供的url, 所有文件数据被成功下载后,执行onsuccess程序
result.length === len && onsuccess(result);
}
}, function(e) {
if (result) {
result = null; // 在错误的情况下, 确保onerror只执行一次
onerror(e);
}
})
})
}
如果有多个下载失败,我们设置了result数组为null,从而保证onerror只被调用一次。即在第一次错误发生时。
downloadAllAsync函数实现的是一旦下载完成就立即将中间结果保存在result数组的末尾。因此,陷阱是保存下载文件内容的数组的顺序是未知的。几乎不能正确使用这样的API,因为调用者无法找出哪个结果对应哪个文件。
疑问:为什么使用setTimeout函数来调用onsuccess回调函数,而不是直接调用它
我们将请求的中间结果存储在原始的索引位置来达到预期结果
function downloadAllAsync(urls, onsuccess, onerror) {
var result = [],
len = urls.length;
if(len === 0) {
setTimeout(onsuccess.bind(null, result), 0);
return;
}
urls.forEach(function(url, index) {
downloadAsync(url, function(r) {
if(result) {
result[index] = r; // store at fixed index
result.length === len && onsuccess(result); // race condition
}
}, function(e) {
if (result) {
result = null;
onerror(e);
}
})
})
}
该实现利用了foreach回到函数的第二个参数。该参数为当前迭代提供的数组索引。不幸的是,这仍然不正确。
数组更新契约,即设置一个索引属性,总是确保数组的length属性大于索引。
正确的实现应用了一个计数器来追踪正在进行的操作数量。
function downloadAllAsync(urls, onsuccess, onerror) {
var result = [],
pending = urls.length;
if(pending === 0) {
setTimeout(onsuccess.bind(null, result), 0);
return;
}
urls.forEach(function(url, index) {
downloadAsync(url, function(r) {
if(result) {
result[index] = r; // store at fixed index
// pending -= 1; // register the success
// pending === 0 && onsuccess(result); // race condition
--padding || onsuccess(result);
}
}, function(e) {
if (result) {
result = null;
onerror(e);
}
})
})
}
现在整个世界都太平了,不论事情以什么样的顺序发生,pending计数器都能准确地指出何时所有的事件会被完成,并以预期的顺序返回完整的结果。
参考:编写高质量JS代码68个有效方法
更新: 2014/05/14
一般而言,事件与侦听器关系是一对多,但在异步编程中,也会出现事件与侦听器的关系是多对一的情况,话句话说,一个业务逻辑可能依赖多个回调或事件传递的结果。例如,在网页渲染的过程中,通常需要数据、模板、资源文件,这三者互相之间并不依赖,但最终渲染结果中三者缺一不可。如果采用默认的异步方法调用,程序也许将会如下所示:
fs.readFile(template_path, 'utf8', function(err, template) {
db.query(sql, function(err, data) {
l10n.get(function(err, resourse) {
// TODO
});
});
});
这在结果的保证上是没有问题的,问题在于这并没有利用好异步I/O带来的并发优势。这是异步编程的典型问题,为了实现最终结果的处理而导致可以并行调用但实际只能串行执行。
var count = 0;
var result = {}; // 存放查询结果
var done = function(key, value) {
result[key] = value;
count++;
count === 3 && render(result);
};
fs.readFile(template_path, 'utf8', function(err, template) {
done('template', template);
});
db.query(sql, function(err, data) {
done('data', data);
});
l10n.get(function(err, resourse) {
done('resourse', resourse);
});
JS多异步之间的协作方案的更多相关文章
- Node.js 和 Python之间如何进行选择?
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文出处:https://dzone.com/articles/nodejs-vs-python-which ...
- js构建ui的统一异常处理方案(一)
从早期从事基于java的服务器端开发,再到之后从事基于web和js的ui开发,总体感觉基于web页面的ui开发远不如服务器端健壮.主要是早期ie浏览器功能太弱小,很多业务被迫放到服务器端去实现,浏览器 ...
- js的异步和单线程
最近,同事之间做技术分享的时候提到了一个问题"js的异步是另开一个线程吗?"当时为此争论不休.会后自己查阅了一些资料,对这个问题进行一个自我的分析与总结,有不同意见的希望可以赐教, ...
- 探秘JS的异步单线程
对于通常的developer(特别是那些具备并行计算/多线程背景知识的developer)来讲,js的异步处理着实称得上诡异.而这个诡异从结果上讲,是由js的“单线程”这个特性所导致的. 我曾尝试用“ ...
- JS的异步模式
JS的异步模式:1.回调函数:2.事件监听:3.观察者模式:4.promise对象 JavaScript语言将任务的执行模式可以分成两种:同步(Synchronous)和异步(Asychronous) ...
- Node.js之异步编程
> 文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 
上一篇文章,我分析了同步代码做异常处理是基于责任链模式,而通过try.catch等语句可以很容易地实现这种责任链模式.但是如果是异步调用,我们无法直接通过try.catch语句实现责任链模式,并且通过 ...
- Quick-Cocos2d-x v3.3 异步加载Spine方案 转
Quick-Cocos2d-x v3.3 异步加载Spine方案 浩月难求也与2015-03-25 15:06:3441 次阅读 背景 项目中使用了Quick-Cocos2d-x 3.3,由于Spin ...
- 【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制
转载自http://xidui.github.io/2015/10/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3python3-4-Asyncio%E5%BA%93% ...
随机推荐
- 17、文案人员 - IT软件人员书籍系列文章
这里说的文案人员是软件项目中的一个角色.其主要负责相关文档的整理,用户使用说明书的编写等等,在项目中是一个辅助的角色. 文案人员所做的事情不错,但是她能够辅助软件配置管理工程师进行工作,更好的为维护文 ...
- yii2解决百度编辑器umeditor图片上传问题
作者:白狼 出处:http://www.manks.top/article/yii2_umeditor_upload本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原 ...
- Java数组的12个常用方法
以下是12个关于Java数组最常用的方法,它们是stackoverflow得票最高的问题. 声明一个数组 String[] aArray = new String[5]; String[] bArra ...
- CentOS 6.2编译安装Nginx1.2.0+MySQL5.5.25+PHP5.3.13
CentOS 6.2编译安装Nginx1.2.0+MySQL5.5.25+PHP5.3.132013-10-24 15:31:12标签:服务器 防火墙 file 配置文件 written 一.配置好I ...
- PHP 取前一天或后一天、一个月时间
//获得当前时间 //date()格式化时间返回String类型. date("Y-m-d H:i:s") $current_date = date(’Y-m-d’ ...
- Centos修改DNS重启或者重启network服务后丢失问题处理
本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephisto)写的,SourceLink 阅读目录 介绍 起因 处理 GitHub 本文版 ...
- spring项目的 context root 修改之后,导致 WebApplicationContext 初始化两次的解决方法
修改了 spring web 项目的 context root 为 / 之后,在启动项目时,会导致 WebApplicationContext 初始化两次,下面是其初始化日志: 第一次初始化: 四月 ...
- mysqldump 逻辑备份的正确姿势
在上一篇文章 MySQL 命令行工具之 mysqldump 深入研究 中,我们搞定了mysqldump的参数和基本原理.那么我们该怎么样最好的使用它的?它有哪些坑呢? 1. 利用mysqldump进行 ...
- 关于InnoDB的Next-Key lock
最近一段时间在准备新员工培训的材料,本来打算介绍介绍概念就OK的,但是既然写了事务的章节,就特别想介绍一下锁,介绍了锁,就忍不住想介绍一下Next-Key Lock. 大家知道,标准的事务隔离级别有R ...
- Qt使用自带的windeployqt 生成exe来发布软件
集成开发环境 QtCreator 目前生成图形界面程序 exe 大致可以分为两类:Qt Widgets Application 和 Qt Quick Application.下面分别介绍这两类exe ...