Nodejs -- 流程控制库

本文详细阐述了Node.js中的尾触发机制在Connect中间件中的应用,介绍了async模块的系列、并行和瀑布流功能,以及如何使用事件发布/订阅和Promise/Deferred处理异步任务。
摘要由CSDN通过智能技术生成

流程控制库

尾触发和next

尾触发最多的应用是在Connect的中间件

var app = connect()
app.use(connect.staticCache())
app.use(connect.static(__dirname + '/public'))
app.use(conect.cokkieParser())
app.use(connect.session())
app.use(connect.query())
app.use(connect.bodyParser())
app.use(connect.csrf())
app.listen(3001)

通过use方法注册一系列的中间件,监听端口上的请求,中间件利用了尾触发的机制。

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

每个中间件传递请求对象,响应对象和尾触发的函数,通过队列形成一个处理流
在这里插入图片描述

中间件机制使得在处理网络请求的时候,可以向切面编程一样过滤,验证,日志等共嗯那个。不会与具体业务产生关联
Connect的核心代码

// 创建了http服务器的request事件处理函数
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)} 通过这个代码创建了http服务器的request事件处理函数。真正核心的代码是app.stack=[] stack属性是服务器内部维护的中间件队列,通过调用use方法可以将中间件放到队列中。

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

此时就建立好了模型,通过整合原生的http模块就可以实现监听。监听函数的实现

app.listen = function() {
    var serve = http.createServer(this)
    return server.listen.apply(serve, arguments)
}
app.handle = function(req, res, out) {
    next()
}

原始的next()方法较为复杂,简化后: 取出队列中的中间件并执行,同时传入当前方法实现链式调用。达到持续触发的目的

function next(node) {
    layer = stack[index++]
    layer.handle(req, res, next)
}

可以参考connect的流式处理模式。尽管中间件这种尾触发的模式不要求每个中间方法都是异步的,但是如果每个步骤都可以采用异步来完成,实际上只是串行化的处理,没有办法通过并行的异步调用来提升业务的处理效率,流式处理只是可以将一些串行的逻辑扁平化,但是并行逻辑处理还是需要搭配事件或者promise完成。
在connect中,尾触发适合处理网络请求的场景。将复杂的处理逻辑拆解为简介,单一的处理单元,逐层次的处理请求对象和响应对象。

async

async是流程控制模块,流程控制是开发过程中的基本需求,async提供了20多种的方法用于处理异步的各种写作模块
async提供了series()方法来实现一组任务的串行执行。

async.series([
    function(callback) {
        fs.readFile('file1.txt', 'utf-8', callback)
    }
    function(callback) {
        fs.readFile('file2.txt', 'utf-8', callback)
    }
], function(err, result) {
    // 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])
    })
}) 

series()方法中传入的函数callback()并非是由使用者指定,事实上,此处的回调函数通过async经过高阶函数的方式注入,这里隐含了特殊的逻辑,每个callback()执行的时候都会将结果保存起来,然后执行到下一个调用,直到结束所有的调用,最终的回调函数执行的时候,队列中的异步调用保存到结果通过数组的方式传入。

异步的并行执行

当需要通过并行来提升性能的时候,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('file.txt', 'utf-8', function(err, content) {
    if (err) {
        return fail(err)
    }
    done(0, data)
}) 
fs.readFile('file2.txt','utf-8', function(err, data) {
    if (err) {
        return fail(err)
    }
    done(1, data)
})

同样,通过async编写的代码既没有深度的嵌套,也灭有复杂的状态判断,parallel()方法对于异常的判断是一旦某个异步调用产生了异常,就会将异常作为第一个参数传入给最终的回调函数,只有所有的异步调用都正常完成的时候,才会将结果以数组的形式传入。
EventProxy[前几篇中有] 是基于事件发布/订阅模式设计的,也用到了async相同的原理,通过特殊的回调函数来返回隐含返回值的处理,不同的是在async这个架构中,这个回调函数是通过async封装后传递出来的,但是EventProxy是通过done和fail方法来产生新的回调函数,这两种方法都是高阶函数的应用

异步调用的依赖处理

series()适合无依赖的异步串行。当前一个是后一个调用的输入,series()无法满足需求。async提供了waterfall()方法。

async.waterfall([
    function(callback) {
        fs.readFile('file1.txt', 'utf-8', function(err,data) {
            callback(err, content)
        })
    },
    function (arg1, callback) {
        fs.readFile(arg1, 'utf-8', function(err, content) {
            callback(err, content)
        })
    }
], function (err, result) {
    // result => file4.txt
})

等价于

fs.readFile('file1.txt', 'utf-8', function(err, data) {
    if (err) {
        return callback(err)
    }
    fs.readFile(data1, 'utf-8', function (err, data2) {
    if (err) {
        return callback(err)
    }
    fs.readFile(data2, 'utf-8', function (err, data3) {    
    if (err) {
    return callback(err);
    }
    callback(null, data3)
    })
    })
})    

自动依赖处理

业务如下

1. 从磁盘读取配置文件
2. 链接mongodb
3. 配置文件链接redis
4. 编译静态文件
5. 上传cdn
6. 启动服务器

伪代码

readConfig: function() {}
connectMongoDB: function() {}
connectRedis: function() {}
complieAsserts: function() {}
uploadAsserts: function() {}
startup: function() {}

代码实现

var deps = {
  readConfig: function (callback) {
    // read config file
    callback();
  },
  connectMongoDB: [
    "readConfig",
    function (callback) {
      // connect to mongodb
      callback();
    },
  ],
  connectRedis: [
    "readconfig",
    function (callback) {
      // connect to redis
    },
  ],
  complieAsserts: function (callback) {
    // complie asserts
    callback();
  },
  uploadAsserts: [
    "complieAsserts",
    function (callback) {
      // upload to assert
      callback();
    },
  ],
  startup: [
    "connectMongoDB",
    "connectRedis",
    "uploadAsserts",
    function (callback) {
      // startup
    },
  ],
};

