koa入坑及其中间件原理(1)

看见了吧,刚入坑koa总是会看见一张洋葱图(后面再说吧)

刚入坑一个东西的时候,我们总会问这个东西是什么?这个东西是用来做什么的?这个东西包括了哪些内容以及它的原理?

那好,我们先来解决第一个问题,koa.js是什么东西?

koa.js是一个框架。

哈哈哈哈,第二个问题,koa.js用来做什么的?

① 对于 HTTP 服务

  当前端 UI 向服务器发起了一个 HTTP 请求时,koa.js 能够在 HTTP 请求发送后 (要搞清楚,是已经发送出去的请求,并不是像 axios 一样拦截 request )对于该请求的 request 进行处理

  既然能对请求的 request 进行处理,那么 koa.js 也能对于服务端返回的 HTTP 响应(跟上述一样,是已经发出的 response )进行处理。

② 对于中间件容器

  对于这个中间件容器,我也没什么概念,于是乎... 我去找了下相关的内容。

  什么是中间件容器呢?

  在一个大型分布式的系统中, 负责各个不同组件和服务之间的管理和交互。(哎,简单来说点就是你,你爸,你妈,你想要找你爸要点零花钱,但是必须通过你妈哈哈哈哈哈)

  比如呢,在一个分布式系统中,有N个数据库,数据库按业务模块分配,但是,随时间增加,业务会改变,而对应的数据库的分配也需要改变。这时候,你如果想要修改某些数据,直接操作数据库是不太可能了,那这时候就需要一个中间件,由它来负责对最终的数据库读写,而访问者只是带着一些参数啥的来访问这个中间件。

  言归正传... 切回到主题 koa.js 对于中间件容器能干嘛呢?

  第一点:中间的加载

  第二点:中间的执行

对于 koa.js的作用也就这两点了。

讲完了它是啥,做什么的,该来讲讲它的内容了。

在使用 Koa.js 的过程中,会发现中间件的使用都是如下所示:

const Koa = require(‘koa‘);
let app = new Koa();

const middleware1 = async (ctx, next) => {
  console.log(1);
  await next();
  console.log(6);
}

const middleware2 = async (ctx, next) => {
  console.log(2);
  await next();
  console.log(5);
}

const middleware3 = async (ctx, next) => {
  console.log(3);
  await next();
  console.log(4);
}

app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.use(async(ctx, next) => {
  ctx.body = ‘hello world‘
})

app.listen(3001)

// 启动访问浏览器
// 控制台会出现以下结果
// 1
// 2
// 3
// 4
// 5
// 6

问题来了,为什么会出现以上的结果?

这个主要是 Koa.js 的一个中间件引擎 koa-compose 模块来实现的,也就是 Koa.js 实现洋葱模型的核心~~

从洋葱模型可以看出来(怎么看?就是从左到右穿过去.. 先碰到最外圈.. 最后也是碰到的最外圈),中间件在 await next() 前后的操作,很像数据结构-----“栈”,先进后出,符合洋葱模型。同时,在上面的代码中还有 ctx,这个是就是上下文管理操作数据了(哎,写详细点吧,这个上下文就是来保持两个函数之间传递的时候的一些参数状态吧)。于是乎我们可以总结出如果要实现洋葱模型需要具有的几点特性。

① 需要有统一的 上下文 ctx

② 操作先进后出

③ 有控制先进后出的机制 next

④ 有提前结束的机制

知道了这几个特性,可以自己单纯的用 Promise 做个实现。

// 定义一个上下文
let context = {
    data: []
}; 

// 定义一个中间件 middleware1
async function middleware1( ctx, next ){
    console.log(‘action 001‘);
    ctx.data.push(1);
    await next();
    console.log(‘action 006‘);
    ctx.data.push(6);
}

// 定义一个中间件 middleware2
async function middleware2(ctx, next) {
  console.log(‘action 002‘);
  ctx.data.push(2);
  await next();
  console.log(‘action 005‘);
  ctx.data.push(5);
}

// 定义一个中间件 middleware3
async function middleware3(ctx, next) {
  console.log(‘action 003‘);
  ctx.data.push(3);
  await next();
  console.log(‘action 004‘);
  ctx.data.push(4);
}

Promise.resolve(middleware1(context, async() => {
  return Promise.resolve(middleware2(context, async() => {
    return Promise.resolve(middleware3(context, async() => {
      return Promise.resolve();
    }));
  }));
})).then(() => {
    console.log(‘end‘);
    console.log(‘context = ‘, context);
  });

