作者:麦乐来源:恒生LIGHT云社区Generator函数基本用法function*helloWorldGenerator(){yield'hello';产生“世界”;返回'结束';}varhw=helloWorldGenerator();调用Generator函数后,会生成一个迭代器对象,通过调用迭代器的next方法可以控制函数内部代码的执行。hw.next()//{value:'hello',done:false}hw.next()//{value:'world',done:false}hw.next()//{value:'ending',done:true}hw.next()//{value:undefined,done:true}当Generator函数遇到yield时,可以暂停generator函数内部代码的执行,使其挂起。在可迭代对象上调用next()方法会导致代码从中断处继续执行。我们先来了解一下什么是可迭代对象?可迭代对象要成为可迭代对象,对象必须实现@@iterator方法。这意味着对象(或其原型链上的对象)必须具有键为@@iterator的属性,可通过常量Symbol.iterator访问:[Symbol.iterator]一个没有参数的函数,其返回值是一个符合迭代器协议。让someString="hi";typeofsomeString[Symbol.iterator];让iterator=someString[Symbol.iterator]();iterator+"";//"[对象字符串迭代器]"iterator.next();//{value:"h",done:false}iterator.next();//{value:"i",done:false}iterator.next();//{value:undefined,done:true}我们再看看,为什么挂了?有一个新术语“协程”。什么是协程?协程比线程更轻量级。正如一个进程可以有多个线程一样,一个线程也可以有多个协程。协程不受操作系统内核管理,而完全由程序控制,即在用户态执行。这样做的好处是性能大大提升,因为它不会像线程切换那样消耗资源。协程既不是进程也不是线程,而是一种特殊的函数,可以在某处挂起,挂起外继续运行。因此,与进程和线程相比,协程不是一个维度的概念。生成器函数原理生成器函数是ES6中协程的实现。最大的特点是可以交出函数的执行权(即挂起执行)。总结一下:一个线程有多个协程Generator函数是ES6中协程的实现Yield挂起协程(交给其他协程),next调用协程说到这里,应该有一个新的generator函数来认识一下.在实际开发中,直接使用Generator函数并不常见,因为它只能通过手动调用next方法来实现函数内部代码的顺序执行。如果觉得好用,可以实现一个Generator功能的自动执行神器。自动执行的Generator函数可以让Generator函数根据g.next()的返回值{value:'',done:false}中done的值递归执行自己:functionrun(generator){varg=generator();varnext=function(){varobj=g.next()console.log(obj)if(!obj.done){next()}}next()}run(helloWorldGenerator)可以实现自执行功能,但是不保证执行顺序。模拟两个异步请求:functionsleep1(){returnnewPromise((resolve)=>{setTimeout(()=>{console.log('sleep1')resolve(1)},1000)})}functionsleep2(){returnnewPromise((resolve)=>{setTimeout(()=>{console.log('sleep2')resolve(2)},1000)})}modifyfunctionfunction*helloWorldGenerator(){yieldsleep1();控制台日志(1);产量睡眠2();控制台日志(2);}执行run(helloWorldGenerator)看打印顺序:同步代码执行完之后执行异步函数,如果要实现异步代码可以按顺序执行,代码可以进一步优化:functionrun(generator){varg=generator();varnext=function(){varobj=g.next();console.log(obj)if(obj.done)返回;//如果yield后的返回值不是promise,可以使用Promise。wrapresolve防止报错Promise.resolve(obj.value).then(()=>{next()})}next()}如果sleep1是网络请求,yield后就可以拿到request回来了数据长这样一个更优雅的异步问题解决方案。如果想获取sleep函数resolve的值,也是可以实现的。//修改函数变量接收yield语句返回结果function*helloWorldGenerator(){vara=yieldsleep1();控制台日志(一);varb=yieldsleep2();控制台日志(b);}//g.next(v);传递结果functionrun(generator){varg=generator();varnext=function(v){varobj=g.next(v);console.log(obj)if(obj.done)返回;//如果yield后的返回值不是promise,可以用Promise.resolve包裹起来防止报错Promise.resolve(obj.value).then((v)=>{next(v)})}next()您将看到与上面相同的打印结果。仔细看上面的实现方式和asyncawait很像,其实这就是asyncawait的原理。asyncawaitasyncawait本质上是一个自执行的Generator函数,结合promise实现。将Generator函数中的星号(*)换成async,把yield换成await,就这样了。更详细的代码如下,有兴趣的同学可以加深理解://async函数就是把Generator函数的星号(*)换成async,把yield换成await,就这样//async函数返回一个Promise对象,你可以使用then方法添加回调函数。函数执行时,一旦遇到await,会先返回,等到异步操作完成后,再执行函数体后面的语句//Generator函数的执行必须依赖执行器,所以有一个co模块,async函数自动执行Executor和普通函数一样//async函数对Generator函数的改进:/*(1)内置executor(2)更好的语义(3)更广泛的适用性(4)returnvalueisPromise*///async函数的实现原理是将Generator函数和自动执行器封装在一个函数中。asyncfunctionfn(args){//...}//相当于里面的await,换成yieldfunctionfn(args){returnspawn(function*(){//...});}functionspawn(genF){returnnewPromise(function(resolve,reject){constgen=genF();functionstep(nextF){letnext;try{next=nextF();}catch(e){returnreject(e);}if(next.done){returnresolve(next.value);//这就是为什么不使用trycatch不会执行异常异步请求背后的代码,而调用nex()方法不会从这里被调用。}Promise.resolve(next.value).then(function(v){step(function(){returngen.next(v);});},function(e){step(function(){returngen.throw(e);//这可以解释为什么async函数需要使用trycatch来捕获异常,而生成器函数的throw会让代码进入catch});});}step(function(){returngen.next(undefined);});});}想从科技巨头那里了解更多?开发中遇到的问题在哪里讨论?如何获取海量金融科技资源?恒生LIGHT云社区,恒生电子打造的金融科技专业社区平台,分享实用技术干货、资源数据、金融科技行业动态,拥抱所有金融开发者。扫描下方小程序二维码加入我们吧!
