Hook 模型

首先介绍一下 Modern.js 的基础的插件系统中的一些内容,包括 Hook 模型的工作方式、各个 Hook 模型的运行模式、Manager 的工作模式。

每种 Hook 模型都是独立的,可以独立管理运行函数。

基础工作方式

先以 Pipeline 为例,简单介绍一下 Hook 模型的工作方式。先看一个简单的例子:

import { createPipeline } from '@modern-js/plugin';

// 1. 创建
const pipeline = createPipeline<number, number>();

// 2. 添加函数
pipeline.use((count, next) => {
  return next(count + 1);
});
pipeline.use((count, next) => {
  return count * 2;
});

// 3. 执行
pipeline.run(1); // 4
pipeline.run(5); // 12

在这个例子中,创建了一个 Pipeline<number, number> 类型的 Pipeline(L3),这意味着运行它的时候,你需要传入一个 number,然后你会得到一个 number,而这个模型管理的函数的类型是:

(count: number, next: (nextCount: number) => number) => number;

这里全是 number,是因为我们创建的是 Pipeline<number, number> ,如果创建的是 Pipeline<number, string> 则运行它入参是 number,返回值是 string,对应管理的函数的类型会是:

(count: number, next: (nextCount: number) => string) => string;

创建好 Pipeline 之后,可以通过 use 添加函数(L5、L8),需要注意的是,添加的顺序就是他们默认的运行顺序,在这些函数中,你可以对 count 进行处理、返回一个值,如果你调用了 next 函数,则会运行后面的函数,即如果你添加了三个函数: ABC,如果你在 A 中调用 next 那么就会运行 B,同样的,如果你在 B 中调用 next 那么就会运行 C,而在上面的例子中,添加的第一个函数(L5)就运行了 next,所以这里就会运行第二个函数(L8),并且运行的返回值就是 第二个函数的返回值,如果在第一个函数中没有调用 next,直接返回,例如:

import { createPipeline } from '@modern-js/plugin';

// 1. 创建
const pipeline = createPipeline<number, number>();

// 2. 添加函数
pipeline.use((count, next) => {
  return count + 1;
});
pipeline.use((count, next) => {
  return count * 2;
});

// 3. 执行
pipeline.run(1); // 2
pipeline.run(5); // 6

则在运行 Pipeline 的时候就不会运行第二个函数,那么 Pipeline 的运行结果则就是第一个函数的返回值。

最后,运行 Pipeline 的方式也显而易见就是调用 pipeline.run

不同 Hook 模型的区别

上面这部分就是 Pipeline 整体的一个工作模式的描述,其他的 Hook 模型的工作模式基本也是这样,主要的区别点,是函数类型、执行顺序,参数。

Pipeline

上面的例子就是以 Pipeline 为例描述的,这里就不赘述了,在 Pipeline 这个大类中,提供了两个小类:Sync 和 Async,顾名思义,它们的区别就是管理的函数的类型是 Sync 的还是 Async 的。

INFO

当 Pipeline 中没有函数或者所有函数都调用了 next 函数,则就需要在运行的时候提供:

pipeline(
  {},
  {
    onLast: () => {
      // do something
    },
  },
);

Waterfall

这种模型顾名思义,他的特点就是参数的顺序递交,即前面一个函数的返回值,将会成为下一个函数的入参,我们也用一个例子来看一下:

import { createWaterfall } from '@modern-js/plugin';

// 1. 创建
const waterfall = createWaterfall<number>();

// 2. 添加函数
waterfall.use(count => {
  return count + 1;
});
waterfall.use(count => {
  return count * 2;
});

// 3. 执行
waterfall.run(1); // 4
waterfall.run(5); // 12

这个例子中,创建了一个类型为 Waterfall<number> ,即这个模型执行的入参和返回值是一样的,这个例子中都是 number,而它管理的函数的类型是:

(count: number) => number;