// 结果显示
// "action 001"
// "action 002"
// "action 003"
// "action 004"
// "action 005"
// "action 006"
// "end"
// "context = { data: [1, 2, 3, 4, 5, 6]}"

然而,虽然单纯用 Promise 嵌套 可以直接实现中间件流程,但是这样会产生代码可读性和可维护性的问题,也带来了中间件扩展的问题。所以,这需要把 Promise 嵌套 实现的中间件方式进行高度抽象,达到可以自定义中间件的层数。这时候就需要借助神器 async/await 。

首先理清一下需要的步骤:

  • 中间件队列
  • 处理中间队列,并将上下文传进去
  • 中间件的流程控制器 next
  • 异常的处理

根据上面中间件分析的原理,可以抽象出

  • 每一个中间件需要封装一个 Promise
  • 洋葱模型的先进后出操作应该要对应 Promise.resolve 的前后操作
const compose = require(‘./index‘);
// 中间件数组
let middleware = [];
// 上下文
let context = {
  data: []
};
// 添加第一个中间件
middleware.push(async(ctx, next) => {
  console.log(‘action 001‘);
  ctx.data.push(2);
  await next();
  console.log(‘action 006‘);
  ctx.data.push(5);
});
// 添加第二个中间件
middleware.push(async(ctx, next) => {
  console.log(‘action 002‘);
  ctx.data.push(2);
  await next();
  console.log(‘action 005‘);
  ctx.data.push(5);
});
// 添加第三个中间件
middleware.push(async(ctx, next) => {
  console.log(‘action 003‘);
  ctx.data.push(2);
  await next();
  console.log(‘action 004‘);
  ctx.data.push(5);
});

// 得到一个函数,相当于是一个中间件的链吧
const fn = compose(middleware);

// 执行fn函数,传入上下文
fn(context)
  .then(() => {
    console.log(‘end‘);
    console.log(‘context = ‘, context);
  });

以下是compose

module.exports = compose;

// 放入中间件
function compose(middleware) {
  //判断是不是数组
  if (!Array.isArray(middleware)) {
    throw new TypeError(‘Middleware stack must be an array!‘);
  }

  // 返回一个方法
  return function(ctx, next) {
    let index = -1;

    // 按我的理解是返回一条 中间件的链~
    return dispatch(0);

    function dispatch(i) {
      // 防止重复调用
      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();
      }

      // 这个就是拼装一个 Promise 的链子吧
      try {
        return Promise.resolve(fn(ctx, () => {
          return dispatch(i + 1);
        }));
      } catch (err) {
        return Promise.reject(err);
      }
    }
  };
}

至此原理已经看透透,其实就是一个 诺言 的链子~ 来进行的中间件调用~

还未讲完~~~ 后文待续~~~~

