如何更简洁地重构Ramda异步管道以符合函数式编程风格?
问题描述
我刚接触Ramda编程,已经把一个异步函数重构为以下无点风格,但如果更熟悉Ramda工具函数的话,我觉得可以进一步精简代码。
初始代码:
currentPercentage: R.pipeWith(R.andThen)([ async sloId => ({ sloId, slo: await SLOs.read(sloId) }), async ({ sloId, slo }) => ({ slo, stream: await Streams.findBySLOId(sloId) }), async ({ slo, stream }) => ({ slo, events: await Streams.readEvents(stream.id) }), async ({ slo, events }) => currentPercentage(slo.denominator)(interpret(events)), ]),
我希望进一步利用Ramda工具函数,以地道的函数式风格重写这段代码,减少需要在脑中梳理的变量数量,让代码更易懂。我设想的伪代码示例如下(可能并非正确的重构方向):
currentPercentage: R.pipeWith(R.andThen)([ wrap, merge("slo", SLOs.read), merge("stream", Streams.findBySLOId), merge("events", Streams.readEvents), async ({ slo, events }) => currentPercentage(slo.denominator)(interpret(events)), ]),
我猜测应该使用R.merge和R.pick函数,但不确定如何在此场景下有效使用,或许有其他工具函数可以胜任。也有可能当前代码已经是最简形式。
更新
经过一些尝试,我已将代码重构为:
currentPercentage: asyncPipe(loadSLOandResults(SLOs, Streams), currentPercentage),
const loadSLOandResults = (SLOs: SLOsAPI, Streams: StreamsAPI) => asyncPipe( SLOs.read, async slo => ({ slo, stream: await Streams.findBySLOId(slo.id) }), async ({ slo, stream }) => ({ slo, results: interpret(await Streams.readEvents(stream.id)) }) )
我修改了currentPercentage的函数签名,使其与loadSLOandResults的返回值匹配,还创建了一个asyncPipe工具函数来简化代码。
解决方案
你可以封装一个通用的mergeAsyncProp函数,结合Ramda的pipeWith和andThen来实现你设想的简洁风格,完全贴合函数式范式,同时减少变量的显式传递:
首先定义这个工具函数:
// 通用异步属性合并函数:接收属性名和异步函数,返回一个能将异步结果合并到输入对象的函数 const mergeAsyncProp = (propName, asyncFn) => async (input) => { const value = await asyncFn(input); return R.merge(input, R.objOf(propName, value)); };
然后就可以像你设想的那样重构初始代码:
currentPercentage: R.pipeWith(R.andThen)([ // 第一步:把传入的sloId包装成对象,为后续属性合并做准备 R.objOf('sloId'), // 合并slo属性:从对象中取sloId,调用SLOs.read后合并到对象 mergeAsyncProp('slo', R.prop('sloId')), // 合并stream属性:从对象中取sloId,调用Streams.findBySLOId后合并 mergeAsyncProp('stream', R.pipe(R.prop('sloId'), Streams.findBySLOId)), // 合并events属性:用Ramda的path简化嵌套属性取值,调用Streams.readEvents后合并 mergeAsyncProp('events', R.pipe(R.path(['stream', 'id']), Streams.readEvents)), // 最终计算步骤 async ({ slo, events }) => currentPercentage(slo.denominator)(interpret(events)), ]),
额外优化点
- 如果你的
asyncPipe是(...fns) => R.pipeWith(R.andThen)(fns)的封装,那它和Ramda原生的写法等价,可以继续沿用,能让代码更简洁。 - 最终计算步骤也可以用Ramda风格改造(前提是不牺牲可读性):
R.pipe( R.juxt([R.path(['slo', 'denominator']), R.pipe(R.prop('events'), interpret)]), ([denominator, interpretedEvents]) => currentPercentage(denominator)(interpretedEvents) )
重构优势
- 完全消除了中间变量的显式声明,数据传递全靠管道和属性合并完成,减少认知负担
mergeAsyncProp是通用工具函数,可复用在其他异步属性合并场景- 每一步职责清晰:包装输入、合并slo、合并stream、合并events、计算结果,符合单一职责原则
内容的提问来源于stack exchange,提问作者iulspop
相关产品推荐
相关产品推荐

