原文博客地点,迎接进修交换:
点击预览
读了下Koa的源码,写的相称的精简,碰到处置惩罚中心件实行的模块koa-Compose,决议进修一下这个模块的源码。
浏览本文可以学到:
- Koa中心件的加载
- next参数的泉源
- 中心件掌握权实行递次
先上一段运用Koa启动效劳的代码:
放在文件app.js中
const koa = require('koa'); // require引入koa模块
const app = new koa(); // 建立对象
app.use(async (ctx,next) => {
console.log('第一个中心件')
next();
})
app.use(async (ctx,next) => {
console.log('第二个中心件')
next();
})
app.use((ctx,next) => {
console.log('第三个中心件')
next();
})
app.use(ctx => {
console.log('预备相应');
ctx.body = 'hello'
})
app.listen(3000)
以上代码,可以运用node app.js启动,启动后可以在浏览器中接见http://localhost:3000/
接见后,会在启动的敕令窗口中打印出以下值:
第一个中心件
第二个中心件
第三个中心件
预备相应
代码申明:
- app.use()要领,用来将中心件增加到行列中
- 中心件就是传给app.use()作为的参数的函数
- 运用app.use()将函数增加至行列当中后,当有要求时,会顺次触发行列中的函数,也就是顺次实行一个个中心件函数,实行递次依据挪用app.use()增加的递次。
- 在每一个中心件函数中,会实行next()函数,意义是把掌握权交到下一个中心件(实际上是挪用next函数后,会挪用下一个中心件函数,背面剖析源码会有申明),假如不挪用next()函数,不能挪用下一个中心件函数,那末行列实行也就停止了,在上面的代码中表现就是不能相应客户端的要求了。
app.use(async (ctx,next) => {
console.log('第二个中心件')
// next(); 解释以后,下一个中心件函数就不会实行
})
内部历程剖析
- 内部应用app.use()增加到一个数组行列中:
// app.use()函数内部增加
this.middleware.push(fn);
// 终究this.middleware为:
this.middleware = [fn,fn,fn...]
细致参考这里Koa的源码use函数:https://github.com/koajs/koa/blob/master/lib/application.js#L104
- 运用koa-compose模块的compose要领,把这个中心件数组合并成一个大的中心件函数
const fn = compose(this.middleware);
细致参考这里Koa的源码https://github.com/koajs/koa/blob/master/lib/application.js#L126
- 在有要求后后会实行这个中心件函数fn,进而会把一切的中心件函数顺次实行
如许单方面的形貌可能会不知所云,可以跳过不看,只是让诸位晓得Koa实行中心件的历程
本篇主如果剖析
koa-compose的源码,以后剖析全部Koa的源码后会做细致申明
所以最主要的照样运用koa-compose模块来掌握中心件的实行,那末来一探终究这个模块怎样举行事情的
koa-compose
koa-compose模块可以将多个中心件函数合并成一个大的中心件函数,然后挪用这个中心件函数就可以顺次实行增加的中心件函数,实行一系列的使命。
源码地点:https://github.com/koajs/compose/blob/master/index.js
先从一段代码最先,建立一个compose.js的文件,写入以下代码:
const compose = require('koa-compose');
function one(ctx,next){
console.log('第一个');
next(); // 掌握权交到下一个中心件(实际上是可以实行下一个函数),
}
function two(ctx,next){
console.log('第二个');
next();
}
function three(ctx,next){
console.log('第三个');
next();
}
// 传入中心件函数构成的数组行列,合并成一个中心件函数
const middlewares = compose([one, two, three]);
// 实行中心件函数,函数实行后返回的是Promise对象
middlewares().then(function (){
console.log('行列实行终了');
})
可以运用node compose.js运转此文件,敕令行窗口打印出:
第一个
第二个
第三个
行列实行终了
中心件这儿的重点,是compose函数。compose函数的源代码虽然很简约,但要邃晓邃晓实在要下一番工夫。
以下为源码剖析:
'use strict'
/**
* Expose compositor.
*/
// 暴露compose函数
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
// 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函数
function two(ctx,next){
console.log('第二个');
next().then(function(){
console.log('第二个挪用then后')
});
}
- 中心件函数可所以async/await函数,在函数内部可以写恣意的异步处置惩罚,处置惩罚获得效果后再举行下一个中心件函数。
建立一个文件问test-async.js,写入以下代码:
const compose = require('koa-compose');
// 猎取数据
const getData = () => new Promise((resolve, reject) => {
setTimeout(() => resolve('获得数据'), 2000);
});
async function one(ctx,next){
console.log('第一个,守候两秒后再举行下一个中心件');
// 模仿异步读取数据库数据
await getData() // 比及猎取数据后继承实行下一个中心件
next()
}
function two(ctx,next){
console.log('第二个');
next()
}
function three(ctx,next){
console.log('第三个');
next();
}
const middlewares = compose([one, two, three]);
middlewares().then(function (){
console.log('行列实行终了');
})
可以运用node test-async.js运转此文件,敕令行窗口打印出:
第一个,守候两秒后再举行下一个中心件
第二个
第三个
第二个挪用then后
行列实行终了
在以上打印输出历程当中,实行第一个中心件后,在内部会有一个异步操纵,运用了async/await后获得同步操纵一样的体验,这步操纵多是读取数据库数据或许读取文件,读取数据后,挪用next()实行下一个中心件。这里模仿式守候2秒后再实行下一个中心件。
更多参考了async/await:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_functionhttps://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await
实行递次
挪用next后,实行的递次会让人发生疑惑,建立文件为text-next.js,写入以下代码:
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)
以上代码,可以运用node text-next.js启动,启动后可以在浏览器中接见http://localhost:3000/
接见后,会在启动的敕令窗口中打印出以下值:
第一个中心件函数
第二个中心件函数
相应
第二个中心件函数next以后
第一个中心件函数next以后
是否是对这个递次发生了深深地疑问,为何会如许呢?
当一个中心件挪用 next() 则该函数停息并将掌握通报给定义的下一个中心件。当在下流没有更多的中心件实行后,客栈将睁开而且每一个中心件恢复实行其上游行动。
历程是如许的:
- 先实行第一个中心件函数,打印出 ‘第一个中心件函数’
- 挪用了next,不再继承向下实行
- 实行第二个中心件函数,打印出 ‘第二个中心件函数’
- 挪用了next,不再继承向下实行
- 实行末了一个中心件函数,打印出 ‘相应’
- …
- 末了一个中心函数实行后,上一个中心件函数收回掌握权,继承实行,打印出 ‘第二个中心件函数next以后’
- 第二个中心件函数实行后,上一个中心件函数收回掌握权,继承实行,打印出 ‘第一个中心件函数next以后’
借用一张图来直观的申明:
细致看他人怎样邃晓next的递次:https://segmentfault.com/q/1010000011033764
近来在看Koa的源码,以上属于个人邃晓,若有误差迎接斧正进修,感谢。
参考资料:https://koa.bootcss.com/
https://cnodejs.org/topic/58fd8ec7523b9d0956dad945