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‘:‘[email protected]‘});
    }]
}, 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

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。