💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # 中间件执行顺序 koa的next()方法,与express不一样,调用后并非直接跳过当前中间件,而是该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。 ~~~ const koa = require('koa'); const app = new koa(); app.use((ctx, next) => { console.log('第一个中间件函数') next(); console.log('第一个中间件函数next之后'); }) app.use(async (ctx, next) => { console.log('第二个中间件函数') next(); console.log('第二个中间件函数next之后'); }) app.use(ctx => { console.log('响应'); ctx.body = 'hello' }) app.listen(3000) ~~~ 结果 ``` // 第一个中间件函数 // 第二个中间件函数 // 响应 // 第二个中间件函数next之后 // 第一个中间件函数next之后 ``` 使用洋葱模型来说明 ![](https://box.kancloud.cn/124ca99aceddded2be3c2f2ca4a87b8b_800x311.png) # 使用中间件 * app.use()方法,用来将中间件添加到队列中 * 中间件就是传给app.use()作为的参数的函数 * 使用app.use()将函数添加至队列之中后,当有请求时,会依次触发队列中的函数,也就是依次执行一个个中间件函数,执行顺序按照调用app.use()添加的顺序。 * 在每个中间件函数中,会执行next()函数,意思是把控制权交到下一个中间件(实际上是调用next函数后,会调用下一个中间件函数,后面解析源码会有说明),如果不调用next()函数,不能调用下一个中间件函数,那么队列执行也就终止了,在上面的代码中表现就是不能响应客户端的请求了。 ``` // koa/lib/application.js { /** * * @param {Function} fn * @return {Application} self */ use(fn) { if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); if (isGeneratorFunction(fn)) { fn = convert(fn); } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); return this; } } ``` <br> # 内部过程 * 内部利用 `app.use()` 添加到一个数组队列中: ~~~ // app.use()函数内部添加 this.middleware.push(fn); // 最终this.middleware为: this.middleware = [fn,fn,fn...] ~~~ * 使用`koa-compose`模块的`compose`方法,把这个中间件数组合并成一个大的中间件函数 ~~~ const fn = compose(this.middleware); ~~~ * 在有请求后后会执行这个中间件函数 fn,进而会把所有的中间件函数依次执行 # koa-compose 源码 ``` 'use strict' module.exports = compose /** * @param {Array} middleware * @return {Function} */ // compose函数需要传入一个数组队列 [fn,fn,fn,fn] 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 */ // compose函数调用后,返回的是以下这个匿名函数 // 匿名函数接收两个参数,第一个随便传入,根据使用场景决定 // 第一次调用时候第二个参数next实际上是一个undefined,因为初次调用并不需要传入next参数 // 这个匿名函数返回一个promise return function (context, next) { // last called middleware # //初始下标为-1 let index = -1 return dispatch(0) function dispatch (i) { // 如果传入i为负数且<=-1 返回一个Promise.reject携带着错误信息 // 所以执行两次next会报出这个错误。将状态rejected,就是确保在一个中间件中next只调用一次 if (i <= index) return Promise.reject(new Error('next() called multiple times')) // 执行一遍next之后,这个index值将改变 index = i // 根据下标取出一个中间件函数 let fn = middleware[i] // next在这个内部中是一个局部变量,值为undefined // 当i已经是数组的length了,说明中间件函数都执行结束,执行结束后把fn设置为undefined // 问题:本来middleware[i]如果i为length的话取到的值已经是undefined了,为什么要重新给fn设置为undefined呢? if (i === middleware.length) fn = next //如果中间件遍历到最后了。那么。此时return Promise.resolve()返回一个成功状态的promise // 方面之后做调用then if (!fn) return Promise.resolve() // try catch保证错误在Promise的情况下能够正常被捕获。 // 调用后依然返回一个成功的状态的Promise对象 // 用Promise包裹中间件,方便await调用 // 调用中间件函数,传入context(根据场景不同可以传入不同的值,在KOa传入的是ctx) // 第二个参数是一个next函数,可在中间件函数中调用这个函数 // 调用next函数后,递归调用dispatch函数,目的是执行下一个中间件函数 // next函数在中间件函数调用后返回的是一个promise对象 // 读到这里不得不佩服作者的高明之处。 try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } } ``` 补充说明: * 根据以上的源码分析得到,在一个中间件函数中不能调用两次**next()**,否则会抛出错误 ~~~ function one(ctx,next){ console.log('第一个'); next(); next(); } ~~~ 抛出错误: ``` next() called multiple times ``` * `next()`调用后返回的是一个Promise对象,可以调用then函数 * 中间件函数可以是async/await函数,在函数内部可以写任意的异步处理,处理得到结果后再进行下一个中间件函数。 ~~~ function two(ctx,next){ console.log('第二个'); next().then(function(){ console.log('第二个调用then后') }); } ~~~ <br> <br> # 测试 ``` var index = -1; function compose() { return dispatch(0) } function dispatch(i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i var fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve('fn is undefined') try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } function f1(context, next) { console.log('middleware 1'); next().then(data => console.log(data, 'f1 then')); console.log('middleware 1'); return 'middleware 1 return'; } function f2(context, next) { console.log('middleware 2'); next().then(data => console.log(data, 'f2 then')); console.log('middleware 2'); return 'middleware 2 return'; } function f3(context, next) { console.log('middleware 3'); next().then(data => console.log(data, 'f3 then')); console.log('middleware 3'); return 'middleware 3 return'; } var middleware = [ f1, f2, f3 ] var context = {}; var next = function (context, next) { console.log('middleware 4'); next().then(data => console.log(data, 'next then')); console.log('middleware 4'); return 'middleware 4 return'; }; compose().then(data => console.log(data, 'compose then')); ``` <br> <br> 结果 ``` middleware 1 middleware 2 middleware 3 middleware 4 middleware 4 middleware 3 middleware 2 middleware 1 fn is undefined next then middleware 4 return f3 then middleware 3 return f2 then middleware 2 return f1 then middleware 1 return compose then ``` <br> # 参考资料 * [傻瓜式解读koa中间件处理模块koa-compose](https://segmentfault.com/a/1190000016843275) * [中间件执行模块koa-Compose源码分析](https://segmentfault.com/a/1190000013447551)