迭代器的运用——批量获取hitokoto语句
等我生成一份500kb的json文件,看哪位小朋友还敢硬编码。
原本🐱忙着睡觉,并不想自己提供一份超大数据文件给小朋友。不过我显然高估了小朋友的读题能力——当我看到有同学重复了10+遍DOM树操作时,我差点在数据结构课上气得跳起来。
总之,感谢这位同学治好了我的低血压,我花了一个小时,粗略地认识了迭代器,顺便写了一个可以批量收集hitokoto语句的小脚本。在🎮的帮助下,增加了处理Status Code 514
访问拒绝的部分,由于能够在访问频率过快时自动等待一段时间重启,获取数据所需时间显著下降👍。
Prerequisite
我完成的第一个版本使用了计时器来控制请求的发送。
1 | const timeId = setInterval(sendRequest, 1000); |
简化代码如上,通过改变n的数量,以每秒1次的频率向目标接口发送请求,并把data
加入到数组中。
很显然这是一个非常不优雅的解决方案,有一种手工计时的味道,以及没有catch可能出现的错误,使程序不太稳定。
在面向StackOverflow编程后,我意识到异步迭代+generator的组合可能比较适合这个使用场景,开始尝试。
Iterable object
这是我第一次见到可迭代对象🤦♂️。简而言之,iterable object中包含了我们要迭代的范围,每一轮循环中需要返回的变量,以及循环被推进和判断迭代是否结束的方法next()
。
下面就是一个例子:
1 | let range = { |
现在,这个迭代器就可以使用for...of
方法来遍历了。
1 | for (let num of range) { |
Generator
通常来说,一个function只能返回return
一次,而generator
可以使用yield
返回任意次,直到触发了return
为止。此时的done
为true
。
1 | function* generateSequence() { |
可以想象到的是,我们可以通过传参,yield
出我们想要的结果。
1 | function* generateSequence(start, end) { |
在这个例子中,已经可以举一反三,利用临时变量的累加来达到批量发送请求的目的了。
迭代!
为什么不直接使用for循环呢?在一般情况下,for循环都是同步进行的。也就是说,当循环100次时,这100次的请求是几乎同步发送的,因为我们没有要求剩下99次请求需要等待第1次请求返回后再依次发送。因此,需要利用for await
来进行异步操作。那么,只需要使用generator来生成一个间隔一段时间就发送一次请求的迭代器即可。
1 | async function* generateSequence(end) { |
这里我返回的是response,一个json变量。最后,只需要在for await
中遍历创建好的迭代器,依次将获得的json对象push
入事先创建的数组之中就能完成获取。
1 | let data = []; |
加速
完成这样一个循环后,程序已经可以运行了。但是由于服务器和网络等因素的限制,如果请求速度过快(如500ms/次),就有可能出现Status code 514
的错误。如果此时不处理这个错误,程序将直接退出。这显然不是一个稳定、快速的脚本,因此我在迭代器中增加了一些错误处理,当请求失败时,等待一段时间再重新进行请求。
1 | const getData = async () => { |
增加容错之后,相比于第一个版本1s/次的速度,获取数据的效率显著上升。
太棒了!学到许多👍