中间件实行模块koa-Compose源码剖析

原文博客地点,迎接进修交换:
点击预览

读了下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_function

https://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以后’

借用一张图来直观的申明:
《中间件实行模块koa-Compose源码剖析》

细致看他人怎样邃晓next的递次:https://segmentfault.com/q/1010000011033764

近来在看Koa的源码,以上属于个人邃晓,若有误差迎接斧正进修,感谢。

参考资料:https://koa.bootcss.com/
https://cnodejs.org/topic/58fd8ec7523b9d0956dad945

    原文作者:戎马
    原文地址: https://segmentfault.com/a/1190000013447551
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