reduceRight 是构建装饰器链的理想工具,因其从右向左累积的特性天然匹配“定义顺序与执行顺序相反”的包裹逻辑;composeDecorators 利用它将装饰器数组合成单函数,支持同步异步混合及上下文注入。

reduceRight 是数组方法中少被重视但极具表现力的工具,特别适合从右向左组合函数——这恰好契合装饰器链(如中间件、请求拦截、数据转换流水线)的执行顺序:最外层装饰器最先执行,但定义时往往写在最后,逻辑上“后置即前置”。用 reduceRight 构建装饰器链,能清晰表达“包裹关系”,避免嵌套调用或手动反转数组。
理解装饰器链的本质:函数包裹
一个装饰器接收目标函数,返回新函数;多个装饰器叠加,就是一层套一层的闭包包裹。例如:
const withAuth = fn => (...args) => { /* 检查权限 */ return fn(...args); };<br>const withLog = fn => (...args) => { console.log('call'); return fn(...args); };<br>// 期望执行顺序:withAuth(withLog(target)) → 先 log,再 auth<br>// 即:target 被 withLog 包裹,再被 withAuth 包裹注意:调用时最外层装饰器(withAuth)先运行,但它必须作用于已被内层包裹过的函数。因此定义顺序和执行顺序是反的——reduceRight 正好按定义顺序从右往左累积,天然匹配这一模型。
用 reduceRight 构建可复用的装饰器合成器
写一个通用函数,接受装饰器数组与原始函数,返回被层层包裹后的新函数:
const composeDecorators = (decorators) => (target) =><br> decorators.reduceRight(<br> (wrapped, decorator) => decorator(wrapped),<br> target<br> );
使用示例:
const addTimestamp = fn => (...args) => {<br> console.time('exec');<br> const res = fn(...args);<br> console.timeEnd('exec');<br> return res;<br>};<br><br>const withRetry = fn => async (...args) => {<br> for (let i = 0; i < 3; i++) {<br> try { return await fn(...args); }<br> catch (e) { if (i === 2) throw e; }<br> }<br>};<br><br>const decoratedFetch = composeDecorators([<br> withRetry,<br> addTimestamp,<br> withAuth<br>])(fetch);<br>// 等价于 withAuth(addTimestamp(withRetry(fetch)))关键点:
- 数组顺序即“书写顺序”:越靠后的装饰器越靠近原始函数(最内层)
-
reduceRight初始值是target,每次用当前装饰器包裹已累积的结果 - 支持同步/异步混合(只要装饰器自身处理好 Promise 即可)
处理异步装饰器与错误穿透
当链中存在异步装饰器(如鉴权、日志上报),需确保 await 正确传递,并让错误冒泡到外层:
const withTimeout = (ms) => fn => async (...args) => {<br> const controller = new AbortController();<br> const timeoutId = setTimeout(() => controller.abort(), ms);<br> try {<br> const result = await fn(...args, { signal: controller.signal });<br> clearTimeout(timeoutId);<br> return result;<br> } catch (err) {<br> clearTimeout(timeoutId);<br> if (err.name === 'AbortError') throw new Error(`Timeout after ${ms}ms`);<br> throw err;<br> }<br>};该装饰器不改变返回类型,仍返回 Promise,下游装饰器可继续 await。只要每个装饰器都遵循“接收函数 → 返回同签名函数”的契约,reduceRight 合成就完全透明。
扩展:带上下文参数的装饰器链
实际业务中,装饰器常需共享上下文(如 request 对象、配置项)。可改造合成器,注入统一 context:
const composeDecoratorsWithContext = (decorators) => (target, context) =><br> decorators.reduceRight(<br> (wrapped, decorator) => decorator(wrapped, context),<br> target<br> );<br><br>// 使用:<br>const decoratedHandler = composeDecoratorsWithContext([<br> withValidation,<br> withRateLimit,<br> withTracing<br>])(handler, { req, config });此时每个装饰器签名为 (fn, ctx) => newFn,context 在每层都可用,且不破坏链式结构。

















