从Yargs源码学习中间件的设计

news2024/11/15 4:25:24
alt

yargs中间件介绍

yargs 是一个用于解析命令行参数的流行库,它能帮助开发者轻松地定义 CLI(命令行接口),并提供参数处理、命令组织、help文本自动生成等功能。今天我们来学习一下它对中间件的支持。

中间件的API详细信息,可以查看这里:https://yargs.js.org/docs/#api-reference-middlewarecallbacks-applybeforevalidation

在 yargs 中,中间件(Middleware)是一种用于在命令行参数解析过程中,插入自定义逻辑的机制。中间件能够在参数验证之前或之后执行,允许开发者对 argv 对象进行操作或修改。通过中间件,开发者可以对命令行输入进行预处理、验证、转化,或者根据业务需求添加自定义操作。

其用途主要是这三个:

  1. 参数的预处理:中间件可以在命令行参数验证之前执行,处理或修改 argv,比如对输入进行格式化。
  2. 参数验证后的处理:中间件也可以在参数验证之后执行,用于进一步处理解析后的数据或生成特定的输出。
  3. 全局中间件:中间件可以作用于所有的命令或选项,称为全局中间件。通过全局中间件,可以对所有命令的输入进行统一处理。

全局中间件

我们先看看api的介绍

.middleware(callbacks, [applyBeforeValidation])

  • callbacks:可以是一个函数或函数列表。每个回调函数都会接收一个对 argv 的引用,argv 是一个包含命令行参数的对象。
  • applyBeforeValidation(可选):布尔值,默认为 false。如果设置为 true,则中间件将在验证之前执行,但在解析之后。

从这里可以看出,其可以定义单个或者多个中间件,也可以定义执行顺序。

下面来看几个例子:

const mwFunc1 = argv => console.log('I\'m a middleware function');
const mwFunc2 = argv => console.log('
I\'m another middleware function');

yargs
  .command('myCommand''some command', {}, function(argv){
    console.log('Running myCommand!');
  })
  .middleware([mwFunc1, mwFunc2]).parse();

在这个例子中,当从命令行调用 myCommand 时,mwFunc1 首先被调用,然后是 mwFunc2,最后是命令的处理函数。控制台的输出将是:

I'm a middleware function
I'
m another middleware function
Running myCommand!
require('yargs/yargs')(process.argv.slice(2))
  .middleware(function (argv) {
    if (process.env.HOME) argv.home = process.env.HOME
  }, true)
  .command('configure-home'"do something with a user's home directory",
    {
      'home': {
        'demand'true,
        'string'true
      }
    },
    function(argv) {
      console.info(`we know the user's home directory is ${argv.home}`)
    }
  )
  .parse()

在这个例子中,中间件用于从环境变量中填充 home 目录。由于中间件会接受一个形参argv,所以其也可以对该参数做二次修改。

command中间件

command中间件只对当前command生效,其会强制把applyBeforeValidation参数设置为false。其接口形式如下:

.command(cmd, desc, [builder], [handler])

command中间件只会在command运行的时候执行,所以它将晚于全局中间件执行。

require('yargs')
  .command('$0''accept username', () => {}, (argv) => {
    // The middleware will have been applied before the default
    // command is called:
    console.info(argv);
  })
  .choices('user', ['Goofy''Miky'])
  .middleware(argv => {
    console.info('gots here');
    const user = argv.user;
    switch (user) {
      case 'Goofy':
        argv.user = {
          firstName: 'Mark',
          lastName: 'Pipe',
        };
        break;
    }
    return argv;
  })
  .parse('--user Miky');

如何实现中间件

前面介绍了两种不同的中间件,那其内部是如何实现的呢?其内部主要依赖middleware.ts来处理全局中间件的添加、应用和管理。它定义了全局中间件的类和相关函数,并提供了工具来处理在命令行解析过程中的中间件逻辑。

https://github.com/yargs/yargs/blob/main/lib/middleware.ts

GlobalMiddleware 类

