MrQuinn 2020-08-16
假设所有的数据库读取,http api 接口请求都为一个中间件,将中间件当做插件,插入需要获取数据的位置。
api.js
module.exports = async (ctx, next) => { ctx.share_data.api_data = await axios.get('/api'); await next(); };
db.js
module.exports = async (ctx, next) => { ctx.share_data.db_data = await mysql_query('SELECT XXX').catch(() => {}); await next(); };
串联
app.js
const api = require('api.js'); const db = require('db.js'); app.get('/get-api', api, (ctx) => ctx.body = ctx.share_data); app.get('/get-db', db, (ctx) => ctx.body = ctx.share_data); app.get('/get-api-and-db', api, db, (ctx) => ctx.body = ctx.share_data);
看着挺和谐,但是如果有多个数据中间件串联则会导致接口的响应时间为所有中间件的总和。
并发
可义一个 compose 函数,需要并发的中间件包装起来
super-compose.js
module.exports = (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!'); } return async (context = {}, next = f => f) => { await Promise.all( middleware.map(middleware => { return new Promise((rs, rj) => { middleware(context, () => Promise.resolve()) .then(rs) .catch(rj); }); }), ); await next(); }; };
app.js
const api = require('api.js'); const db = require('db.js'); const superCompose = require('super-compose.js'); app.get('/get-api-and-db', superCompose([api, db]), (ctx) => ctx.body = ctx.share_data);
依赖关系
看着貌似解决了,但如何处理具有上下文依赖的情况呢?例如 api_1 依赖 api 的数据。
改下 api.js,加上缓存校验。处理可能被多次compose的重复接口调用
module.exports = async (ctx, next) => { if (ctx.share_data.api_data) { return await next(); } ctx.share_data.api_data = await axios.get('/api'); await next(); };
api-1.js
const api = require('api.js'); module.exports = compose([ api, async (ctx, next) => { const { api_data: { api_data: { id = 0 } = {} } = {} } = ctx; if (id < 0) { await next(); } else { ctx.api_data.api_1_data = await axios.get('/api', { params: { id } }); } await next(); }, ])
app.js
const api_1 = require('api_1.js'); const db = require('db.js'); const superCompose = require('super-compose.js'); app.get('/get-api-and-db', superCompose([api_1, db]), (ctx) => ctx.body = ctx.share_data);
跳过中间件
有时候,需要根据特定的条件,绕过某些接口调用
改造下 api.js,通过加入过滤列表
module.exports = async (ctx, next) => { const { break_list = [] } = ctx; if (break_list.includes('api_data')) { // 可能会误伤其他组合引用该中间件的情况。 // 如可能会误伤,可加上。 // ctx.break_list = break_list.filter(v => v !== 'api_data') return await next(); } else { ctx.share_data.api_data = await axios.get('/api'); } await next(); }
app.js
const api = require('api.js'); const db = require('db.js'); const superCompose = require('super-compose.js'); app.get( '/get-api-and-db', async (ctx, next) => { ctx.break_list = ['api_data']; await next(); }, superCompose([api, db]), ctx => (ctx.body = ctx.share_data) );
数据合并处理
结合 super-compose 与 koa-compose 将所有需要的中间件组合起来,在写一个针对页面的controller
const api = require('api.js'); const db = require('db.js'); const superCompose = require('super-compose.js'); const compost = rquire('koa-compose') const babala = compose([ superCompose([api, db]), async (ctx, next) => { const { share_data: { api_data: { id = 0 } = {}, db_data: { title } = {} } = {}, } = ctx; ctx.body = { id, title }; // OR // ctx.share_data.babala = {} }, ]); app.get( '/get-api-and-db', babala );
结尾
解决经常出现的一个函数内大量的接口、逻辑操作,超长的上下文逻辑。
app.get('/api', async ctx => { const api_1 = await axios.get('/api_1'); await api_2 = await axios.get('/api_2'); // ... // ... // 这里有一百行 // ... const [api_3, api_4] = await new Promise.all([axios.get('/api_3'), axios.get('/api_4')]); // ... // ... // 这里有几百行 // ... ctx.body = {}; });