nodejs学习笔记 —— 异步编程解决方案
在js或者node编程中,由于异步的频繁和广度使用,使得回调和嵌套的深度导致编程的体验遇到一些挑战,如果写出优雅和好看的代码,本文主要针对异步编程的主流方案做一些总结
1、事件发布/订阅模式
事件监听器模式是一种广泛用于异步编程的模式, 是回调函数的事件化,又称发布/订阅模式, node自身提供events模块,是该模式的一个简单实现。
EventPorxy
2、promise/deferrd模式
在2009年被Kris Zyp抽象为一个提议草案,发布在CommonJS规范中, 目前,CommonJS草案中已经包括Promise/A、Promise/B、Promise/D这些异步模型。由于Promise/A较为常用也较为简单,只需要具备then()方法即可。所以先介绍一些改模式。
一般来说,then()的方法定义如下:
then(fulfilledHandler, errorHandler, progressHandler)
Promises/A
通过继承Node的events模块,我们可以实现一个简单Promise模块。
promise部分
var Promise = function() {
EventEmitter.call(this);
}
util.inherits(Promise, EventEmitter); // util是node自带的工具类 Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {
if(typeof fulfilledHandler === "function") {
this.once('success', fulfilledHandler);
}
if(typeof errorHandler === "function") {
this.once('error', errorHandler);
}
if(typeof progressHandler === "function") {
this.on('progress', progressHandler);
}
return this;
}
deferred部分
var Deferred = function() {
this.state = 'unfulfilled';
this.promise = new Promise();
} Deferred.prototype.resolve = function(obj) {
this.state = 'fulfilled';
this.promise.emit('success', obj);
} Deferred.prototype.reject = function(obj) {
this.state = 'failed';
this.promise.emit('error', obj);
} Deferred.prototype.progress = function(obj) {
this.promise.emit('progress', obj);
}
使用
function readFile(file, encoding) {
var deferred = new Deferred();
fs.readFile(file, encoding, deferred.resolve);
return deferred.promise;
} readFile('/test.txt', 'utf-8').then(function(data) {
...
}, function(err) {
...
});
以上是promise/deferred模式的简单实现,状态转换图可以描述如下:
promise模式比发布/订阅模式略为优雅, 但还不能满足很多场景的实际需求,比如一组纯异步的API为了协同完成一串事情。
Promises/A+
规范将之前 Promises/A 规范的建议明确为了行为标准。其扩展了原规范以覆盖一些约定俗成的行为,以及省略掉一些仅在特定情况下存在的或者有问题的部分
详见:中文版:http://www.ituring.com.cn/article/66566, 英文版:https://promisesaplus.com/
3、流程控制库
尾触发与next
尾触发目前应用最多的地方是Connect的中间件, 中间件处理网络请求时,可以向面向切面编程一样进行过滤、验证、日志等功能,最简单的中间件如下:
function(req, res, next) {
//中间件
}
每个中间件传递请求对象、响应对象和尾触发函数,通过队列形成一个处理流,如下:
看一个例子
app.use(function(req, res, next) {
setTimeout(function() {
next();
}, 0)
}, function(req, res, next) {
setTimeout(function() {
next();
}, 0);
});
从这个实例中可以简单猜到尾触发的实现原理了,简单讲就是通过调用use维护一个队列, 调用next的时候出队并执行,依次循环。
async
目前最知名的流程控制模块,async模块提供了20多个方法用于处理异步的多种写作模式, 如:
1、异步的串行执行
async.series([
function(callback){
// do some stuff ...
callback(null, 'one');
},
function(callback){
// do some more stuff ...
callback(null, 'two');
}
],
// optional callback
function(err, results){
// results is now equal to ['one', 'two']
}); // an example using an object instead of an array
async.series({
one: function(callback){
setTimeout(function(){
callback(null, 1);
}, 200);
},
two: function(callback){
setTimeout(function(){
callback(null, 2);
}, 100);
}
},
function(err, results) {
// results is now equal to: {one: 1, two: 2}
});
异常处理原则是一遇到异常,即结束所有调用,并将异常传递给最终回调函数的第一个参数
2、异步的并行执行
// an example using an object instead of an array
async.parallel({
one: function(callback){
setTimeout(function(){
callback(null, 1);
}, 200);
},
two: function(callback){
setTimeout(function(){
callback(null, 2);
}, 100);
}
},
function(err, results) {
// results is now equals to: {one: 1, two: 2}
});
与EventProxy基于事件发布和订阅模式的不同在于回调函数的使用上, async回调函数由async封装后传入, 而EventProxy则通过done(), fail()方法来生成新的回调函数, 实现方式都是高阶函数的应用。
3、异步调用的依赖处理
async.waterfall([
function(callback){
callback(null, 'one', 'two');
},
function(arg1, arg2, callback){
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback){
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});
4、自动依赖处理
async.auto({
get_data: function(callback){
console.log('in get_data');
// async code to get some data
callback(null, 'data', 'converted to array');
},
make_folder: function(callback){
console.log('in make_folder');
// async code to create a directory to store a file in
// this is run at the same time as getting the data
callback(null, 'folder');
},
write_file: ['get_data', 'make_folder', function(callback, results){
console.log('in write_file', JSON.stringify(results));
// once there is some data and the directory exists,
// write the data to a file in the directory
callback(null, 'filename');
}],
email_link: ['write_file', function(callback, results){
console.log('in email_link', JSON.stringify(results));
// once the file is written let's email a link to it...
// results.write_file contains the filename returned by write_file.
callback(null, {'file':results.write_file, 'email':'user@example.com'});
}]
}, function(err, results) {
console.log('err = ', err);
console.log('results = ', results);
});
在现实的业务环境中,具有很多复杂的依赖关系, 并且同步和异步也不确定,为此auto方法能根据依赖关系自动分析执行。
Step
轻量的async, 在API暴露上也具备一致性, 因为只有一个接口Step。
在异步处理上有一些不同, Step一旦产生异常,会将异做为下一个方法的第一个参数传入
var s = require('step');
s(
function readSelf() {
fs.readFile(__filename, this);
},
function(err, content) {
//并行执行任务
fs.readFile(__filename, this.parallel());
fs.readFile(__filename, this.parallel());
},
function() {
//任务分组保存结果
var group = this.group();
console.log(arguments);
fs.readFile(__filename, group());
fs.readFile(__filename, group());
},
function () {
console.log(arguments);
}
)
Wind
待补充
总结
对比几种方案的区别:事件发布/订阅模式相对是一种原始的方式,Promise/Deferred模式贡献了一个非常不错的异步任务模型的抽象,重头在于封装异步的调用部分, 而流程控制库则要灵活很多。
除了async、step、EventProxy、wind等方案外,还有一类通过源代码编译的方案来实现流程控制的简化, streamline是一个典型的例子。
参考
《深入浅出nodejs》第四章
https://promisesaplus.com/
https://github.com/caolan/async/
https://github.com/creationix/step
http://www.ituring.com.cn/article/66566
nodejs学习笔记 —— 异步编程解决方案的更多相关文章
- NodeJS学习之异步编程
NodeJS -- 异步编程 NodeJS最大的卖点--事件机制和异步IO,对开发者并不透明 代码设计模式 异步编程有很多特有的代码设计模式,为了实现同样的功能,使用同步方式和异步方式编写代码会有很大 ...
- 《C#并发编程经典实例》学习笔记—异步编程关键字 Async和Await
C# 5.0 推出async和await,最早是.NET Framework 4.5引入,可以在Visual Studio 2012使用.在此之前的异步编程实现难度较高,async使异步编程的实现变得 ...
- 09-Node.js学习笔记-异步编程
同步API,异步API 同步API:只有当前API执行完成后,才能继续执行下一个API console.log('before'); console.log('after'); 异步API:当前API ...
- [译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并修改其中的Value 学习笔记——异步 程序员常说的「哈希表」是个什么鬼?
[译]聊聊C#中的泛型的使用(新手勿入) 写在前面 今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发 ...
- Nodejs学习笔记(四)——支持Mongodb
前言:回顾前面零零碎碎写的三篇挂着Nodejs学习笔记的文章,着实有点名不副实,当然,这篇可能还是要继续走着离主线越走越远的路子,从简短的介绍什么是Nodejs,到如何寻找一个可以调试的Nodejs ...
- Nodejs学习笔记(三)——一张图看懂Nodejs建站
前言:一条线,竖着放,如果做不到精进至深,那就旋转90°,至少也图个幅度宽广. 通俗解释上面的胡言乱语:还没学会爬,就学起走了?! 继上篇<Nodejs学习笔记(二)——Eclipse中运行调试 ...
- Nodejs学习笔记(二)——Eclipse中运行调试Nodejs
前篇<Nodejs学习笔记(一)——初识Nodejs>主要介绍了在搭建node环境过程中遇到的小问题以及搭建Eclipse开发Node环境的前提步骤.本篇主要介绍如何在Eclipse中运行 ...
- NodeJS学习笔记之Connect中间件模块(一)
NodeJS学习笔记之Connect中间件模块(一) http://www.jb51.net/article/60430.htm NodeJS学习笔记之Connect中间件模块(二) http://w ...
- Nodejs学习笔记(六)--- Node.js + Express 构建网站预备知识
目录 前言 新建express项目并自定义路由规则 如何提取页面中的公共部分? 如何提交表单并接收参数? GET 方式 POST 方式 如何字符串加密? 如何使用session? 如何使用cookie ...
随机推荐
- PHP中引入文件的四种方式及区别
文件加载语句:include,require,include_once,require_once include,require: require函数通常放在 PHP 程序的最前面,PHP 程序在执行 ...
- PHP常用功能模块
错误异常模块 错误处理 1. 系统定义了一些二进制码,用来表示错误报告的级别: 在 /etc/php5/apache2/php.ini中修改php配置文件,其中display_errors默认 ...
- WPF的消息机制(二)- WPF内部的5个窗口之隐藏消息窗口
目录 WPF的消息机制(一)-让应用程序动起来 WPF的消息机制(二)-WPF内部的5个窗口 (1)隐藏消息窗口 (2)处理激活和关闭的消息的窗口和系统资源通知窗口 (3)用于用户交互的可见窗口 (4 ...
- HY.Mail:C#简单、易用的邮件工具库
一.开发HY.Mail的初衷 Nuget或者github上有很多成熟且优秀的邮件库可以使用, 但是目前找到的使用都不够简洁或者不适合我的使用场景 我的场景是开发应用场景(例如系统通知.运维通知),而非 ...
- Java 读取配置文件
1.读取XML文件使用dom4j-full.jar包的SAXReader解析: Document document=new SAXReader.reader("xml文路径/文件名xxx.x ...
- flex盒模型实现头部尾部固定
近期做移动app.wap等站,需要头部固定在顶部,不随着内容滚动而滚动平时第一想法就是使用position:fixed;top:0;z-index:10;这样去实现但这样使用fixed之后,会在ios ...
- iOS性能优化技术
小小总结,后续继续跟进. 1. 提高应用性能的几个开发细节 * 尽量避免使用constraint实现动画 * 尽量避免使用数组的删除操作 * 尽量避免使用 NSString::stringWithFo ...
- QCanvasItem介绍-QT3
QCanvasItem类提供一个在QCanvas上的抽象图形对象. 各种QCanvasItem子类提供立即可用的行为.这个类是一个纯粹的抽象超类,它提供了在所有具体的canvas项目类中共享的行为.Q ...
- solr7.2安装实例,中文分词器
一.安装实例 1.创建实例目录 [root@node004]# mkdir -p /usr/local/solr/home/jonychen 2.复制实例相关配置文件 [root@node004]# ...
- Java学习笔记9(面向对象二:this、继承、抽象类)
就近原则: 类中的方法中的变量和成员变量重名时,调用类的方法时候,生效的是方法中的变量,如果方法中没有定义变量,才会去成员变量中寻找 于是,提出了this关键字,为了区分重名问题 public cla ...