Generator是一个非常强大的语法,但是它并没有被广泛使用(请看下面twitter上的调查!)。为什么会这样?与async/await相比,它使用起来更复杂,也不容易调试(在大多数情况下,它可以追溯到过去)。尽管我们可以用非常简单的方式获得类似的体验,但人们通常更喜欢异步/等待。
然而,生成器允许我们通过yield关键字遍历自己的代码!这是一个超级强大的语法,其实我们可以操纵执行过程!让我们从不太明显的取消操作开始。让我们从同步操作开始。
我为本文中提到的函数创建了一个代码仓库—— github.com/Bloomca/obs…
批处理
执行Generator函数会返回一个walker对象,这意味着我们可以同步遍历它。我们为什么要这么做?原因可能是为了实现批量加工。假设我们需要下载1000个项目,并在表格中一行一行地显示出来(不要问我为什么,假设我们没有使用框架)。虽然马上展示没有错,但有时候可能不是最好的解决办法。也许你的MacBook Pro可以轻松处理,但普通人的电脑不行(更别说手机了)。所以,这意味着我们需要以某种方式延迟执行。
请注意,这个例子是关于性能优化的。在遇到这个问题之前,没有必要这样做。——过早优化是万恶之源!
//原来的同步实现版本是函数render items(items){ for(items of items){ render item(item);} }//函数将由我们的执行器遍历//来执行,其实我们也可以用同样的同步方式来执行!函数* renderItems(items) {//我使用.的遍历方法,以避免为(items of items){ yield render items(items)生成新函数;}}没有区别吧?所以,这里的区别是,现在我们可以以不同的方式运行这个函数,而不需要改变源代码。其实我之前也提到过,不需要等待,我们可以同步执行。所以,让我们调整我们的代码。每次让步后增加4 ms的延迟(JavaScript VM中的心跳)怎么样?我们有1000个项目,这将需要4秒钟来渲染。——还不错。假设我想在2秒内完成渲染,很容易一次想到两种渲染方法。突然使用Promise的解决方案会变得更加复杂。——我们必须传递另一个参数:一次渲染的项目数。通过我们的执行器,我们仍然需要传递这个参数,但是好处是它对我们的renderItems方法完全没有影响。
函数runWithBatch(chunk,fn,args) { const gen=fn(.args);让num=0;返回新的Promise((resolve,promiseReject)={ callNextStep();函数callNextStep(res) { let结果;尝试{ result=gen . next(RES);} catch(e){ return reject(e);}下一个(结果);}函数next({ done,value }){ if(done){ return resolve(value);} //如果(num % chunk===0) { return sleep(4),我们睡眠的每个区块都会有一个刻度。然后(继续);} else { return project();}函数process(){ return callNextStep(value);} } });}//第一个参数是——,每批多少个项目const items=[.];batchRunner(2,函数*){ for(item of item){ yield renderItem(item);}});如您所见,我们可以轻松地更改每个批处理中的项目数量,而不考虑执行器,并返回到正常的同步执行模式——,所有这些都不会影响我们的renderItems方法。
取消
让我们考虑传统函数——取消。在我的承诺取消一般(翻译:如何取消你的承诺?)已经在本文中详细讨论过了。所以我将使用其中的一些代码:
函数runWithCancel(fn,args) { const gen=fn(.args);让取消,取消;const promise=new promise((resolve,promise reject)={//定义cancel函数从我们的fn中返回它//定义cancel方法并返回它cancel=()={ canceled=true;拒绝({ reason : ' canceled ' });};onFulfilled();函数onFulfilled(res) { if(!已取消){ let结果;尝试{ result=gen . next(RES);} catch(e){ return reject(e);}下一个(结果);返回null} }函数onRejected(err){ var result;try { result=gen . throw(err);} catch(e){ return reject(e);}下一个(结果);}函数next({ done,value }){ if(done){ return resolve(value);}//假设我们总是收到Promise,所以不需要检查returnvalue类型。然后(履行,拒绝);} });返回{承诺,取消};}这里最好的部分是我们可以取消所有还没有执行的请求(我们也可以把像AbortController这样的对象参数传递给我们的执行器,这样它甚至可以取消当前的请求!),我们没有修改自己业务逻辑中的一行代码。
暂停/继续
另一个特殊要求可能是暂停/恢复功能。为什么要这个功能?想象一下,我们渲染1000行数据,速度非常慢。我们想给用户提供暂停/恢复渲染的功能,让他们可以停止所有后台工作,阅读下载的内容。我们开始吧!
//渲染的方法还是同一个函数* render items(){ for(item of item){ yield render item(item);} }函数runWithPause(genFn,args) {让pausePromiseResolve=null让保罗妥协;const gen=genFn(.args);const promise=new Promise((解析,拒绝)={ onfulfiledwithspromise();函数onfulfiledwithspromise(RES){ if(pausePromise){ pausePromise . then(()=onfulfiled(RES));} else { on fulfilled(RES);} }函数on fulfilled(RES){ let result;尝试{ result=gen . next(RES);} catch(e){ return reject(e);}下一个(结果);返回null}函数Onejected(err){ var result;try { result=gen . throw(err);} catch(e){ return reject(e);}下一个(结果);}函数next({ done,value }){ if(done){ return resolve(value);}//假设我们总是收到Promise,所以不需要检查returnvalue类型。然后(onfulfilledwithprompt,on retried);} });return { pause :()={ pausePromise=new Promise(resolve={ pausePromiseResolve=resolve;});},resume :()={ PauseCrosseresolve();pauseflorence=null;},承诺};}通过调用这个执行器,我们可以返回一个具有暂停/恢复功能的对象,所有这些都可以很容易地获得,或者使用我们以前的业务代码!因此,如果您有许多“重”的请求链,这需要很长时间,并且您想为您的用户提供暂停/恢复功能,您可以随意在代码中实现这个执行器。
错误处理
我们有一个神秘的onprinted调用,这是我们在这一部分讨论的主题。如果我们使用普通的异步/等待或承诺链写入,我们将通过try/catch语句处理错误,如果不添加大量逻辑代码,很难处理错误。通常,如果我们需要以某种方式处理错误(例如重试),我们只需在Promise内部处理它们,Promise会调用我们自己,并且可能会再次回到同一点。此外,这不是一个普遍的解决办法。可悲的是,在这里连发电机都帮不了我们。我们发现了发电机——的局限性。虽然我们可以控制执行流程,但是我们不能移动生成器函数的主体。所以我们不能后退一步,再次执行我们的命令。一个可行的解决方案是使用命令模式,它告诉我们yield结果的数据结构——应该是我们执行这个命令所需的所有信息,这样我们就可以再次执行它。因此,我们的方法需要更改为:
函数* render items(){ for(item of item){//我们需要将所有内容传递出去://Method,content,参数yield [renderItem,null,item];}}如您所见,这让我们不清楚发生了什么。因此,最好编写一些wrapWithRetry方法,检查catch代码块中的错误类型,然后重试。但是我们仍然可以做一些不影响我们功能的事情。例如,我们可以添加忽略错误——的策略。在异步/等待中,我们必须用try/catch包装每个调用,或者添加一个空的。catch(()={})部分。有了生成器,我们可以编写一个执行器并忽略所有错误。
函数runWithIgnore(fn,args) { const gen=fn(.args);返回新的承诺((解析,承诺对象)={ onFulfilled();函数onFulfilled(res) {继续({ data : RES });}//这些是yield返回的错误。//我们想忽略它们。//所以我们照常做,但是不要将错误函数传递给被拒绝(错误){继续({ error });}函数继续(数据){ let result尝试{ result=gen.next(数据);} catch (e) {//这些错误是同步错误(如TypeError)返回reject(e);}//为了区分错误和正常结果//我们用它来执行next(result);}函数next({ done,value }){ if(done){ return resolve(value);}//假设我们总是收到Promise,所以不需要检查returnvalue类型。然后(履行,拒绝);} });}关于异步/等待
Async/await是现在首选的语法(甚至是共同讨论过的),这是未来。然而,生成器也在ECMAScript标准中,这意味着为了使用它们,除了编写一些工具函数之外,您不需要任何东西。我试着给你看一些不那么简单的例子。这些例子的价值取决于你的看法。请记住,没有多少人熟悉生成器,如果在整个代码库中只在一个地方使用它们,那么使用Promise可能会更容易。另一方面,有些问题可以通过生成器优雅简洁地处理。
摘要
以上就是边肖介绍的JavaScript中使用生成器的方法。希望对大家有帮助。如果你有任何问题,请给我留言,边肖会及时回复你。非常感谢您对我们网站的支持!