玩转Koa之核心原理分析(2)

function use(route, fn) { var handle = fn; var path = route; // 不传入route则默认为'https://www.jb51.net/',这种基本是框架处理参数的一种套路 if (typeof route !== 'string') { handle = route; path = 'https://www.jb51.net/'; } ... // 存储中间件 this.stack.push({ route: path, handle: handle }); // 以便链式调用 return this; }

use方法内部获取到中间件的路由信息(默认为'https://www.jb51.net/')和中间件的处理函数之后,构建成layer对象,然后将其存储在一个队列当中,也就是上述代码中的stack。

connect中间件的执行流程主要由handle与call函数决定:

function handle(req, res, out) { var index = 0; var stack = this.stack; ... function next(err) { ... // 依次取出中间件 var layer = stack[index++] // 终止条件 if (!layer) { defer(done, err); return; } var path = parseUrl(req).pathname || 'https://www.jb51.net/'; var route = layer.route; // 路由匹配规则 if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) { return next(err); } ... call(layer.handle, route, err, req, res, next); } next(); }

handle函数中使用闭包函数next来检测layer是否与当前路由相匹配,匹配则执行该layer上的中间件函数,否则继续检查下一个layer。

这里需要注意next中检查路由的方式可能与想象中的不太一样,所以默认路由为'https://www.jb51.net/'的中间件会在每一次请求处理中都执行。

function call(handle, route, err, req, res, next) { var arity = handle.length; var error = err; var hasError = Boolean(err); try { if (hasError && arity === 4) { // 错误处理中间件 handle(err, req, res, next); return; } else if (!hasError && arity < 4) { // 请求处理中间件 handle(req, res, next); return; } } catch (e) { // 记录错误 error = e; } // 将错误传递下去 next(error); }

在通过call方法执行中间件方法的时候,采用try/catch捕获错误,这里有一个特别需要注意的地方是,call内部会根据是否存在错误以及中间件函数的参数决定是否执行错误处理中间件。并且一旦捕获到错误,next方法会将错误传递下去,所以接下来普通的请求处理中间件即使通过了next中的路由匹配,仍然会被call方法给过滤掉。

下面是layer的处理流程图:

玩转Koa之核心原理分析

上述就是connect中间件设计的核心要点,总结起来有如下几点:

通过use方法注册中间件;

中间件的顺序执行是通过next方法衔接的并且需要手动调用,在next中会进行路由匹配,从而过滤掉部分中间件;

当中间件的执行过程中发生异常,则next会携带异常过滤掉非错误处理中间件,也是为什么错误中间件会比其他中间件多一个error参数;

在请求处理的周期中,需要手动调用res.end()来结束响应;

 2、Koa中间件的设计

Koa中间件与connect中间件的设计有很大的差异:

Koa中间件的执行并不需要匹配路由,所以注册的中间件每一次请求都会执行。(当然还是需要手动调用next);

Koa中通过继承event,暴露error事件让开发者自定义异常处理;

Koa中res.end由中间件执行完成之后自动调用,这样避免在connect忘记调用res.end导致用户得不到任何反馈。

Koa中采用了async/await语法让开发者利用同步的方式编写异步代码。

当然,Koa中也是采用use方法注册中间件,相比较connect省去路由匹配的处理,就显得很简洁:

use(fn) { this.middleware.push(fn); return this; }

并且use支持链式调用。

Koa中间件的执行流程主要通过koa-compose中的compose函数完成:

function compose (middleware) { if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } /** * @param {Object} context * @return {Promise} * @api public */ return function (context, next) { let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { // 递归调用下一个中间件 return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } } }

看到这里本质上connect与koa实现中间件的思想都是递归,不难看出koa相比较connect实现得更加简洁,主要原因在于:

connect中提供路由匹配的功能,而Koa中则是相当于connect中默认的'https://www.jb51.net/'路径。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/50d5e538906503c135717bdb8ef17ad9.html