function call(handle, route, err, req, res, next) { var arity = handle.length; var error = err; var hasError = Boolean(err); debug( '%s %s : %s' , handle.name || '<anonymous>' , route, req.originalUrl); try { if (hasError && arity === 4) { // error-handling middleware handle(err, req, res, next); return ; } else if (!hasError && arity < 4) { // request-handling middleware handle(req, res, next); return ; } } catch (e) { // replace the error error = e; } // continue next(error); }
可以看出一个重点:对错误处理,connect 的要求 是函数必须是 四个参数,而 express 也是如此。如果有错误, 中间件没有一个参数的个数是 4, 就会错误一直传下去,直到后面的 defer(done, err); 进行处理。
还有 app.use 添加中间件:
proto.use = function use(route, fn) { var handle = fn; // fn 只是一个函数的话 三种接口 // 1. err, req, res, next 2. req, res, 3, req, res, next var path = route; // default route to 'https://www.jb51.net/' if ( typeof route !== 'string' ) { handle = route; path = 'https://www.jb51.net/' ; } // wrap sub-apps if ( typeof handle.handle === 'function' ) { // 自定义中的函数对象 var server = handle; server.route = path; handle = function (req, res, next) { // req, res, next 中间件 server.handle(req, res, next); }; } // wrap vanilla http.Servers if (handle instanceof http.Server) { handle = handle.listeners( 'request' )[0]; // (req, res) // 最后的函数 } // strip trailing slash if (path[path.length - 1] === 'https://www.jb51.net/' ) { path = path.slice(0, -1); } // add the middleware debug( 'use %s %s' , path || 'https://www.jb51.net/' , handle.name || 'anonymous' ); this .stack.push({ route: path, handle: handle }); return this ; };
从代码中,可以看出,use 方法添加中间件到 this.stack 中,其中 fn 中间件的形式有两种: function (req, res, next) 和 handle.handle(req, res, next) 这两种都可以。还有对 fn 情况进行特殊处理。
总的处理流程就是这样,用 use 方法添加中间件,用 next 编历中间件,用 finalHandle 进行最后的处理工作。
在代码中还有一个函数非常奇怪:
/* istanbul ignore next */ var defer = typeof setImmediate === 'function' ? setImmediate : function (fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
defer 函数中的 fn.bind.apply(fn, arguments) ,这个方法主要解决了,一个问题,不定参的情况下,第一个参数函数,怎样拿到的问题,为什么这样说呢?如果中我们要达到以上的效果,需要多多少行代码?
function () { var cb = Array.from(arguments)[0]; var args = Array.from(arguments).splice(1); process.nextTick( function () { cb.apply( null ,args); }) }
这还是 connect 兼容以前的 es5 之类的方法。如果在 es6 下面,方法可以再次简化
function (..args){ process.nextTick(fn.bind(...args)) }
总结
connect 做为 http 中间件模块,很好地解决对 http 请求的插件化处理的需求,把中间件组织成请求上的一个处理器,挨个调用中间件对 http 请求进行处理。
其中 connect 的递归调用,和对 js 的函数对象的使用,让值得学习,如果让我来写,就第一个调个的地方,就想不到使用 函数对象 来进行处理。
而且 next 的设计如此精妙,整个框架的使用和概念上,对程序员基本上没有认知负担,这才是最重要的地方。这也是为什么 express 框架最受欢迎。koa 相比之下,多几个概念,还使用了不常用的 yield 方法。
connect 的设计理念可以用在,类似 http 请求模式上, 如 rpc, tcp 处理等。
我把 connect 的设计方法叫做 中间件模式,对处理 流式模式,会有较好的效果。