当前位置: 首页 > 后端技术 > Node.js

ES6Generator与异步同步编写

时间:2023-04-03 23:01:54 Node.js

在开始之前,我们一直没有停止改造javascript语言的异步调用方式。我们一直想像java一样用同步的方式写异步,虽然Promise允许我们在then方法中加入异步回调,但是这种调用方式还是不够优雅。Generator是es6新加入的,我们可以利用它的特性来实现更加优雅的异步任务的写法。协程简介协程与线程或进程无关。它不是操作系统提供的API接口,而是通过编程语言或汇编语言操作程序上下文和程序栈来实现的。一个线程可以包含多个协程,线程的调度由操作系统决定,协程的调度由用户决定。操作系统对此一无所知,由于它可以由用户调度,所以在执行协作任务时特别方便。(注意这样方便,因为可以通过协程解决的问题也可以通过线程和进程解决,但是比较复杂)Generator简介Generator是es6中协程的实现。是es6中的一个函数,这个函数是可以分阶段执行的,也就是说我们可以选择在这个函数的某个位置交出当前线程的执行权限,也可以选择转移权限在当前函数之外的某个位置,然后交还给这个函数,让它继续执行。这种调度完全取决于用户。es6中的协程函数是这样的function*gen(p){vara=yieldp+1;//1varb=yieldp+2;//2返回b;//3}varg=gen(1);g.next();//{value:2,done:false}g.next();//{value:3,done:false}g.next();//{value:undefined,done:true}throughvarg=gen(1);只是创建一个迭代器,函数体gen里面的内容在第一个g.next()执行时并没有执行;andwillyieldwhere该语句执行后,会返回结果。不执行以下语句。返回值是一个对象,其第一个属性是yield后表达式的值(p+1或p+2的值);第二个属性表示Generator函数是否已经执行。这里我们通过yield交出执行权限,通过next归还权限。函数*gen(p){vara=yieldp+1;//1varb=yielda+1;//2注意这里用了一个returnb;}varg=gen(1);g.next();//{value:2,done:false}g.next();//{value:NaN,done:false}这里的值为NaNg.next();//{value:undefined,done:true}g.next();//{value:2,done:false}g.next(2);//{value:3,done:false}g.next(6);//{value:6,done:true}注意这里//1处//2处vara=yieldp+1;此赋值语句中a的值不是p+1的值。这个语句只是一种写法,其中a的值就是我们在secondnext中传入的2,这个很重要。b的值也是我们接下来第三个传的6Generator的一个重要特征。从上面的内容我们总结了Generator1的3个重要特征。通过yield交出执行权限,通过next2返回执行权限。调用next会得到一个返回值,其中包含yield3之后表达式的执行结果。我们可以将它传递给next参数,并且可以通过上面写的特殊方式在Generator函数中引用利用Generator的特性实现异步代码的同步写入我们来模拟一个异步函数functionpost(url,callback){setTimeout(function(){vardata={//模拟异步处理结果url:url,value:10};callback(data);},1000);}post('http://_ivenj',function(data){console.log(data.url);//http://_ivenjconsole.log(data.value);//10});对应上面的异步函数,我想通过Generatorfunction*gen(url){vardata=yieldpost(url);//1console.log(data.url);console.log(data.value);}varg=gen('http://_ivenj');varresultG=g.next();g.下一个(resultG.value);是的,这样写就漂亮多了,很像java的同步写法。不同的是多了yield和*,这是无害的。当然,用上面的肯定不行。因为post毕竟是异步方法。没有返回值。如果这种写法实现不了,我就废话半天,所以可以通过封装来实现。上面的写法可以通过以下两点来实现(1)react实践中reduxapplyMiddleware方法详解我有一篇文章。本文介绍了柯里化。虽然这篇文章是用React写的,但是Currying是独立的。这里需要用到柯里化的思想(2)我们需要在callback中调用next继续执行,(这里有人会认为不需要callback,为什么还要用,请继续读...)我们需要包装post函数的调用形式kPost(url){returnfunction(callback){post(url,callback);}}通过这样的包装,我们可以保证调用kPost会同步得到一个返回值function*gen(url){vardata=yieldkPost(url);//1console.log(data.url);console.log(data.value);}//这里的执行方式会有所不同varg=gen('http://_ivenj');//启动任务varresultG1=g.next();varvalue_resultG1=resultG1。价值;//resultG1.value必须是一个函数,因为我们包装了value_resultG1(function(data){g.next(data);//通过在异步回调中调用next并传值来保证异步依赖的代码结果可以正确执行});下面是整体代码,是以上片段的组合,请粘贴到浏览器控制平台,或者用node运行,你会看到想要的结果functionpost(url,callback){setTimeout(function(){vardata={//模拟异步处理结果url:url,value:10};callback(data);},1000);}functionkPost(url){returnfunction(callback){post(url,callback);}}function*gen(url){vardata=yieldkPost(url);//1控制台.log(data.url);console.log(data.value);}//这里的执行方式会有所不同varg=gen('http://_ivenj');//启动任务varresulttG1=g.next();varvalue_resultG1=resultG1.value;//resultG1.value必须是一个函数,因为我们包装了value_resultG1(function(data){g.next(data);});有的人会说,你怎么不把异步回调就转过来,还要写回调,那说明你还没有真正理解其中的奥秘。我们会发现异步的value_resultG1(function(data){g.next(data);});我们写的只是调用next来传递结果,这有一个共同点,不管是哪种异步,我们都只是传递值。每个人都一视同仁。真正的业务逻辑确实是同步写的。然后,我们就可以提取出公共的地方,写一个通用的函数来执行这个传值操作。就这样,我们彻底告别了异步,再也见不到了,好开心。co.js是这个生成器的一个实现库。要使用它,我们只需将我们的gen传递给它,就像这样co(gen)是的,就是这样。接下来,我们写一个coGenerator执行器functionco(taskDef){//得到一个类似于java中外部句柄迭代器的迭代器vartask=taskDef();//开始一个任务varresult=task.next();//调用next的递归函数functionstep(){if(!result.done){//如果生成器没有执行完if(typeofresult.value==="function"){result.value(function(err,data){if(err){result=task.throw(err);return;}result=task.next(data);//传递当前异步处理结果step();//递归执行});}else{结果=task.next(result.value);//如果执行完成,传递值step();//递归执行}}}//启动递归函数step();}cofunction执行的完整代码post(url,callback){setTimeout(function(){vardata={//模拟异步处理结果url:url,value:10};callback(data);},1000);}functionkPost(url){返回function(回调){post(url,回调);}}functiongen(url){returnfunction*(){vardata=yieldkPost(url);//1console.log(data.url);控制台日志(数据值);}}functionco(taskDef){vartask=taskDef();//开始任务varresult=task.next();//调用下一个递归函数functionstep(){if(!result.done){//如果生成器没有完成if(typeofresult.value==="function"){result.value(function(err,data){if(err){result=task.throw(err);return;}result=task.next(data);//传递当前异步处理结果step();//递归执行});}else{结果=task.next(result.value);//如果执行完成,传递值step();//递归执行}}}//开始递归函数step();}co(gen('http://_ivenj'));//调用方法就这么简单上面的代码执行1s会抛出异常并正确打印{url:"http://_ivenj",value:10},聪明的你一定知道为什么会抛出异常!!!到这里已经解释清楚了,说完了,你会疑惑是不是把异步包装成Promise也是可以的,答案是可以的,柯里化的思想只是一种实现方式,Promise也是一种方式,你可以自己想一想,co.js是一个实现了两种方式的执行器,从语言层面封装了Generator。在es7中,我们可以使用async和await来更优雅的实现类java的顺序写法。async和await是Generator的语法糖,executors是用es7构建的。其他人说这是最终的解决方案。