MDN reduceRight异步示例中composition与...args含义及相关疑问
const waterfall = (...functions) => (callback, ...args) => functions.reduceRight( (composition, fn) => (...results) => fn(composition, ...results), callback )(...args); const randInt = max => Math.floor(Math.random() * max) const add5 = (callback, x) => { setTimeout(callback, randInt(1000), x + 5); }; const mult3 = (callback, x) => { setTimeout(callback, randInt(1000), x * 3); }; const sub2 = (callback, x) => { setTimeout(callback, randInt(1000), x - 2); }; const split = (callback, x) => { setTimeout(callback, randInt(1000), x, x); }; const add = (callback, x, y) => { setTimeout(callback, randInt(1000), x + y); }; const div4 = (callback, x) => { setTimeout(callback, randInt(1000), x / 4); }; const computation = waterfall(add5, mult3, sub2, split, add, div4); computation(console.log, 5) // -> 14 // 等价于以下手写实现: const computation2 = (input, callback) => { const f6 = x=> div4(callback, x); const f5 = (x, y) => add(f6, x, y); const f4 = x => split(f5, x); const f3 = x => sub2(f4, x); const f2 = x => mult3(f3, x); add5(f2, input); }
关于
composition的来源和含义composition是reduceRight遍历过程中的累加值,代表当前遍历到的函数执行完成后,需要接续执行的整条回调链。reduceRight从函数数组最右端开始倒序遍历,初始累加值是你传入的最终回调(例子里是console.log)。每遍历到一个函数,就会把当前的累加值(也就是后续要执行的链)作为callback参数和当前函数拼在一起,生成一个新的包装函数,作为下一轮遍历的composition。
你可以对照下面的手写computation2理解:整个reduceRight的过程,就是自动帮你从最内层的f6开始,一层层向外包装出f5、f4、f3、f2的过程,每一轮的composition就是对应已经包好的内层函数。等遍历到最左侧的第一个函数add5时,最终拿到的composition就是f2,此时拼出来的完整入口函数就是(...results) => add5(f2, ...results),和手写逻辑完全一致。关于
...args的理解
你的理解完全正确。waterfall是柯里化结构的函数:第一层接收要串行执行的函数列表,返回第二层函数;第二层函数接收的第一个参数是最终回调callback,剩余的所有参数打包为...args,就是传给整个执行链第一个函数的初始入参。你调用computation(console.log, 5)时,callback对应console.log,...args就是[5]。关于末尾
(...args)的作用reduceRight执行完成后,返回的是刚才说的、已经拼好整条执行链的入口函数,这个函数需要接收初始入参才能启动整个流程。末尾的(...args)就是把调用时传入的初始参数(也就是例子里的5)传给这个入口函数,立刻启动整条异步串行链。
如果去掉这部分,waterfall执行后返回的就不是可以直接传参调用的执行函数,而是一个未启动的待调用函数,你还需要再手动传一次初始参数才能跑通流程。
完全没必要通读全部MDN文档。
MDN的定位是工具参考手册,不是循序渐进的教材,它的内容覆盖了核心语法、Web API、兼容处理、废弃特性等海量内容,其中很大一部分是日常开发中很少会碰到的,无目的通读的效率极低,而且大部分内容看完不用很快就会忘,读几周看不到头是非常正常的。
更高效的使用方式是:
- 先过一遍MDN里的JS核心入门教程,掌握基础语法和常用内置对象的核心用法即可
- 日常写代码遇到记不清的API、拿不准的语法边界,直接定向搜索对应MDN条目查阅即可
- 遇到特定场景需求时(比如这次看的reduceRight实现异步串行),再针对性看对应API的高级用法和示例,比无差别通读的学习效率高很多。
内容的提问来源于stack exchange,提问作者YGOPRO

