compose && pipe

compose 以及pipe

composepipe 是两种常见的函数组合方式,它们用于将多个函数组合成一个新的函数。这两种方式的主要区别在于函数的执行顺序和参数的传递方式

compose 函数通常将函数从右到左组合,也就是说,最右边的函数先执行,然后是紧挨着它的左边的函数,以此类推,直到所有函数都执行完毕。

1
const compose = (f, g) => x => f(g(x))

pipe 函数则通常将函数从左到右组合,这意味着最左边的函数先执行,然后依次执行后面的函数。

1
const pipe = (f, g) => x => g(f(x));

使用

pipe 管道偏向于线性队列 ,一个个任务从左到右执行。 compose 内到外 。本质上可以相互转化

lodash实现 flowRight 、flow, 但是是同步的,异步的如下

1
2
3
4
5
6
7
8
9
export const pipe =
(...fns) =>
(x) =>
fns.reduce((acc, fn) => fn(acc), x);

export const pipep =
(...fns) =>
async (x) =>
await fns.reduce(async (acc, fn) => fn(await acc), x);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import * as UTILS from "./utils.js";
import { flowRight, flow } from "lodash-es";

const add = (num) => {
return (baseNum) => {
return baseNum + num;
};
};
const multiply = (num) => {
return (baseNum) => {
return baseNum * num;
};
};

const addAsync = (num) => {
return (baseNum) =>
new Promise((resolve, reject) => {
global.setTimeout(() => {
if (typeof num === "number") {
resolve(baseNum + num);
}
}, 200);
});
};
const multiplyAsync = (num) => {
return (baseNum) =>
new Promise((resolve, reject) => {
global.setTimeout(() => {
if (typeof num === "number") {
resolve(baseNum * num);
}
}, 200);
});
};
const add1 = add(1);
const add2 = add(2);

const mul2 = multiply(2);
const mul3 = multiply(3);

const addAsync1 = addAsync(1);
const addAsync2 = addAsync(2);

const mulAsync2 = multiplyAsync(2);
const mulAsync3 = multiplyAsync(3);

console.log("同步 flow");
console.log(flow(add1, mul2)(11));
console.log("异步 pipep");
const result = await UTILS.pipep(addAsync1, mulAsync2)(11);
console.log(result);

koa- compose

中间件函数 洋葱模型,外层执行到内存,在回溯到外层。所有中间件函数都可以修改和挂载新的变量到context

1
2
3
4
5
6
7
8
9
const compose = (...middlewares) =>
(middlewares || []).reverse().reduce(
(dispatch, middleware) => {
return async (ctx, next) => {
return await middleware(ctx, async () => await dispatch(ctx, next));
};
},
async (ctx, next) => (await next) && next(ctx)
);

koa的源码实现做了中间键的执行次数限制 还有一些针对插件空的处理 ,核心功能类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const ctxData = {
requestId: "1232324",
user: {
name: "zhangsan",
},
};

const tasks1 = async (ctx, next) => {
console.log("获取到requestId", ctx.requestId);
await next(ctx);
console.log("回溯到tasks1", ctx.requestId);
};

const tasks2 = async (ctx, next) => {
console.log("校验用户名", ctx.user.name);
await next(ctx);
console.log("回溯到tasks2", ctx.requestId);
};

// 修改上下文 增加参数
const tasks3 = async (ctx, next) => {
console.log("校验用户名", ctx.user.name);
ctx.task3Data = "this is task3";
await next(ctx);
console.log("回溯到task3", ctx.user.name);
};

console.log("开始compose");
const all = UTILS.compose(...[tasks1, tasks2, tasks3]);

const res = await all(ctxData);

console.log(res);

// 假设有mfn1 mfn2 ctx 和next 作为参数

// 没有中间件 cb 只有上下文 执行的是 cb 直接返回null cb

// 第一次 fnRet1= (ctx, next) => mfn1(ctx, next) =>

// 第二次 fnRet2=(ctx, next) => mfn2(ctx, fnRet1(ctx,next))

// 执行 fnRet2
// fnRet2({ name: 1 }, (ctx) => {
// console.log("中间", ctx);
// });
// await 执行 fnRet1

// 执行 mfn1

// await cb

// 继续执行mfn1 往外层执行

// 继续执行mfn2

compose && pipe
https://blog.dayday.cyou/2024/08/12/compose-pipe/
作者
godbutton
发布于
2024年8月12日
许可协议