洋葱模型 koa-compose源码解析

目录
  • 洋葱模型
  • 源码
  • 动手
  • 总结

洋葱模型

koa-compose是一个非常简单的函数,它接受一个中间件数组,返回一个函数,这个函数就是一个洋葱模型的核心。

源码地址:github.com/koajs/compo…

网上一搜一大把图,我就不贴图了,代码也不上,因为等会源码就是,这里只是介绍一下概念。

洋葱模型是一个非常简单的概念,它的核心是一个函数,这个函数接受一个函数数组,返回一个函数,这个函数就是洋葱模型的核心。

这个返回的函数就是聚合了所有中间件的函数,它的执行顺序是从外到内,从内到外。

例如:

  • 传入一个中间件数组,数组中有三个中间件,分别是abc
  • 返回的函数执行时,会先执行a,然后执行b,最后执行c
  • 执行完c后,会从内到外依次执行ba
  • 执行完a后,返回执行结果。

这样说的可能还是不太清楚,来看一下流程图:

这里是两步操作,第一步是传入中间件数组,第二步是执行返回的函数,而我们今天要解析就是第一步。

源码

源码并不多,只有不到 50 行,我们来看一下:

'use strict'
/**
 * Expose compositor.
 */
module.exports = 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) {
    // last called middleware #
    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()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

虽然说不到 50 行,其实注释都快占了一半,直接看源码,先看两个部分,第一个导出,第二个是返回的函数。

module.exports = compose
function compose (middleware) {
  // ...
  return function (context, next) {
      return dispatch(0)
      function dispatch (i) {
        // ...
      }
  }
}

这里确实是第一次见这样玩变量提升的,所以先给大家讲一下变量提升的规则:

  • 变量提升是在函数执行前,函数内部的变量和函数声明会被提升到函数顶部。
  • 变量提升只会提升变量声明,不会提升赋值。
  • 函数提升会提升函数声明和函数表达式。
  • 函数提升会把函数声明提升到函数顶部,函数表达式会被提升到变量声明的位置。

这里的module.exports = compose是变量提升,function compose是函数提升,所以compose函数会被提升到module.exports之前。

下面的return dispatch(0)是函数内部的变量提升,dispatch函数会被提升到return之前。

虽然这样可行,但是不建议这样写,因为这样写会让代码变得难以阅读,不多说了,继续吧:

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!')
    }
    // ...
}

最开始就是洋葱模型的要求判断了,中间件必须是数组,数组里面的每一项必须是函数。

继续看:

return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    // ...
}

这个函数是返回的函数,这个函数接收两个参数,contextnextcontext是上下文,next是下一个中间件,这里的nextcompose函数的第二个参数,也就是app.callback()的第二个参数。

index注释写的很清楚,是最后一个调用的中间件的索引,这里初始化为-1,因为数组的索引是从0开始的。

dispatch函数是用来执行中间件的,这里传入0,也就是从第一个中间件开始执行。

function dispatch (i) {
    if (i <= index) return Promise.reject(new Error('next() called multiple times'))
    index = i
}

可以看到,dispatch函数接收一个参数,这个参数是中间件的索引,这里的i就是dispatch(0)传入的0

这里的判断是为了防止next被调用多次,如果i小于等于index,就会抛出一个错误,这里的index-1,所以这个判断是不会执行的。

后面就赋值了indexi,这样就可以防止next被调用多次了,继续看:

let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()

这里的fn是中间件,也是当前要执行的中间件,通过索引直接从最开始初始化的middleware数组里面取出来。

如果是到了最后一个中间件,这里的next指的是下一个中间件,也就是app.callback()的第二个参数。

如果fn不存在,就返回一个成功的Promise,表示所有的中间件都执行完了。

继续看:

try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
    return Promise.reject(err)
}

这里就是执行中间件的地方了,fn是刚才取到的中间件,直接执行。

然后传入contextdispatch.bind(null, i + 1),这里的dispatch.bind(null, i + 1)就是next,也就是下一个中间件。

这里就有点递归的感觉了,但是并没有直接调用,而是通过外部手动调用next来执行下一个中间件。

这里的try...catch是为了捕获中间件执行过程中的错误,如果有错误,就返回一个失败的Promise

动手

老规矩,还是用class来实现一下这个compose函数。

class Compose {
    constructor(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!')
        }
        this.index = -1
        this.middleware = middleware
        return (next) => {
            this.next = next
            return this.dispatch(0)
        }
    }
    dispatch(i) {
        if (i <= this.index) return Promise.reject(new Error('next() called multiple times'))
        this.index = i
        let fn = this.middleware[i]
        if (i === this.middleware.length) fn = this.next
        if (!fn) return Promise.resolve()
        try {
            return Promise.resolve(fn(this.dispatch.bind(this, i + 1)))
        } catch (err) {
            return Promise.reject(err)
        }
    }
}
var middleware = [
    (next) => {
        console.log(1)
        next()
        console.log(2)
    },
    (next) => {
        console.log(3)
        next()
        console.log(4)
    },
    (next) => {
        console.log(5)
        next()
        console.log(6)
    }
]
var compose = new Compose(middleware)
compose()
var middleware = [
    (next) => {
        return next().then((res) => {
            return res + '1'
        })
    },
    (next) => {
        return next().then((res) => {
            return res + '2'
        })
    },
    (next) => {
        return next().then((res) => {
            return res + '3'
        })
    }
]
var compose = new Compose(middleware)
compose(() => {
    return Promise.resolve('0')
}).then((res) => {
    console.log(res)
})