ε=(´ο`*)))唉

借鉴原文 https://chenshenhai.github.io/koajs-design-note/note/chapter01/05.html

原文地址:https://www.cnblogs.com/cxz520/p/11773350.html

时间: 2024-10-28 04:17:46

koa入坑及其中间件原理(1)的相关文章

Koa框架实践与中间件原理剖析

Koa框架实践与中间件原理剖析 最近尝试用了一下Koa,并在此记录一下使用心得. 注意:本文是以读者已经了解Generator和Promise为前提在写的,因为单单Generator和Promise都能够写一篇博文来讲解介绍了,所以就不在这里赘述.网上资料很多,可以自行查阅. Koa是Express原班人马打造的一个更小,基于nodejs平台的下一代web开发框架.Koa的精妙之处就在于其使用generator和promise,实现了一种更为有趣的中间件系统,Koa的中间件是一系列generat

web前端入坑第五篇:秒懂Vuejs、Angular、React原理和前端发展历史

秒懂Vuejs.Angular.React原理和前端发展历史 2017-04-07 小北哥哥 前端你别闹 今天来说说 "前端发展历史和框架" 「前端程序发展的历史」 「 不学自知,不问自晓,古今行事,未之有也 」 我们都知道如今流行的框架:Vue.Js.AngularJs.ReactJs.已经逐渐应用到各个项目和实际应用中,它们都是MVVM数据驱动框架系列的一种. 在了解MVVM之前,我们先回想一下前端发展的历史阶段,做到心中有数,才会更好理解. 这段回想历史.由于网上就可查不少资料,

vue2.x入坑总结—回顾对比angularJS/React的一统

从感性的角度讲,我是不屑于用VUE,觉得react套件用起来更顺手,但是vue现在越来火,所以也不得入vue(杂烩汤)的坑.vue/anguarJS/React,三者对关系现在就是: https://www.zhoulujun.cn/uploadfile/images/2018/0626/20180626214906428779269.jpg 自己ps了下,觉得深有道理,骚年们自己体悟,然后再问军哥^_^ 不过回归真题,看vue还是先了解下https://cdn.zhoulujun.cn/vue

vue2.x入坑总结—回顾对比angularJS/React

从感性的角度讲,我是不屑于用VUE,觉得react套件用起来更顺手,但是vue现在越来火,所以也不得入vue(杂烩汤)的坑.vue/anguarJS/React,三者对关系现在就是: https://www.zhoulujun.cn/uploadfile/images/2018/0626/20180626214906428779269.jpg 自己ps了下,觉得深有道理,骚年们自己体悟,然后再问军哥^_^ 不过回归真题,看vue还是先了解下https://cdn.zhoulujun.cn/vue

web前端入坑第四篇:你还在用 jQuery?

web前端入坑第四篇:你还在用 jQuery? 大妈都这么努力,我们有几个人回家还看书的? 先来补齐[web前端入坑系列]前三篇的连接web前端入坑系列:点击标题进入第一篇: web 前端入坑第一篇:web前端到底是什么?有前途吗第二篇: web前端入坑第二篇:web前端到底怎么学?干货资料!第三篇:web前端入坑第三篇 | 一条"不归路" - 学习路线! 再说这个话题之前,我们先来扫盲普及一下 [jquery] 到底是什么以及它火爆将近十年的重要原因. [ 重新认识 - Jquery

Kotlin快速入坑指南(干货型文档)

<p style="text-align:center;color:#42A5F5;font-size:2em;font-weight: bold;">前言 即使每天10点下班,其实需求很多,我也要用这腐朽的声带喊出:我要学习,我要写文章!! 又是一篇Kotlin的文章,为啥...还不是因为工作需要.毫无疑问,最好的学习方式是通过官方文档去学习.不过个人觉得官方文档多多少少有一些不够高效. 中文官方文档 因此这篇是从我学习的个人视角以文档的形式去输出Kotlin语言基础的学

Spring Cloud Gateway入坑记

Spring Cloud Gateway入坑记 前提 最近在做老系统的重构,重构完成后新系统中需要引入一个网关服务,作为新系统和老系统接口的适配和代理.之前,很多网关应用使用的是Spring-Cloud-Netfilx基于Zuul1.x版本实现的那套方案,但是鉴于Zuul1.x已经停止迭代,它使用的是比较传统的阻塞(B)IO + 多线程的实现方案,其实性能不太好.后来Spring团队干脆自己重新研发了一套网关组件,这个就是本次要调研的Spring-Cloud-Gateway. 简介 Spring

弃坑pexpect,入坑paramiko

弃坑pexpect,入坑paramiko 上文书说到,ssh库pexpect的使用,简直就是个"月亮公主"--满眼全是坑.勉强把程序写好了,跑起来的时候发现了一个新坑,让我不可抗拒的把它弃掉了--经常莫名其妙的连不上服务器!开线程连接14台服务器,总有1到3台连不上,还查不到原因.这还了得!一怒之下把写好的pexpect封装库删掉了,用paramiko重新写起.其实这个库也是有一些坑的,这个放在后面说.先介绍一下这个库的用法. 安装方法:没有什么新鲜的 pip install para

py3学习笔记0(入坑)

什么是Python? Python是一种面向对象.解释型计算机程序设计语言,语法简洁,具有很多强大的库.它也被称为胶水语言,能够把其他语言制作的库轻松地粘合在一起.现常用于科学计算,界面设计,网络通信等. 它优雅,明确,简单,将“用一种方法,最好是只有一种方法来做一件事”的优雅哲学贯穿始终. 当然关键还是它比较好玩,就抱着玩一玩的心态入坑看看咯. 初期学的是py2,主要写写小游戏,爬虫什么的. 在学校很久没有编程了,平时还是要写写代码练练手. 那就开始学py3吧!虽然都差不多,但周围有好多人都想