首页>>前端>>JavaScript->别再用generator模拟async啦,它还有很酷的用法

别再用generator模拟async啦,它还有很酷的用法

时间:2023-11-29 本站 点击:0

前一阵在某个技术群里发现有人在讨论JavaScript的generator,不少人一提generator就会把它跟异步联系在一起,我认为这是一个很深的误解。

generator 究竟跟异步是什么关系?以co为代表的一批早期框架用它来模拟async/await,但是这是否能说明 generator 与异步相关呢?我认为答案是否定的,co中用到的语言特性很多,if、函数、变量……而 generator 只是其中之一罢了。

generator 的确是实现模拟async/await的一个关键语言特性,但是,正确的因果关系是 generator 和 async/await 共用了一个JS的底层设施:函数暂停执行并保留当时执行环境。

在我的观念中,generator 的应用前景远远比 async/await 更为广阔。generator 代表了一种"无穷序列"的抽象,或者说不定长序列的抽象,这个抽象可以为我们带来编程思路上的突破。

在非常前卫的函数式语言Haskell的官网首页,有这样一段代码:

primes=filterPrime[2..]wherefilterPrime(p:xs)=p:filterPrime[x|x<-xs,x`mod`p/=0]

这是一段 Haskell 程序员引以为傲的代码,它的作用是生成一个质数序列,在多数语言中,都很难复刻这个逻辑结构。其中,最关键的一点就是它应用了延迟计算和无穷列表的概念。

我们试着分析这段 Haskell 代码的逻辑,[2..]表示一个从2开始到无穷的整数序列, filterPrime是一个函数,对这个整数序列做了过滤,函数具体内容则由后面的where指定。所以,能够把整数序列变成质数序列的关键代码就是 filterPrime。那么,它究竟做了什么呢?

这段代码简短得不可思议,首先我们来看参数部分,p:xs 是解构赋值的形参,它表示,把输入中的列表第一个元素赋值为p,剩余部分,赋值为xs。

第一次调用 filterPrime 实参为[2..],此时,p的值就是2,而xs则是无尽列表[3..]

那么,filterPrime 是如何将 p 和 xs 过滤成质数列表的呢?我们来看这段代码:

[x|x<-xs,x`mod\`p/=0]`

这段大概的意思,可以用一段适合JS程序员理解的伪代码来解释:

xs.filter(x=>x%p!==0)

就是从列表 xs 中,过滤 p 的倍数。当然了,xs 并不是 JavaScript 原生数组,所以它并没有方便的filter方法。

那么,接下来,这个过滤好的数组传递给 filterPrime 递归就很有意思了,此时 xs 中已经被过滤掉了 p 的倍数,剩下的第一个数就必定是质数了,我们继续用 filterPrime 递归过滤其第一个元素的倍数,就可以继续找到下一个质数。

最后,代码p : 表示将 p 拼接到列表的第一个。

那么,在 JavaScript 中,是否能复刻这样的编程思想呢?

答案当然是可以,其关键正是 generator。

首先我们要解决的问题就是[2..],这是一个无尽列表,JavaScript中不支持无尽列表,但是我们可以用 generator 来表示,其代码如下:

function*integerRange(from,to){for(leti=from;i<to;i++){yieldi;}}

接下来,数组的filter并不能够很好地作用于无尽列表,所以我们需要一个针对无尽列表的filter函数,其代码如下:

function*filter(iter,condition){for(letvofiter){if(condition(v)){yieldv;}}}

最后是我们的重头戏 filterPrime 啦,只要读懂了Haskell,这算不上困难,实现代码如下:

function*filterPrime(iter){letp=iter.next().value;letrest=iter;yieldp;for(letvoffilterPrime(filter(iter,x=>x%p!=0)))yieldv;}

代码写好了,我们可以用JavaScript中独有的异步能力,来输出这个质数序列看看:

functionsleep(d){returnnewPromise(resolve=>setTimeout(resolve,d));}voidasyncfunction(){for(letvoffilterPrime(integerRange(2,Infinity))){awaitsleep(1000);console.log(v);}}();

好啦,虽然语法噪声稍微有点多,但是到此为止我们就实现了跟 Haskell 一样的质数序列算法。

除了无尽列表,generator 也很适合包装一些API,表达“不定项的列表”这样的概念。比如,我们可以对正则表达式的exec做一些包装,使它变成一个 generator。

function*execRegExp(regexp,string){letr=null;while(r=regexp.exec(string)){yieldr;}}

使用的时候,我们就可以用 for...of 结构了。下面代码展示了一个简单的词法分析写法。

lettokens=execRegExp(/let|var|\s+|[a-zA-Z$][0-9a-zA-Z$]*|[1-9][0-9]*|\+|-|\*|\/|;|=/g,"leta=1+2;")for(letsoftokens){console.log(s);}

这样的API设计,是不是比原来更简洁优美呢?

你看 generator 是一个潜力如此之大的语言特性,它为 JavaScripter 们打开了通往"无尽"数学概念的大门。所以,别再想着拿它模拟异步啦,希望看过本文,你能获得一点灵感,把 generator 用到开源项目或者生产中的 API 设计,谢谢观赏。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/665.html