当前位置: 首页 > Web前端 > HTML5

柯里化在异步编程中的应用

时间:2023-04-05 17:11:20 HTML5

柯里化js函数是一等公民,在函数执行过程中可以作为函数的参数或者返回值。这样的执行函数称为高阶函数。高阶函数的特点是可以很容易柯里化(Currying(柯里化)就是把一个接受多个参数的函数转化为一个接受单个参数(原函数的第一个参数)的函数,并返回一个接受第一个参数的函数剩余根据维基百科的理解,大概就是下面这个例子functionadd(x,y){returnx+y}functioncurried_add(x){returnfunction(y){returnx+y}}constcurried_add=curried_add(1)curried_add(2)//3curried_add(3)//4从例子中可以看出currying有防止参数重复的作用,并且具有延迟执行的特点,我们来看看异步的应用利用柯里化的特性进行编程,一开始我们简单介绍一下async/await的原理,generator和promiseasync/await语法其实就是generator和promise的语法糖,当generator函数执行的时候,会生成一个generator对象被退回。这个对象是一个特殊的迭代器对象,同时也是一个可迭代对象(iteratorobjectanditerableobject)。再来说说它的特殊性。function*generator(){constvalue=yield1console.log(value)try{//捕获错误yield2}catch(e){console.log(e)}}constiterator=generator();iterator===迭代器[Symbol.iterator]();//true部署一个返回自身的可迭代接口[...iterator]//[1,2]constiterator2=generator();//迭代器对象是一次性消耗的,需要启动一个新的迭代器iterator2.next()//{value:1,done:false}iterator2.next(`value`)//使用next向生成器发送数据functioniterator2.throw(newError('error'))//使用throw向生成器函数抛出错误从上面的例子可以看出,生成器对象在迭代过程中可以和生成器函数通信,如果使用在promise中,只要将状态改变后promise执行的回调结果返回给generator函数,就可以实现类似async/await的效果。在迭代过程中,可以实现一个执行器函数来迭代,这个函数类似于执行async。functioncreateDelayPromise(time){returnnewPromise((resolve)=>{setTimeout(()=>{resolve(time)},time)})}function*createIterator(){consttime1=yieldcreateDelayPromise(1000)控制台。log(time1)consttime2=yieldcreateDelayPromise(2000)console.log(time2)consttime3=yieldcreateDelayPromise(3000)console.log(time3)returntime1+time2+time3}functionrun(createIterator){constiterator=createIterator()letresult=iterator.next()letrconstp=newPromise(resolve=>{r=resolve})functionnext(){if(result.done){r(result.value)}else{Promise.resolve}(result.value).then(value=>{result=iterator.next(value)next()}).catch((err)=>{result=iterator.throw(err)next()})}}next()returnp}run(createIterator).then((value)=>{console.log(value)})上面使用createDelayPromise封了一个promise类型的异常任务,然后使用next方法迭代迭代器对象,等待对应的异步任务有结果,然后通过生成器对象的next和throw方法将结果传回生成器函数。上面是promise和generator的结合,可以让异步代码以类似同步的方式写在generator函数中,也对应curried函数,也有延迟执行的效果,也可以通过以下方式实现类似的效果与发电机合作。在Generator和currying中,除了一些接口实现了promises,还有很多长期存在的接口仍然使用回调类型的接口。比如node中很多接口都会把callback参数放在参数列表的最后,把err放在回调执行的第一个位置,像这样functioncreateDelayCurry(time,callback){setTimeout(()=>{callback(null,time)},time)}接下来,通过实现上面不同的run方法,结合currying来实现上面的效果,这次yield不再是一个promise-based对象,而是一个curried函数。在开始之前,实现一个通用的curry函数constcurry=(fn,...args)=>fn.length<=args.length?fn(...args):curry.bind(null,fn,...args);这个curry函数的作用是等到最初存储的函数的参数收集够了再执行,如果不够就继续收集。接下来会在迭代中加入回调参数,这样异步任务就可以执行了。constcurry=(fn,...args)=>fn.length<=args.length?fn(...args):curry.bind(null,fn,...args);functioncreateDelayCurry(time,callback){setTimeout(()=>{callback(null,time)},time)}constcurringDelay=curry(createDelayCurry)function*createIterator(){consttime1=yieldcurringDelay(1000)console.log(time1)consttime2=yieldcurringDelay(2000)console.log(time2)consttime3=yieldcurringDelay(3000)console.log(time3)returntime1+time2+time3}functionrun(createIterator,callback){constiterator=createIterator()letresult=iterator.next()functionnext(){if(result.done){回调(null,result.value)}else{result.value((err,value)=>{//补上最后回调函数if(err){result=iterator.throw(err)next()}else{result=iterator.next(值)next()}})}}next()}run(createIterator,(err,value)=>{console.log(err,value)})执行效果和promise版本差不多parallel上面是串行的版本,我们来看看并行的实现先看promise函数的一般并行写法*createIteratorParallel(){consttimes=yieldPromise.all([createDelayPromise(1000),createDelayPromise(2000),createDelayPromise(3000)])returntimes}对应实现一个all方法functioncurryAll(curries,callback){letlen=curries.lengthletresult=[]letcount=0curries.forEach((curried,index)=>{curried((err,value)=>{if(err){callback(err)return}else{count++result[index]=valueif(count==len){callback(null,result)}}})})}constcurriedAll=curry(curryAll)function*createIteratorParallel(){consttimes=yieldcurriedAll([curringDelay(1000),curringDelay(2000),curringDelay(3000)])returntimes}yield后面仍然是柯里化函数esNew语法上面的方案要求异步任务的回调需要在参数的末尾。如果callback不在最后,那么curry函数的实现就需要修改,但是有没有可能不修改它的实现,甚至不写curry呢?Curry是函数式编程的基本单元,最新的es规范从语法的角度提供了实验支持。函数添加(x,y){返回x+y;}constaddOne=add(1,?);//从左边申请addOne(2);//3constaddTen=add(?,10);//从右边申请addTen(2);//addOneabove12addTen是一个柯里化函数,大多数浏览器不支持这种语法,如果你想使用它也很简单,使用一个babel插件babel--plugins@babel/plugin-proposal-partial-applicationscript.js如果使用上面的语法,就不需要引入curry函数function*createIterator(){consttime1=yieldcreateDelayCurry(1000,?)console.log(time1)consttime2=yieldcreateDelayCurry(2000,?)console.log(time2)consttime3=yieldcreateDelayCurry(3000,?)console.log(time3)returntime1+time2+time3}如果callback在前面,那么把问号放在前面,其实可以放在任何参数位置,主要看具体的接口要求。小结柯里化的应用非常广泛。以上只是一个简单的例子。通过这个例子,我也明白了async/await的基本实现原理。虽然async/await已经得到了广泛的支持,promise也得到了广泛的应用,可能不再需要直接使用generator函数,但是不妨碍简单的理解。