异步编程总结-6
发布于 5 年前 作者 halu886 3103 次浏览 来自 分享

该文章阅读需要5分钟,更多文章请点击本人博客halu886

流程控制库

除了事件订阅/发布模式和Promise/Deferred 写入了规范中的模式,还有一些没有写入规范但是灵活的规范。

尾触发与Next

除了事件和Promise,还有一种需要手工调用持续执行执行后续调用的方式,我们称为尾触发,关键字使用next,常用于Connect的中间件。

例如

var app = conntect();
// Middleware
app.use(connect.staticCache());
app.use(connect.static(_dirname+'/public'));
app.use(connect.cookieParser());
app.use(connect.session());
app.use(connect.query());
app.use(connect.bodyParser());
app.use(connect.csrf());
app.listen(3001);

通过user()注册好相关中间件,然后监听端口,中间件通过next尾触发的机制。

function(req,res,next){
    //中间件
}

每个中间件通过传递请求体和返回体还有尾触发函数进行在队列中传递,形成一个处理流。

1

中间件机制在处理网络请求时可以像切面编程过滤,验证,处理日志,与具体业务模块模块解耦。

具体实现如下

function createServer(){
    function app(req,res){app.handle(req,res);}
    utils.merge(app,proto);
    utils.merge(app,EventEmitter.prototype);
    app.route = '/';
    app.stack = [];
    for(var i = 0; i < arguments.length; ++i){
        app.use(arguments[i]);
    }
    return app;
}

通过如下代码创建请求处理函数

function app(req,res){app.handle(req,res);}

但是核心代码是app.stack=[],维护内部的中间件队列。通过user()将中间件放入队列中。

app.use = function(route,fn){
    this.stack.push({route:route,handle:fn});

    return this;
}

建好模型好,通过http原生模型实现事件监听。

app.listen = function(){
    var server = http.createServer(this);
    return server.listen.apply(server,arguments);
}

最终所有的一切回到app.handler(),每一个监控请求的网络请求都从这里开始。

app.handle = function(req,res,out){
    next();
}

取出当前队列中间件,递归执行持续调用,next简化的逻辑如下

function next(err){
    layer = stack[index++];

    layer.handler(req,res,next);
}

中间件这个模式当遇到异步的时候其实串行处理,不能通过异步提升效率,主要是用于流式处理请求的业务逻辑。将一些逻辑扁平化。

async

async是最著名的流程控制模块。

异步的串行执行

以下例子说明async如何解决恶魔金字塔的问题。

async 通过series 实现一组任务串行执行

async.series([
    function(callback){
        fs.readFile('file1.txt','utf-8',callback);
    },
    function(callback){
        fs.readFile('file2.txt','utf-8',callback);
    }
],function(err,results){
    //results =>[file1.txt,file2.txt]
})

等价于

fs.readFile('file1.txt','utf-8',function(err,content){
    if(err){
        return callback(err);
    }
    fs.readFile('file2.txt','utf-8',function(err,data){
        if(err){
            return callback(err);
        }
        callback(null,[content,data]);
    })
})

这段代码重点是异步的callback不由调用者指定,而是通过series通过高阶函数注入进去,最后在统一的回调中按异步的顺序作为数组传入。对于异常的处理,则是一旦发生异常,结束所有调用。

异步的并行执行

当需要通过并行提升性能时,async提供了 parallel()方法。

async.parallel([
    function(callback){
        fs.readFile('file1.txt','utf-8',callback);
    },
    function(callback){
        fs.readFile('file2.txt','utf-8',callback);
    }
],function(err,results){
    //results => [file1.txt,file2.txt]
})

等价于下面这行代码

var counter = 2;
var results = [];
var done = function(index,value){
    results[index] = value;
    counter--;
    if(counter === 0){
        callback(null,results);
    }
}

// 只传递第一个异常
var hasErr = false;
var fail = function(err){
    if(!hasErr){
        hasErr = true;
        callback(err);
    }
};

fs.readFile('file1.txt','utf-8',function(err,content){
    if(err){
        return fail(err);
    }
    done(0,content);
});
fs.readFile('file2.txt','utf-8',function(err,data){
    if(err){
        return fail(err);
    }
    done(1,data);
});

通过parallel方法实现的并发没有多层嵌套也没有状态的变化,也是通过回调的注入。当出现异常时,将异常作为第一个参数传入最终的回调中。所有异步调用结束时,所有的结果以数组的方式传入回调中。

EventProxy的原理类似

var EventProxy = require('eventproxy');

var proxy = new EventProxy();

proxy.all('content','data',function(content,data){
    callback(null,[content,data]);
})
proxy.fail(callback);

fs.readFile('file1.txt','utf-8',proxy.done('content'));
fs.readFile('file2.txt','utf-8',proxy.done('data'));

两个方式的思路类似,都是通过特殊的回调函数隐含返回值的处理。但是不同的是,前者是基于async框架封装后传递出来,而后者则是通过done()和fail()的生成新的回调函数。两种都是高阶函数的应用。

以上知识点均来自<<深入浅出Node.js>>,更多细节建议阅读书籍:-)

回到顶部