Nodejs 的经典 httpServer 代码
var http = require('http'); var server = http.createServer(requestHandler); function requestHandler(req, res) { res.end('hello visitor!'); } server.listen(3000);
里面的函数 requestHandler
就是所有http请求的响应函数,即所有的请求都经过这个函数的处理,是所有请求的入口函数。
通过 requestHandler 函数我们能写一些简单的 http 逻辑,比如上面的例子,所有请求都返回 hello visitor!
。
然而,我们的业务逻辑不可能这么简单。例如:需要实现一个接口,要做的是当请求过来时,先判断来源的请求是否包含请求体,然后判断请求体中的id是不是在数据库中存在,最后若存在则返回true,不存在则返回false。
1. 检测请求中请求体是否存在,若存在则解析请求体;
1. 查看请求体中的id是否存在,若存在则去数据库查询;
1. 根据数据库结果返回约定的值;
我们首先想到的,抽离函数,每个逻辑一个函数,简单好实现低耦合好维护。
实现代码:
function parseBody(req, callback) { //根据http协议从req中解析body callback(null, body); } function checkIdInDatabase(body, callback) { //根据body.id在Database中检测,返回结果 callback(null, dbResult); } function returnResult(dbResult, res) { if (dbResult && dbResult.length > 0) { res.end('true'); } else { res.end('false') } } function requestHandler(req, res) { parseBody(req, function(err, body) { checkIdInDatabase(body, function(err, dbResult) { returnResult(dbResult, res); }); }); }
上面的解决方案解决了包含三个步骤的业务问题,出现了3个 });
还有3个 err
需要处理,上面的写法可以得达到预期效果。
然而,业务逻辑越来越复杂,会出发展成30个回调逻辑,那么就出现了30个 });
及30个 err
异常。更严重的是,到时候写代码根本看不清自己写的逻辑在30层中的哪一层,极其容易出现 多次返回 或返回地方不对等问题,这就是 回调金字塔 问题了。
大多数同学应该能想到解决回调金字塔的办法,朴灵的《深入浅出Node.js》里讲到的三种方法。下面列举了这三种方法加上ES6新增的Generator,共四种解决办法。
理论上,这四种都能解决回调金字塔问题。而Connect和Express用的是 类似异步流程控制的思想
。
关于异步流程控制库下面简要介绍下,或移步@第五课。 异步流程控制库首先要求用户传入待执行的函数列表,记为funlist。流程控制库的任务是让这些函数 顺序执行 。
callback是控制顺序执行的关键,funlist里的函数每当调用callback会执行下一个funlist里的函数
我们动手实现一个类似的链式调用,其中 funlist
更名为 middlewares
、callback
更名为 next
,码如下:
var middlewares = [ function fun1(req, res, next) { parseBody(req, function(err, body) { if (err) return next(err); req.body = body; next(); }); }, function fun2(req, res, next) { checkIdInDatabase(req.body.id, function(err, rows) { if (err) return next(err); res.dbResult = rows; next(); }); }, function fun3(req, res, next) { if (res.dbResult && res.dbResult.length > 0) { res.end('true'); } else { res.end('false'); } next(); } ] function requestHandler(req, res) { var i=0; //由middlewares链式调用 function next(err) { if (err) { return res.end('error:', err.toString()); } if (i<middlewares.length) { middlewares[i++](req, res, next); } else { return ; } } //触发第一个middleware next(); }
上面用middlewares+next完成了业务逻辑的 链式调用
,而middlewares里的每个函数,都是一个 中间件
。
整体思路是:
处理逻辑函数(中间件)
存储在一个list中;循环调用
list中的 处理逻辑函数(中间件)
;Connect的思想跟上面阐述的思想基本一样,先将处理逻辑存起来,然后循环调用。
Connect中主要有五个函数 PS: Connect的核心代码是200+行,建议对照源码看下面的函数介绍。
函数名 | 作用 |
---|---|
createServer | 包装httpServer形成app |
listen | 监听端口函数 |
use | 向middlewares里面放入业务逻辑 |
handle | 上一章的requestHandler函数增强版 |
call | 业务逻辑的真正执行者 |
大家都知道Express是Connect的升级版。
Express不只是Connect的升级版,它还封装了很多对象来方便业务逻辑处理。Express里的Router是Connect的升级版。
Express大概可以分为几个模块
模块 | 描述 |
---|---|
router | 路由模块是Connect升级版 |
request | 经过Express封装的req对象 |
response | 经过Express封装的res对象 |
application | app上面的各种默认设置 |
简要介绍一下每个模块