GlobalMiddleware 是一个管理全局中间件的类,它存储中间件并提供相关操作,如添加、冻结、解冻和重置中间件。

  • globalMiddleware: Middleware[] = []存储所有注册的中间件。
  • frozens: Array<Middleware[]> = []用于存储冻结状态下的中间件组,支持回滚到之前的中间件配置。

构造函数

constructor(yargs: YargsInstance) {
  this.yargs = yargs;
}

构造函数接受一个 YargsInstance 对象(即 yargs 实例),用于后续调用命令行解析逻辑。

addMiddleware 方法

addMiddleware(
  callback: MiddlewareCallback | MiddlewareCallback[],
  applyBeforeValidation: boolean,
  global = true,
  mutates = false
): YargsInstance {
  • 功能:该方法允许添加单个或多个中间件。
  • ** callback**:接受中间件函数或中间件数组。
  • ** applyBeforeValidation**:标记中间件是否应该在命令行参数验证之前应用。
  • ** global**:标识中间件是否为全局作用域。
  • ** mutates**:标识中间件是否会修改 argv

通过该方法添加的中间件会被存储在 globalMiddleware 数组中。

addCoerceMiddleware 方法

addCoerceMiddleware(
  callback: MiddlewareCallback,
  option: string
): YargsInstance {
  • 功能:该方法专门用于处理 coerce 类型的中间件,每个选项只能注册一个 coerce 中间件。
  • 操作:先过滤掉之前注册的同一选项的 coerce 中间件,然后重新添加新的中间件。

freezeunfreeze 方法

freeze() {
  this.frozens.push([...this.globalMiddleware]);
}
unfreeze() {
  const frozen = this.frozens.pop();
  if (frozen !== undefinedthis.globalMiddleware = frozen;
}
  • ** freeze**:将当前的中间件快照保存到 frozens 数组中。
  • ** unfreeze**:从 frozens 中取出最后保存的快照,并恢复到 globalMiddleware 中。

reset 方法

reset() {
  this.globalMiddleware = this.globalMiddleware.filter(m => m.global);
}
  • 功能:重置中间件,仅保留全局中间件( global: true)。

工具方法:commandMiddlewareFactory

export function commandMiddlewareFactory(
  commandMiddleware?: MiddlewareCallback[]
): Middleware[] 
{
  • 功能:接受命令级中间件数组,并将 applyBeforeValidation 设置为 false,表示这些中间件默认在验证之后应用。

工具方法:applyMiddleware

export function applyMiddleware(
  argv: Arguments | Promise<Arguments>,
  yargs: YargsInstance,
  middlewares: Middleware[],
  beforeValidation: boolean
{
  return middlewares.reduce<Arguments | Promise<Arguments>>(
    (acc, middleware) => {
      if (middleware.applyBeforeValidation !== beforeValidation) {
        return acc;
      }

      if (middleware.mutates) {
        if (middleware.applied) return acc;
        middleware.applied = true;
      }

      if (isPromise(acc)) {
        return acc
          .then(initialObj =>
            Promise.all([initialObj, middleware(initialObj, yargs)])
          )
          .then(([initialObj, middlewareObj]) =>
            Object.assign(initialObj, middlewareObj)
          );
      } else {
        const result = middleware(acc, yargs);
        return isPromise(result)
          ? result.then(middlewareObj => Object.assign(acc, middlewareObj))
          : Object.assign(acc, result);
      }
    },
    argv
  );
}
  • 功能:应用所有匹配条件的中间件。
  • ** argv**:代表命令行参数对象,可能是普通对象也可能是 Promise
  • ** middlewares**:传入的中间件数组。
  • ** beforeValidation**:根据此标识决定是否只应用验证前的中间件。

此函数是核心逻辑,通过 reduce 迭代应用中间件,依次修改 argv 对象。如果 argv 或中间件返回值是 Promise,则将其转换为异步逻辑处理。

到这里,我们就了解了Yarg是如何实现中间件的了。

中间件知识的迁移

除了Yargs之外,Express、Koa等同样也拥有中间件,其实我们可以从他们身上总结出一套通用的中间件实现,在我们需要的时候,可以迁移到其它场景。

中间件的核心是一种可以在处理逻辑链中插入处理函数的技术。它能够接收输入、处理输入,并将输出传递给下一个中间件,或者返回最终结果。

所以,其是对流程的抽象,中间接负责承接流程中处理的差异,而把调用留给核心主流程。通过中间件的技术,我们可以实现如下几点:

  • 分离关注点:中间件允许将应用中的不同功能模块分开,使得每个模块只处理自己关心的部分,例如用户身份验证、错误异常处理等
  • 提高代码的可扩展性:通过中间件对外暴露处理函数,使得系统功能更易于扩展
  • 简化复杂逻辑:对于复杂的流程,我们可以拆解成多个简单的步骤,既增加了每个步骤的控制性,又简化了流程操作。例如,在处理 HTTP 请求时,可以拆解成:解析请求体 → 检查身份认证 → 处理权限 → 执行主要业务逻辑 → 格式化返回值 → 记录日志
  • 提高代码的可扩展性:中间件可以使系统功能更易于扩展。例如,在一个请求处理的生命周期中,添加一个新的功能只需要添加一个中间件。无需修改现有的逻辑,只需将新中间件插入到处理中间。

中间件的基本结构

function middleware(input, next{
  // 对 input 进行处理
  const result = process(input);
  
  // 调用下一个中间件
  return next(result);
}

通用结构包括:

  • 输入:通常是某种上下文对象(如 req/ resargv 等)。
  • 输出:经过处理后的结果,传递给下一个中间件。
  • ** next**:指向下一个中间件的函数或处理器。

中间件的注册与存储

为了灵活添加和管理中间件,通常需要将中间件存储在一个有序列表中,便于按顺序执行。

通用的中间件存储和注册方法:

class MiddlewareManager {
  constructor() {
    this.middlewares = [];
  }
  
  addMiddleware(middleware) {
    this.middlewares.push(middleware);
  }
  
  getMiddlewares() {
    return this.middlewares;
  }
}

中间件执行控制

在某些情况下,中间件需要有能力决定是否中止链的执行。这通常通过不调用 next 来实现。

通用模式:

function middleware(input, next{
  if (shouldStop(input)) {
    return input; // 不调用 next,中止链的执行
  }
  return next(input);
}

在 HTTP 请求处理中,可能会根据某些条件终止请求处理链并直接返回响应。同样地,在命令行工具中,某些条件下可以提前结束中间件链的执行。

中间件的管理与重置

中间件链可以根据业务需求进行管理、冻结、解冻和重置,这通常用在特定场景下修改中间件或者重置中间件的行为。

通用模式:

class MiddlewareManager {
  constructor() {
    this.middlewares = [];
    this.frozens = [];
  }

  freeze() {
    this.frozens.push([...this.middlewares]);
  }

  unfreeze() {
    const frozen = this.frozens.pop();
    if (frozen) this.middlewares = frozen;
  }

  reset() {
    this.middlewares = this.middlewares.filter(m => m.global);
  }
}

这种机制允许保存当前的中间件状态,并在需要时恢复。

如果需要灵活的配置,还可以给中间件附加上option的配置项。

执行中间件链

核心是按顺序调用中间件。可以通过 reduce 或递归的方式将中间件串联起来。每个中间件完成当前处理后,需要决定是否将处理权传递给下一个中间件。

通用的执行逻辑:

function executeMiddlewares(input, middlewares{
  let index = -1;
  
  function next(currentInput{
    index++;
    if (index < middlewares.length) {
      return middlewares[index](currentInput, next);
    }
    return currentInput; // 所有中间件处理完成后的结果
  }

  return next(input);
}

这里的 next 函数控制中间件的执行顺序,每次调用都会进入下一个中间件。

支持异步中间件

在实际应用中,很多中间件需要处理异步操作(如数据库查询、HTTP 请求等)。因此,中间件链需要支持异步操作。

通用的异步中间件支持:

async function executeAsyncMiddlewares(input, middlewares{
  let index = -1;

  async function next(currentInput{
    index++;
    if (index < middlewares.length) {
      const result = await middlewares[index](currentInput, next);
      return result;
    }
    return currentInput;
  }

  return next(input);
}

这可以确保异步中间件正确地等待 Promise 解决后再执行下一个中间件。

大白话总结一下,中间件就是管理一堆函数,并在特定的时候调用这些函数。

总结

yargs 的中间件为命令行工具的开发提供了极大的灵活性。通过中间件,开发者可以轻松地定制参数解析和处理的过程,适用于复杂的命令行应用场景。

我们也可以将中间件的思维迁移到我们的业务开发中,对于复杂的流程,做好模块拆分,就可以增加一个Middlewares来管理对应模块的处理函数,并在流程需要的时候调用他们。

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2156889.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python | Leetcode Python题解之第430题扁平化多级双向链表

题目&#xff1a; 题解&#xff1a; class Solution:def flatten(self, head: "Node") -> "Node":def dfs(node: "Node") -> "Node":cur node# 记录链表的最后一个节点last Nonewhile cur:nxt cur.next# 如果有子节点&#…

旋转机械故障数据集 全网首发

旋转机械故障 数据集 11G资料 泵、齿轮箱、电机、流量、液压系统、轴承(西储大学、辛辛那提大学、FEMTO、MOSFET)、PHM08挑战数据集、我闪发动机降级模拟数据集、铣床等 旋转机械故障数据集 数据集描述 该数据集是一个综合性的旋转机械故障检测和诊断数据集&#xff0c;旨在…

【ChatGPT】提示词助力广告文案、PPT制作与书籍推荐的高效新模式

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;高效广告推销文案提示词使用方法 &#x1f4af;AI自动生成PPT全流程提示词使用方法 &#x1f4af;精选书籍推荐爆款文案提示词使用方法 &#x1f4af;小结 &#x1f4af;…

【VUE3.0】动手做一套像素风的前端UI组件库---Radio

目录 引言做之前先仔细看看UI设计稿解读一下都有哪些元素&#xff1a;参考下成熟的组件库&#xff0c;看看还需要做什么&#xff1f; 代码编写1. 设计group包裹选项的组件group.vueitem.vue 2. 让group的v-model和item的value联动起来3. 完善一下item的指示器样式4. 补充禁用模…

【测试】——JUnit

&#x1f4d6; 前言&#xff1a;JUnit 是一个流行的 Java 测试框架&#xff0c;主要用于编写和运行单元测试&#xff0c;用来管理测试用例。本文采用JUnit 5 目录 &#x1f552; 1. 添加依赖&#x1f552; 2. 注解&#x1f558; 2.1 Test&#x1f558; 2.2 BeforeAll AfterAll&…

【Docker】基于docker compose部署artifactory-cpp-ce服务

基于docker compose部署artifactory-cpp-ce服务 1 环境准备2 必要文件创建与编写3 拉取镜像-创建容器并后台运行4 访问JFog Artifactory 服务 1 环境准备 docker 以及其插件docker compose &#xff0c;我使用的版本如下图所示&#xff1a; postgresql 的jdbc驱动, 我使用的是…

【图像检索】基于纹理(LBP)和形状特征的图像检索,matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于纹理(LBP)和形状特征&#xff08;hu特征&#xff09;的图像检索&#xff0c;用m…

力扣206.反转链表

力扣《反转链表》系列文章目录 刷题次序&#xff0c;由易到难&#xff0c;一次刷通&#xff01;&#xff01;&#xff01; 题目题解206. 反转链表反转链表的全部 题解192. 反转链表 II反转链表的指定段 题解224. 两两交换链表中的节点两个一组反转链表 题解325. K 个一组翻转…

【二等奖论文】2024年华为杯研究生数学建模E题成品论文获取入口

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片链接&#xff0c;那是获取资料的入口&#xff01; 点击链接获取【2024华为杯研赛资料汇总】&#xff1a; https://qm.qq.com/q/Wgk64ntZCihttps://qm.qq.com/q/Wgk64ntZCi 详细建模思路&#xff1a; 要解…

C++--模板(template)详解—— 函数模板与类模板

目录 1.泛型编程 2.函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 3.类模板 3.1 类模板的定义格式 3.2 类模板的实例化 1.泛型编程 在C中如果让你写一个交换函数&#xff0c;应该怎么做呢&#xff1f…

二叉树进阶【c++实现】【二叉搜索树的实现】

目录 二叉树进阶1.二叉搜索树1.1二叉搜索树的实现1.1.1二叉搜索树的查找1.1.2二叉搜索树的插入1.1.3中序遍历(排序)1.1.4二叉搜索树的删除(重点) 1.2二叉搜索树的应用1.2.1K模型1.2.2KV模型 1.3二叉搜索树的性能分析 二叉树进阶 前言&#xff1a; map和set特性需要先铺垫二叉搜…

Python3网络爬虫开发实战(16)分布式爬虫(第一版)

文章目录 一、分布式爬虫原理1.1 分布式爬虫架构1.2 维护爬取队列1.3 怎样来去重1.4 防止中断1.5 架构实现 二、Scrapy-Redis 源码解析2.1 获取源码2.2 爬取队列2.3 去重过滤2.4 调度器 三、Scrapy 分布式实现3.1 准备工作3.2 搭建 Redis 服务器3.3 部署代理池和 Cookies 池3.4…

超越sora,最新文生视频CogVideoX-5b模型分享

CogVideoX-5B是由智谱 AI 开源的一款先进的文本到视频生成模型&#xff0c;它是 CogVideoX 系列中的更大尺寸版本&#xff0c;旨在提供更高质量的视频生成效果。 CogVideoX-5B 采用了 3D 因果变分自编码器&#xff08;3D causal VAE&#xff09;技术&#xff0c;通过在空间和时…

【OpenAI o1背后技术】Sef-play RL:LLM通过博弈实现进化

【OpenAI o1背后技术】Sef-play RL&#xff1a;LLM通过博弈实现进化 OpenAI o1是经过强化学习训练来执行复杂推理任务的新型语言模型。特点就是&#xff0c;o1在回答之前会思考——它可以在响应用户之前产生一个很长的内部思维链。也就是该模型在作出反应之前&#xff0c;需要…

简单题104. 二叉树的最大深度 (python)20240922

问题描述&#xff1a; python&#xff1a; # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution(object…

Python 入门(一、使用 VSCode 开发 Python 环境搭建)

Python 入门第一课 &#xff0c;环境搭建...... by 矜辰所致前言 现在不会 Python &#xff0c;好像不那么合适&#xff0c;咱先不求精通&#xff0c;但也不能不会&#xff0c;话不多说&#xff0c;开干&#xff01; 这是 Python 入门第一课&#xff0c;当然是做好准备工作&a…

论前端框架的对比和选择 依据 前端框架的误区

前端框架的对比和选择依据 在前端开发中&#xff0c;有多种框架可供选择&#xff0c;以下是一些常见前端框架的对比和选择依据&#xff1a; 一、Vue.js 特点&#xff1a; 渐进式框架&#xff0c;灵活度高&#xff0c;可以逐步引入到项目中。学习曲线相对较平缓&#xff0c;容…

Java项目实战II基于Java+Spring Boot+MySQL的民宿在线预定平台(开发文档+源码+数据库)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在旅游市场…

强大的重命名工具 | Bulk Rename Utility v4.0 便携版

软件简介 Bulk Rename Utility是一款功能强大且易于使用的文件批量重命名工具。它不仅体积小巧&#xff0c;而且完全免费&#xff0c;提供了友好的用户界面。该软件允许用户对文件或文件夹进行批量重命名&#xff0c;支持递归操作&#xff0c;即包含子文件夹的重命名。 软件特…

Apache Iceberg 概述

Apache Iceberg概述 一、what is Apache Iceberg&#xff1f; 为了解决数据存储和计算引擎之间的适配的问题&#xff0c;Netflix开发了Iceberg&#xff0c;2018年11月16日进入Apache孵化器&#xff0c;2020 年5月19日从孵化器毕业&#xff0c;成为Apache的顶级项目。 Apache…