函数式编程中Algebraic Effects的含义及JavaScript示例问询
什么是函数式编程中的 Algebraic Effects?
简单来说,Algebraic Effects 是一种**分离"操作意图"和"操作实现"**的副作用处理机制,它让你可以在纯函数里声明"我需要做某个有副作用的操作"(比如获取用户输入、读取全局状态),但不用关心这个操作具体怎么实现——具体的实现交给专门的"处理器(Handler)"来完成。
和 Monad 这类传统副作用处理方式比,它的优势在于:
- 不需要把整个代码包裹在 Monad 的链式调用里,代码更接近普通的同步写法
- 同一个操作意图可以被不同的处理器适配,比如开发环境用模拟数据,生产环境用真实接口
用 JavaScript 模拟 Algebraic Effects
JavaScript 原生没有内置 Algebraic Effects,但我们可以用**生成器函数(Generator)**来模拟核心逻辑——生成器的暂停/恢复特性刚好对应 Effect 的"抛出请求"和"处理后继续执行"。
第一步:定义 Effect 和 Handler 基础机制
先写一个简单的工具函数,用来捕获生成器里抛出的 Effect 请求,然后交给对应的 Handler 处理:
// 处理生成器函数与对应Effect处理器的核心逻辑 function runWithHandler(generator, handlers) { const iterator = generator(); function handle(result) { if (result.done) return Promise.resolve(result.value); // 识别并处理Effect请求 if (result.value.type && handlers[result.value.type]) { return Promise.resolve(handlers[result.value.type](result.value.payload)) .then(response => handle(iterator.next(response))) .catch(error => handle(iterator.throw(error))); } // 处理普通yield值 return handle(iterator.next(result.value)); } return handle(iterator.next()); } // 辅助函数:创建标准化的Effect请求 function createEffect(type, payload) { return { type, payload }; }
第二步:声明带 Effect 的业务函数
比如我们写一个问候用户的函数,它只声明"需要获取用户名字"这个意图,完全不关心输入的来源:
function* greetUser() { // 抛出"获取用户输入"的Effect请求,仅传递提示文本 const userName = yield createEffect('USER_INPUT', { prompt: '请输入你的名字:' }); // 纯逻辑:根据输入生成问候语 return `你好,${userName}!`; }
第三步:为 Effect 编写不同的处理器
现在我们可以给USER_INPUT这个Effect编写不同的实现,适配不同场景:
场景1:浏览器环境用弹窗获取输入
const browserHandlers = { USER_INPUT: ({ prompt }) => { return prompt(prompt); // 调用浏览器原生弹窗函数 } }; // 运行函数 runWithHandler(greetUser, browserHandlers).then(console.log); // 浏览器中执行时,会弹出输入框,输入名字后控制台将打印问候语
场景2:测试环境用模拟数据
const testHandlers = { USER_INPUT: () => { return '测试用户'; // 直接返回预设模拟数据,无需真实交互 } }; // 测试时运行 runWithHandler(greetUser, testHandlers).then(console.log); // 控制台直接输出:你好,测试用户!
核心逻辑说明
- 生成器函数
greetUser里的yield createEffect(...)是在"抛出"一个Effect请求,此时函数会暂停执行。 runWithHandler捕获这个请求后,找到对应的Handler处理,拿到结果后用iterator.next(response)恢复生成器执行,把结果传递给userName变量。- 这就实现了意图与实现的完全分离:业务逻辑只关心"需要什么",具体怎么获取由外部Handler决定。
内容的提问来源于stack exchange,提问作者SmallTown NE
相关产品推荐
相关产品推荐