可能简单看这个例子感觉和上面的 Pipeline 功能一样,那需要注意的是,首先这里 Waterfall 管理的函数没有 next 函数作为第二个参数,所以它无法在函数内部通过调用 next 来先运行之后添加的函数,从而修改运行的顺序,其次这里的运行的入参的类型和返回值的类型必须是一样的(而 Pipeline 可以不一样)。

同样的,在 Waterfall 这个大类中,也提供了 Sync 和 Async 的小类,分别对应 Sync 和 Async 的函数。

Workflow

这种 Hook 模型与上面两种 Hook 模型的区别是,没有那么强的前后参数返回值递交的概念,在这个模型中,每个函数都是基于同样的入参,相对独立运行的,通过一个例子简单看一下:

import { createWorkflow } from '@modern-js/plugin';

// 1. 创建
const workflow = createWorkflow<number, number>();

// 2. 添加函数
workflow.use(count => {
  return count + 1;
});
workflow.use(count => {
  return count * 2;
});

// 3. 执行
workflow.run(1); // [2, 2]
workflow.run(5); // [6, 10]

在这个例子中,添加了两个函数,所以运行的结果就是这两个函数运行的结果形成的一个数组。

虽然这种模型中没有那么强的前后参数返回值递交的概念,但依旧有执行顺序的区别,在 Workflow 这个大类中,提供了三个小类:Sync、Async、Parallel。他们之间的区别就是函数的执行顺序,当然默认的都是按照添加顺序执行,而在 Sync、Async 则是强制按照添加顺序执行,而 Parallel 则是 Async 模式的一个变体,即它使用的是 Promise.all 来执行所有函数,而 Async 则会 await 前面的函数运行结束。

Hook 模型对比

函数类型 执行顺序 函数参数来源 执行返回值来源 倾向处理的任务类型 函数 TS 类型
Pipeline Sync 默认执行第一个添加的函数,可以通过 next 调用之后添加的函数 第一个函数的参数来源是运行的参数,之后的函数的参数来源是,前一个函数向 next 函数传递的参数 第一个函数的返回值
  • 需要修改初始参数
  • 需要修改函数执行顺序
(input: I, next: Next<I, O>) => O
AsyncPipeline Sync/Async 默认执行第一个添加的函数,可以通过 next 调用之后添加的函数 第一个函数的参数来源是运行的参数,之后的函数的参数来源是,前一个函数向 next 函数传递的参数 第一个函数的返回值
  • 需要修改初始参数
  • 需要修改函数执行顺序
(input: I, next: AsyncNext<I, O>) => O | Promise<O>
WaterFall Sync 一直顺序执行 第一个函数的参数来源是运行的参数,之后的函数的参数来源是,前一个函数的返回值 最后一个函数的返回值
  • 需要修改初始参数
  • 不需要修改函数执行顺序
(I: I) => I
AsyncWaterFall Sync/Async 一直顺序执行 第一个函数的参数来源是运行的参数,之后的函数的参数来源是,前一个函数的返回值 最后一个函数的返回值
  • 需要修改初始参数
  • 不需要修改函数执行顺序
(I: I) => I | Promise<I>
Workflow Sync 一直顺序执行 所有函数的入参都是运行的参数 所有函数返回值形成的数组
  • 不需要修改初始参数
  • 不需要修改函数执行顺序
(I: I) => O
AsyncWorkflow Sync/Async 一直顺序执行 所有函数的入参都是运行的参数 所有函数返回值形成的数组
  • 不需要修改初始参数
  • 不需要修改函数执行顺序
(I: I) => O | Promise<O>
ParallelWorkFlow Sync/Async 异步执行 所有函数的入参都是运行的参数 所有函数返回值形成的数组
  • 不需要修改初始参数
  • 不关心执行顺序
(I: I) => O | Promise<O>

Workflow、Waterfall 其实都是 Pipeline 的变体,Pipeline 可以通过特定的写法来实现 Workflow、Waterfall,但都较为麻烦,有许多隐形的约定。为了方便使用,提供了这两种变体来满足这种特殊场景。