• 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

  • 相关阅读:
    先建制度,后建系统
    历史即哲学
    要针对对方关心的问题开展有效的交流
    技术人员安身立命
    人工智能与信息爆炸
    50年100年后我们的计算结构(架构)是什么样的?
    关于“怎么看”的哲学思考
    c语言之秒数算法
    httpd centos7
    SSH隧道技术----端口转发,socket代理
  • 原文地址:https://www.cnblogs.com/mininice/p/4054030.html
Copyright © 2020-2023  润新知