用 compose 替换嵌套回调可将业务链路转为自上而下的声明式流水线,关键在于统一输入输出形态、显式处理异步、隔离副作用、增强可观测性。
直接用 compose 替换嵌套回调或层层 .then,能让业务链路从“向右滑动的面条”变成“自上而下的声明式流水线”。关键不是堆砌函数,而是把每一步抽象成输入→输出明确、无副作用、可独立测试的小单元。
真实业务中,各步骤输入输出类型常不一致:接口返回的是对象,拼接 URL 需要字符串,WebSocket 初始化又要 ID。强行 compose 会卡在参数传递上。解决办法是提前约定“链路契约”:
{ token, appId, h5Id, url }),只读取自己需要的字段{ ...input, h5Id: res.result.h5Id })return undefined 隐式传播,而是统一抛出 Error,由最外层 try/catch 或专用错误处理器接管原生 compose 不处理 Promise,所以不能直接塞 fetch 或 async 函数。正确做法是:
Promise 的一元函数,例如:getSSOToken = ({ tag }) => singleSignOnToken({ formSource: tag }).then(r => ({ ...r, tag }))
await compose(step5, step4, step3, step2, step1)(initialData),而不是 compose(...)(await initialPromise)
async/await 塞进 compose 内部——那会让调用方无法控制 await 时机,也破坏了函数纯度WebSocket 连接、页面渲染、弹窗提示这些操作有副作用,不适合放在纯函数链里。建议:
{ appId, h5Id, url, wsReady: true, keywords: [...] })renderPage(result) 或 initWebSocket(result),它接收 compose 输出,再触发 DOM 或网络动作ifThen(tokenValid, getSSOToken, redirectToLogin),保持主链仍是线性流compose 链一旦出错,堆栈指向内部,很难定位哪一步挂了。上线前加两道保障:
tap(console.log) 包裹中间函数,比如 compose(tap(x => console.log('after create:', x)), appH5Create, ...)
const fetchToken = ...,让错误信息至少带函数名withTiming('fetchToken', fetchToken),方便后续性能归因