如何从头实现一个node.js的koa框架(4)

let middlewares = [m1, m2, m3]; let len = middlewares.length; // 最后一个中间件的next设置为一个立即resolve的promise函数 let next = async function () { return Promise.resolve(); } for (let i = len - 1; i >= 0; i--) { next = createNext(middlewares[i], next); } next(); // 输出m1, m2, m3

至此,我们也有了koa中间件机制实现的思路,新的application.js如下:

/** * @file simpleKoa application对象 */ let http = require('http'); let context = require('./context'); let request = require('./request'); let response = require('.//response'); class Application { /** * 构造函数 */ constructor() { this.middlewares = []; this.context = context; this.request = request; this.response = response; } // ...省略中间 /** * 中间件挂载 * @param {Function} middleware 中间件函数 */ use(middleware) { this.middlewares.push(middleware); } /** * 中间件合并方法,将中间件数组合并为一个中间件 * @return {Function} */ compose() { // 将middlewares合并为一个函数,该函数接收一个ctx对象 return async ctx => { function createNext(middleware, oldNext) { return async () => { await middleware(ctx, oldNext); } } let len = this.middlewares.length; let next = async () => { return Promise.resolve(); }; for (let i = len - 1; i >= 0; i--) { let currentMiddleware = this.middlewares[i]; next = createNext(currentMiddleware, next); } await next(); }; } /** * 获取http server所需的callback函数 * @return {Function} fn */ callback() { return (req, res) => { let ctx = this.createContext(req, res); let respond = () => this.responseBody(ctx); let fn = this.compose(); return fn(ctx).then(respond); }; } // ...省略后面 } module.exports = Application;

可以看到,首先对app.use进行改造了,每次调用app.use,就向this.middlewares中push一个回调函数。然后增加了一个compose()方法,利用我们前文分析的原理,对middlewares数组中的函数进行组装,返回一个最终的函数。最后,在callback()方法中,调用compose()得到最终回调函数,并执行。

改写example.js验证一下中间件机制:

let simpleKoa = require('./application'); let app = new simpleKoa(); let responseData = {}; app.use(async (ctx, next) => { responseData.name = 'tom'; await next(); ctx.body = responseData; }); app.use(async (ctx, next) => { responseData.age = 16; await next(); }); app.use(async ctx => { responseData.sex = 'male'; }); app.listen(3000, () => { console.log('listening on 3000'); }); // 返回{ name: "tom", age: 16, sex: "male"}

例子中一共三个中间件,分别对responseData增加了name, age, sex属性,最后返回该数据。

至此,一个koa框架基本已经浮出水面了,不过我们还需要进行最后一个主线的分析:错误处理。

主线四:错误处理

一个健壮的框架,必须保证在发生错误的时候,能够捕获错误并有降级方案返回给客户端。但显然现在我们的框架还做不到这一点,假设我们修改一下例子,我们的中间件中,有一个发生错误抛出了异常:

let simpleKoa = require('./application'); let app = new simpleKoa(); let responseData = {}; app.use(async (ctx, next) => { responseData.name = 'tom'; await next(); ctx.body = responseData; }); app.use(async (ctx, next) => { responseData.age = 16; await next(); }); app.use(async ctx => { responseData.sex = 'male'; // 这里发生了错误,抛出了异常 throw new Error('oooops'); }); app.listen(3000, () => { console.log('listening on 3000'); });

这个时候访问浏览器,是得不到任何响应的,这是因为异常并没有被我们的框架捕获并进行降级处理。回顾我们application.js中的中间件执行代码:

// application.js // ... callback() { return (req, res) => { let ctx = this.createContext(req, res); let respond = () => this.responseBody(ctx); let fn = this.compose(); return fn(ctx).then(respond); }; } // ...

其中我们知道,fn是一个async函数,执行后返回一个promise,回想promise的错误处理是怎样的?没错,我们只需要定义一个onerror函数,里面进行错误发生时候的降级处理,然后在promise的catch方法中引用这个函数即可。

于此同时,回顾koa框架,我们知道在错误发生的时候,app对象可以通过app.on('error', callback)订阅错误事件,这有助于我们几种处理错误,比如打印日志之类的操作。为此,我们也要对Application对象进行改造,让其继承nodejs中的events对象,然后在onerror方法中emit错误事件。改造后的application.js如下:

/** * @file simpleKoa application对象 */ let EventEmitter = require('events'); let http = require('http'); let context = require('./context'); let request = require('./request'); let response = require('./response'); class Application extends EventEmitter { /** * 构造函数 */ constructor() { super(); this.middlewares = []; this.context = context; this.request = request; this.response = response; } // ... /** * 获取http server所需的callback函数 * @return {Function} fn */ callback() { return (req, res) => { let ctx = this.createContext(req, res); let respond = () => this.responseBody(ctx); let onerror = (err) => this.onerror(err, ctx); let fn = this.compose(); // 在这里catch异常,调用onerror方法处理异常 return fn(ctx).then(respond).catch(onerror); }; } // ... /** * 错误处理 * @param {Object} err Error对象 * @param {Object} ctx ctx实例 */ onerror(err, ctx) { if (err.code === 'ENOENT') { ctx.status = 404; } else { ctx.status = 500; } let msg = err.message || 'Internal error'; ctx.res.end(msg); // 触发error事件 this.emit('error', err); } } module.exports = Application;

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

转载注明出处:http://www.heiqu.com/952573eea8af3104b0327baf62be8e1b.html