在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. 浅析Node.js的Event Loop

    目录 浅析Node.js的Event Loop 引出问题 Node.js的基本架构 Libuv Event Loop Event Loop Phases Overview Poll Phase The ...

  2. js把通过图片路径生成base64

    主要思想: 使用canvas.toDataURL()方法将图片的绝对路径转换为base64编码. 一.图片在本地服务器: var imgSrc = "img/1.jpg";//本地 ...

  3. linux 下安装 sphinx 服务器

    准备工作:安装需要的扩展文件 yum install make gcc g++ gcc-c++ libtool autoconf automake imake libxml2-devel expat- ...

  4. [HNOI2008]Cards

    题目描述 小春现在很清闲,面对书桌上的N张牌,他决定给每张染色,目前小春只有3种颜色:红色,蓝色,绿色.他询问Sun有多少种染色方案,Sun很快就给出了答案. 进一步,小春要求染出Sr张红色,Sb张蓝 ...

  5. 3、UML建模技术

    UML(统一建模语言)是当前软件开发中使用最为广泛的建模技术之一,通过使用UML可以构造软件系统的需求模型(用例模型).静态模型.动态模型和架构模型 UML类图 1.UML类图图示 在UML中,类使用 ...

  6. C#Winform 自定义透明按钮和单窗体模块化实现

    技术看点 WinForm自定义控件的使用 WinForm单窗体应用如何模块化 需求及效果 又来一波 C# GDI自定义控件show .这个控件已经使用几年了,最近找出来重构一下.原来是没有边框的,那么 ...

  7. ArcGis连接oracle失败:ORA-6413:连接未打开

    问题: 通过ARCMap 添加Oracle数据库连接时提示,ORA-6413:连接未打开. 运行环境: ArcGis 10.2 Oracle 10g 解决方法: 通过上网查找解决方法,网友说" ...

  8. struts实现文件上传和下载。

    先来实现上传. 写上传不管语言,都要先注意前端的form那儿有个细节. <form name="form1" method="POST" enctype= ...

  9. 【读书笔记】【深入理解ES6】#8-迭代器(Iterator)和生成器(Generator)

    循环语句的问题 var colors = ["red", "green", "blue"]; for (var i = 0, len = c ...

  10. if与while相互嵌套,菱形*的实现.py

    """    *           * *         * * *       * * * *     * * * * *     * * * *       * ...