auto()方法可以根据依赖关系自动分析,以最佳的顺序执行上面的业务async.auto(deps)

proxy.assp('readtheconfig', function () {
    // read config file
    proxy.emit('readConfig');
}).on('readConfig', function () {
    // connect to mongodb
    proxy.emit('connectMongoDB');
}).on('readConfig', function () {
    // connect to redis
    proxy.emit('connectRedis');
}).assp('complietheasserts', function () {
    // complie asserts
    proxy.emit('complieAsserts');
}).on('complieAsserts', function () {
    // upload to assert
    proxy.emit('uploadAsserts');
}).all('connectMongoDB', 'connectRedis', 'uploadAsserts', function () {
    // Startup
});

step

Step(
function readFile1() {
    fs.readFile('file1.txt', 'utf-8',this)
},
function readFile2(err,content) {
    fs.readFile('file2.txt', 'utf-8',this)
},
function done(err,content) {
    console.log(content)
}
)

step 用到了this,是step内部的一个next()方法,将异步调用的结果传递给下一个任务作为参数。

并行任务执行

this具有一个parallel()方法,告诉step,需要等待所有的任务完成才能执行下一个任务。

Step(
  function readFile1() {
    fs.readFile("file1.txt", "utf-8", this.parallel());
    fs.readFile("file2.txt", "utf-8", this.parallel());
  },
  function done(err, content1, content2) {
    // content1 => file1
    // content2 => file2
    console.log(arguments);
  }
);

wind 不加赘述

小结

  1. 事件发布/订阅模式相对算是一种较为原始的方式,Promise/Deferred模式贡献了一个非常不错的异步任务模型的抽象。promise/deferred重点在于封装异步的调用
  2. 流程控制库在于会低啊函数的注入
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 安装Hexo 在安装Hexo之前,需要先安装Node.js和Git。然后通过以下命令安装Hexo: npm install -g hexo-cli 2. 创建一个Hexo博客 在本地创建一个新文件夹,然后通过以下命令在该文件夹中创建一个新的Hexo博客: hexo init my-blog cd my-blog npm install 3. 配置Hexo 在Hexo博客的根目录下,有一个_config.yml文件,打开该文件,配置Hexo博客的相关信息,如网站的标题、副标题、作者信息等。 4. 安装Hexo插件 Hexo有很多插件,可以通过插件来扩展Hexo的功能。例如,可以使用hexo-admin插件来实现Hexo后台管理页面的功能。通过以下命令来安装hexo-admin插件: npm install --save hexo-admin 5. 启动Hexo 在Hexo博客的根目录下,通过以下命令启动Hexo服务器: hexo server 然后在浏览器中访问http://localhost:4000/,就可以看到Hexo博客的首页了。 6. 添加登录功能 要添加登录功能,需要使用一个用户认证,例如passport.js。passport.js是一个Node.js的用户认证,可以用于实现用户的注册、登录、注销等功能。 首先,在Hexo博客的根目录下,通过以下命令安装passport.js: npm install passport passport-local express-session --save 然后,在Hexo博客的根目录下,创建一个名为passport.js的文件,并添加以下代码: var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; passport.use(new LocalStrategy( function(username, password, done) { if (username === 'admin' && password === 'password') { return done(null, {username: username}); } else { return done(null, false, {message: 'Incorrect username or password.'}); } } )); passport.serializeUser(function(user, done) { done(null, user.username); }); passport.deserializeUser(function(username, done) { done(null, {username: username}); }); module.exports = passport; 以上代码创建了一个本地认证策略,如果用户名为admin,密码为password,则认为认证成功。如果认证失败,则返回错误信息。 然后,在Hexo博客的根目录下,创建一个名为login.js的文件,并添加以下代码: var express = require('express'); var passport = require('./passport'); var router = express.Router(); router.get('/', function(req, res) { res.render('login'); }); router.post('/', passport.authenticate('local', { failureRedirect: '/login' }), function(req, res) { res.redirect('/'); }); module.exports = router; 以上代码创建了一个/login的路由,当用户访问/login时,会显示一个登录页面。当用户提交登录信息时,会使用passport.js进行认证,如果认证成功,则跳转到首页;如果认证失败,则返回错误信息。 最后,在Hexo博客的根目录下,创建一个名为app.js的文件,并添加以下代码: var express = require('express'); var session = require('express-session'); var bodyParser = require('body-parser'); var passport = require('./passport'); var login = require('./login'); var app = express(); app.set('views', __dirname + '/views'); app.set('view engine', 'ejs'); app.use(session({ secret: 'my secret', resave: false, saveUninitialized: false })); app.use(bodyParser.urlencoded({ extended: false })); app.use(passport.initialize()); app.use(passport.session()); app.use('/login', login); app.get('/', function(req, res) { if (req.isAuthenticated()) { res.render('index', { user: req.user }); } else { res.redirect('/login'); } }); app.listen(3000, function() { console.log('App listening on port 3000!'); }); 以上代码创建了一个Express应用程序,使用了session、body-parser、passport和login中间件。当用户访问首页时,如果已经认证成功,则显示首页;如果没有认证成功,则跳转到登录页面。 7. 测试登录功能 在Hexo博客的根目录下,通过以下命令启动应用程序: node app.js 然后在浏览器中访问http://localhost:3000/,会跳转到登录页面。输入用户名admin和密码password,登录成功后会跳转到首页,显示当前用户的信息。如果输入的用户名或密码不正确,则会显示错误信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值