我们知道,Koa 中间件是以级联代码(Cascading) 的方式来执行的。类似于回形针的方式,可参照下面这张图:
今天这篇文章就来分析 Koa 的中间件是如何实现级联执行的。
在 koa 中,要应用一个中间件,我们使用 app.use():
app
.use(logger())
.use(bodyParser())
.use(helmet())
先来看看use() 是什么,它的源码如下:
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}
这个函数的作用在于将调用 use(fn) 方法中的参数(不管是普通的函数或者是中间件)都添加到 this.middlware 这个数组中。
在 Koa2 中,还对 Generator 语法的中间件做了兼容,使用 isGeneratorFunction(fn) 这个方法来判断是否为 Generator 语法,并通过 convert(fn) 这个方法进行了转换,转换成 async/await 语法。然后把所有的中间件都添加到了 this.middleware ,最后通过 callback() 这个方法执行。callback() 源码如下:
/**
* Return a request handler callback
* for node's native http server.
*
* @return {Function}
* @api public
*/
callback() {
const fn = compose(this.middleware);
if (!this.listeners('error').length) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
源码中,通过 compose() 这个方法,就能将我们传入的中间件数组转换并级联执行,最后 callback() 返回this.handleRequest()的执行结果。返回的是什么内容我们暂且不关心,我们先来看看 compose() 这个方法做了什么事情,能使得传入的中间件能够级联执行,并返回 Promise。
compose() 是 koa2 实现中间件级联调用的一个库,叫做 koa-compose。源码很简单,只有一个函数,如下:
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
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) {
// 理论上 i 会大于 index,因为每次执行一次都会把 i递增,
// 如果相等或者小于,则说明next()执行了多次
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, function next () {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
可以看到 compose() 返回一个匿名函数的结果,该匿名函数自执行了 dispatch() 这个函数,并传入了0作为参数。
来看看 dispatch(i) 这个函数都做了什么事?
i 作为该函数的参数,用于获取到当前下标的中间件。在上面的 dispatch(0) 传入了0,用于获取 middleware[0] 中间件。
首先显示判断 i<==index,如果 true 的话,则说明 next() 方法调用多次。为什么可以这么判断呢?等我们解释了所有的逻辑后再来回答这个问题。
接下来将当前的 i 赋值给 index,记录当前执行中间件的下标,并对 fn 进行赋值,获得中间件。
index = i;
let fn = middleware[i]
获得中间件后,怎么使用?
try {
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
上面的代码执行了中间件 fn(context, next),并传递了 context 和 next 函数两个参数。context 就是 koa 中的上下文对象 context。至于 next 函数则是返回一个 dispatch(i+1) 的执行结果。值得一提的是 i+1 这个参数,传递这个参数就相当于执行了下一个中间件,从而形成递归调用。
这也就是为什么我们在自己写中间件的时候,需要手动执行
await next()
只有执行了 next 函数,才能正确得执行下一个中间件。
因此每个中间件只能执行一次 next,如果在一个中间件内多次执行 next,就会出现问题。回到前面说的那个问题,为什么说通过 i<=index 就可以判断 next 执行多次?
因为正常情况下 index 必定会小于等于 i。如果在一个中间件中调用多次 next,会导致多次执行 dispatch(i+1)。从代码上来看,每个中间件都有属于自己的一个闭包作用域,同一个中间件的 i 是不变的,而 index 是在闭包作用域外面的。
当第一个中间件即 dispatch(0) 的 next() 调用时,此时应该是执行 dispatch(1),在执行到下面这个判断的时候,
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
此时的 index的值是0,而 i 的值是1,不满足 i<=index 这个条件,继续执行下面的 index=i 的赋值,此时 index 的值为1。但是如果第一个中间件内部又多执行了一次 next()的话,此时又会执行 dispatch(2)。上面说到,同一个中间件内的 i 的值是不变的,所以此时 i 的值依然是1,所以导致了 i <= index 的情况。
可能会有人有疑问?既然 async 本身返回的就是 Promise,为什么还要在使用 Promise.resolve() 包一层呢。这是为了兼容普通函数,使得普通函数也能正常使用。
再回到中间件的执行机制,来看看具体是怎么回事。
我们知道 async 的执行机制是:只有当所有的 await 异步都执行完之后才能返回一个 Promise。所以当我们用 async 的语法写中间件的时候,执行流程大致如下:
通过上面的分析之后,如果你要写一个 koa2 的中间件,那么基本格式应该就长下面这样:
async function koaMiddleware(ctx, next){
try{
// do something
await next()
// do something
}
.catch(err){
// handle err
}
}
koa是一个基于node实现的一个新的web框架,它是由express框架的原班人马打造的。它的特点是优雅、简洁、表达力强、自由度高。它更express相比,它是一个更轻量的node框架
koa-easywechat注意:koa-easywechat中间件要写在最前面,也就是要第一个use,因为我在ctx上挂载了一个wechat对象,这个对象实现了大部分的微信接口,这样才能保证开发者在自己的写路由里,获取到ctx.wechat进行自己的业务开发
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。
安装 koa、简单使用、级联中间件的概念、获取get请求参数、获取post表单数据和文件上传、路由中间件 koa-router
想使用nodejs(koa)搭建一个完整的前后端,完成数据的增删改查,又不想使用数据库,那使用json文件吧。本文介绍了基于koa的json文件的增、删、改、查。
对于一个服务器应用来说,日志的记录是必不可少的,我们需要使用其记录项目程序每天都做了什么,什么时候发生过错误,发生过什么错误等等,便于日后回顾、实时掌握服务器的运行状态,还原问题场景
Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为
上传文件在开发中是很常见的操作,今天我选择使用koa-multer中间件来实现这一功能,除了上传文件外,我还会对文件上传进行限制,以及发生上传错误时的处理。由于原来的 koa-multer 已经停止维护,我们要使用最新的 @koa/multer
这篇文章会讲些什么?如何从零开始完成一个涵盖Koa核心功能的Node.js类库,从代码层面解释Koa一些代码写法的原因:如中间件为什么必须调用next函数、ctx是怎么来的和一个请求是什么关系
Nodejs官方api支持的都是callback形式的异步编程模型。问题:callback嵌套问题,koa2 是由 Express原班人马打造的,是现在比较流行的基于Node.js平台的web开发框架,Koa 把 Express 中内置的 router、view 等功能都移除了
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!