在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学习笔记 —— 异步编程解决方案的更多相关文章

  1. NodeJS学习之异步编程

    NodeJS -- 异步编程 NodeJS最大的卖点--事件机制和异步IO,对开发者并不透明 代码设计模式 异步编程有很多特有的代码设计模式,为了实现同样的功能,使用同步方式和异步方式编写代码会有很大 ...

  2. 《C#并发编程经典实例》学习笔记—异步编程关键字 Async和Await

    C# 5.0 推出async和await,最早是.NET Framework 4.5引入,可以在Visual Studio 2012使用.在此之前的异步编程实现难度较高,async使异步编程的实现变得 ...

  3. 09-Node.js学习笔记-异步编程

    同步API,异步API 同步API:只有当前API执行完成后,才能继续执行下一个API console.log('before'); console.log('after'); 异步API:当前API ...

  4. [译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并修改其中的Value 学习笔记——异步 程序员常说的「哈希表」是个什么鬼?

    [译]聊聊C#中的泛型的使用(新手勿入)   写在前面 今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发 ...

  5. Nodejs学习笔记(四)——支持Mongodb

    前言:回顾前面零零碎碎写的三篇挂着Nodejs学习笔记的文章,着实有点名不副实,当然,这篇可能还是要继续走着离主线越走越远的路子,从简短的介绍什么是Nodejs,到如何寻找一个可以调试的Nodejs ...

  6. Nodejs学习笔记(三)——一张图看懂Nodejs建站

    前言:一条线,竖着放,如果做不到精进至深,那就旋转90°,至少也图个幅度宽广. 通俗解释上面的胡言乱语:还没学会爬,就学起走了?! 继上篇<Nodejs学习笔记(二)——Eclipse中运行调试 ...

  7. Nodejs学习笔记(二)——Eclipse中运行调试Nodejs

    前篇<Nodejs学习笔记(一)——初识Nodejs>主要介绍了在搭建node环境过程中遇到的小问题以及搭建Eclipse开发Node环境的前提步骤.本篇主要介绍如何在Eclipse中运行 ...

  8. NodeJS学习笔记之Connect中间件模块(一)

    NodeJS学习笔记之Connect中间件模块(一) http://www.jb51.net/article/60430.htm NodeJS学习笔记之Connect中间件模块(二) http://w ...

  9. Nodejs学习笔记(六)--- Node.js + Express 构建网站预备知识

    目录 前言 新建express项目并自定义路由规则 如何提取页面中的公共部分? 如何提交表单并接收参数? GET 方式 POST 方式 如何字符串加密? 如何使用session? 如何使用cookie ...

随机推荐

  1. JDK8.0新特性

    连接转载地址:http://www.2cto.com/kf/201609/544044.html Eclipse: http://aiyiupload.oss-cn-beijing.aliyuncs. ...

  2. lesson - 2 yum /单用户/救援模式/Linux 启动

    课程大纲:1.  yum使用yum 是一个在线安装软件包的工具,它可以帮我们解决软件包的依赖,这个日后会详细介绍.我们介绍了以下几个用法:yum list    这个命令可以列出所有安装过和未安装的软 ...

  3. Python Pandas 库的使用例子

    主要在jupyter notebook里面熟悉这个库的使用,它的安装方法与实现,可自行搜索. Pandas是一个优秀的数据分析工具,官网:http://pandas.pydata.org/ 相关的库使 ...

  4. haslayout知多少

    我们都知道浏览器有bug,而IE的bug似乎比大多数浏览器都多.IE的表现与其他浏览器不同的原因之一就是,显示引擎使用一个称为布局(layout)的内部概念.   因为布局是专门针对显示引擎内部工作方 ...

  5. Spring之Bean的基本概念

     转自http://blog.csdn.net/chenssy/article/details/8222744 Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于 ...

  6. 【转2】Appium 1.6.3 在Xcode 8 (真机)测试环境搭建 经验总结

    Appium 1.6.3 在Xcode 8 (真机)测试环境搭建经验总结 关于 Appium 1.6.3 在Xcode 8, 1真机上环境搭建问题更多,写此文章,供大家参考,让大家少走弯路. 在开始i ...

  7. Appium python

    1.运行报错:FAILED_ALREADY_EXISTS: Attempt to re-install io.appium.android.ime without first uninstalling ...

  8. IIS发布 用户 \'IIS APPPOOL\\X\' 登录失败

    曾多次遇到这个问题,我的解决方案就是修改数据库连接字符串 网上给的解决方案就是更换应用程序池的"进程模型":参考:http://jingyan.baidu.com/album/20 ...

  9. poi 读取word文档

    1.导入jar包 官网下载地址: https://www.apache.org/dyn/closer.lua/poi/release/bin/poi-bin-3.17-20170915.zip 最开始 ...

  10. python中的与 和 或

    上周五写程序碰到需要处理多重判断的一个逻辑,一般正确的写法是: if a or b or (c and d) or e: pass 因为变量很长,看上去比较杂乱,自己灵机一动写成了如下的样子: if ...