这次不放执行结果的截图了,可以直接浏览器控制台中自行执行。

总结

koa-compose的实现原理就是通过递归来实现的,每次执行中间件的时候,都会返回一个成功的Promise

其实这里不使用Promise也是可以的,但是使用Promise可以有效的处理异步和错误。

而且从上面手动实现的代码案例中也可以看到,使用Promise可以有更多的灵活性,写法也是多元化。

以上就是洋葱模型 koa-compose源码解析的详细内容,更多关于洋葱模型 koa-compose的资料请关注我们其它相关文章!

(0)

相关推荐

  • 傻瓜式解读koa中间件处理模块koa-compose的使用

    最近需要单独使用到koa-compose这个模块,虽然使用koa的时候大致知道中间件的执行流程,但是没仔细研究过源码用起来还是不放心(主要是这个模块代码少,多的话也没兴趣去研究了). koa-compose看起来代码少,但是确实绕.闭包,递归,Promise...看了一遍脑子里绕不清楚.看了网上几篇解读文章,都是针对单行代码做解释,还是绕不清楚.最后只好采取一种傻瓜的方式: koa-compose去掉一些注释,类型校验后,源码如下: function compose (middleware) {

  • 详解JavaScript执行模型

    JavaScript执行模型 引言 JavaScript是一个单线程(Single-threaded)异步(Asynchronous)非阻塞(Non-blocking)并发(Concurrent)语言,这些语言效果通过一个调用栈(Call Stack).一个事件循环(Event Loop).一个回调队列(Callback Queue)有些时候也叫任务队列(Task Queue)与跟运行环境相关的API组成. 概念 调用栈 Call Stack 调用栈是一个LIFO后进先出数据结构的函数运行栈,它

  • koa中间件核心(koa-compose)源码解读分析

    最近经常使用koa进行服务端开发,迷恋上了koa的洋葱模型,觉得这玩意太好用了.而且koa是以精简为主,没有很多集成东西,所有的东西都需按需加载,这个更是太合我胃口了哈哈哈哈. 相对与express的中间件,express的中间件使用的是串联,就像冰糖葫芦一样一个接着一个,而koa使用的V型结构(洋葱模型),这将给我们的中间件提供更加灵活的处理方式. 基于对洋葱模型的热衷,所以对koa的洋葱模型进行一探究竟,不管是koa1还是koa2的中间件都是基于koa-compose进行编写的,这种V型结构

  • 解决threeJS加载obj gltf模型后颜色太暗的方法

    目录 网上找到的部分解决方法 效果对比 总结 网上找到的部分解决方法 其实通过查找后不难发现网上给出了很多解决方法,但是大部分都无法从根本上解决问题.我之前看到有一篇文章对gltf的解决方法是让gltf增加自发光,相关的设置如下: 使用threeJS的过程中,刚开始总是会遇到些问题,就比如加载obj/gltf等带材质的模型时老是会出现显示效果较暗的问题. object.traverse((child) => { if(child.isMesh) { child.material.emissive

  • JavaScript 设计模式之洋葱模型原理及实践应用

    目录 前言 洋葱模型 实践 总结 前言 先来听听一个故事吧,今天产品提了一个业务需求:用户在一个编辑页面,此时用户点击退出登录,应用需要提示用户当前有编辑内容未保存,是否保存:当用户操作完毕后再提示用户是否退出登录. 流程如下: 因为退出登录是属于公共部分由另一位同学维护,此时和他交流后“善良”的把需求仍给了他.并告知他可以通过某某方法获取我当前是否有编辑内容.然后我继续摸鱼,他开始疯狂输出 const handlerLogout = async () => { if (window.locat

  • 详解JS浏览器事件模型

    什么是事件 我想你很可能听说过事件驱动, 但是事件驱动到底是什么?为什么说浏览器是事件驱动的呢? 事件驱动通俗地来说就是什么都抽象为事件. 一次点击是一个事件 键盘按下是一个事件 一个网络请求成功是一个事件 页面加载是一个事件 页面报错是一个事件 浏览器依靠事件来驱动APP运行下去,如果没有了事件驱动,那么APP会直接从头到尾运行完,然后结束,事件驱动是浏览器的基石. 一个简单的例子 其实现实中的红绿灯就是一种事件,它告诉我们现在是红灯状态,绿灯状态,还是黄灯状态. 我们需要根据这个事件自己去完

  • 洋葱模型 koa-compose源码解析

    目录 洋葱模型 源码 动手 总结 洋葱模型 koa-compose是一个非常简单的函数,它接受一个中间件数组,返回一个函数,这个函数就是一个洋葱模型的核心. 源码地址:github.com/koajs/compo… 网上一搜一大把图,我就不贴图了,代码也不上,因为等会源码就是,这里只是介绍一下概念. 洋葱模型是一个非常简单的概念,它的核心是一个函数,这个函数接受一个函数数组,返回一个函数,这个函数就是洋葱模型的核心. 这个返回的函数就是聚合了所有中间件的函数,它的执行顺序是从外到内,从内到外.

  • Laravel框架源码解析之模型Model原理与用法解析

    本文实例讲述了Laravel框架源码解析之模型Model原理与用法.分享给大家供大家参考,具体如下: 前言 提前预祝猿人们国庆快乐,吃好.喝好.玩好,我会在电视上看着你们. 根据单一责任开发原则来讲,在laravel的开发过程中每个表都应建立一个model对外服务和调用.类似于这样 namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { protected $table =

  • Node.js 网络框架koa compose中间件使用解析

    目录 前言 koa-compose 洋葱模型 源码解析 总结 前言 学习目标: koa-compose 洋葱模型 源码地址:koajs/compose koa-compose Koa-compose 是一个 Koa 中间件工具,Koa 是一个流行的 Node.js 网络框架.Koa-compose 允许你将多个中间件函数组合成一个单独的函数,这样可以更容易地管理和重用中间件. 在 Koa 中,中间件函数是按照特定顺序调用的函数,用于处理传入的 HTTP 请求并生成响应.中间件函数可以执行各种任务

  • Android源码解析之截屏事件流程

    今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程.用过android系统手机的同学应该都知道,一般的android手机按下音量减少键和电源按键就会触发截屏事件(国内定制机做个修改的这里就不做考虑了).那么这里的截屏事件是如何触发的呢?触发之后android系统是如何实现截屏操作的呢?带着这两个问题,开始我们的源码阅读流程. 我们知道这里的截屏事件是通过我们的按键操作触发的,所以这里就需要我们从android系统的按键触发模块开始看起,由于我们在不同的App页面,操作音量减少键和电

  • CountDownLatch源码解析之await()

    CountDownLatch 源码解析-- await(),具体内容如下 上一篇文章说了一下CountDownLatch的使用方法.这篇文章就从源码层面说一下await() 的原理. 我们已经知道await 能够让当前线程处于阻塞状态,直到锁存器计数为零(或者线程中断). 下面是它的源码. end.await(); ↓ public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } s

  • CountDownLatch源码解析之countDown()

    CountDownLatch 源码解析-- countDown() 上一篇文章从源码层面说了一下CountDownLatch 中 await() 的原理.这篇文章说一下countDown() . public void countDown() { //CountDownLatch sync.releaseShared(1); } ↓ public final boolean releaseShared(int arg) { //AQS if (tryReleaseShared(arg)) { d

  • python wsgiref源码解析

    python web开发中http请求的处理流程通常是: web-browser , web-server , wsgi 和 web-application四个环节, 我们学习过基于bottle实现的web-application,也学习了http.server.再完成python3源码中自带的wsgiref的库,就可以拼接最后一个环节wsgi.本文会分下面几个部分: wsgi相关概念 cgi示例 wsgiref源码 wsgi小结 小技巧 wsgi 相关概念 CGI CGI(Common Gat

  • Android Handler,Message,MessageQueue,Loper源码解析详解

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文< Android中Handler的使用>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功能.但

  • Tomcat的类加载机制流程及源码解析

    目录 前言 1.Tomcat 的类加载器结构图: 2.Tomcat 的类加载流程说明: 3.源码解析: 4.为什么tomcat要实现自己的类加载机制: 前言 在前面 Java虚拟机:对象创建过程与类加载机制.双亲委派模型 文章中,我们介绍了 JVM 的类加载机制以及双亲委派模型,双亲委派模型的类加载过程主要分为以下几个步骤: (1)初始化 ClassLoader 时需要指定自己的 parent 是谁 (2)先检查类是否已经被加载过,如果类已经被加载了,直接返回 (3)若没有加载则调用父加载器 p

  • Vite的createServer启动源码解析

    目录 启动Vite的createServer 通过vite3安装一个vue的工程 添加断点并开启调试 边调试边理解代码 启动Vite的createServer 为了能够了解vite里面运行了什么,通过执行单步调试能够更加直观的知道Vite具体内容.所以这次我们来试着启动Vite的createServer,并进行调试. 通过vite3安装一个vue的工程 进入工作目录,运行下面的代码,项目名称随意,语言用Vue. npm create vite 进入工程目录安装依赖 添加断点并开启调试 通过vsc

随机推荐