前言
在我前面两篇文章中简单聊了聊异步的发展,从最开始的回调地狱,到后来的promise,再到手撕一个简单的promise。今天我们来聊聊generator构造器
正文
什么是generator
generator也是实现异步的一个方法,虽然它甚至不如直接使用promise来的快,可以说是十分的不好用。但是能够充分理解generator对学习async函数来说也是不可缺少的一环。
在前言中也提到了,generator是一个构造器函数,那么我们怎么去使用呢?或者说如何去实例化一个generator对象出来?答案很简单
js
function* foo() {
//function之后加一个星号就变成生成器函数
}
正如上面代码所示,只需要在function后面添加一个*就可以。而构造器函数能够默认返回一个generator对象,即使这个函数里面什么也没有
在generator对象中有一个十分重要的关键字------"yield"。这可以说是generator实现异步操作的关键,yield允许用户通过调用generator对象自带的next方法去手动控制代码的执行。例如:
js
function* g() {
let a = 1
console.log(a);
let b = yield a++
console.log(b);
let c = yield a++
console.log(a);
}
let gen = g()
console.log(gen.next(), 'next1');//读取到1,再释放当前yield
console.log(gen.next(5), 'next2');
console.log(gen.next(), 'next3');
当generator内部存在yield去暂停函数代码的执行时,generator返回的对象则会有一些不同的变化
在这个对象中我们分别拿到了value和done。done好理解,代表generator内部是否执行完毕,那么value又是啥?根据上面的代码输出不难发现,value拿到的是紧跟在yield后面的值且默认值为undefined。也就是说每当我们调用一次next,就会得到一个新的generator对象,里面有两个键值对,分别代表yield后面的值,以及内部代码是否执行完毕。
为什么说是"昙花一现的generator"
首先要说明的是,所谓的昙花一现是指generator构造函数在异步处理方面是如此,而在其他地方并不一定也是昙花一现的。在很多人看来generator在异步处理这块最大的贡献就是为async和await这两块语法糖打下了坚实的基础。因为仅通过generator自身去实现异步的话,属实是有点反人类了。除此之外,Generator函数在推出之初,并没有得到广泛的社区支持和工具支持。相比之下,Promise和async/await很快就被各种框架和库所采纳,并且得到了广泛的推广和使用。不仅如此, Generator函数在某些情况下可能会导致性能问题,尤其是与async/await相比,后者在某些JavaScript引擎中可能有更好的性能优化。
最后,我个人认为没落的另一个原因在于不好理解,很容易造成混乱。在之前就提到过yield会拿到紧跟在后面的值,不仅如此,yield的值会被next传入的参数代替。
就以刚刚的代码为例子,按照正常思维来说,b的打印应该是a++完成之后的结果,但是当我们第二次调用next的时候传入了一个参数'5',所以导致了整个yield以及后面的代码的值被替换成了5。那我不传,就可以了吗?
js
console.log(gen.next(), 'next2');
显然依旧是于事无补,因为next会默认返回一个undefined。
Await的前身---co
在co出现之前,要通过generator去实现异步还得搭配上promise,简直就是脱裤子放屁。就在generator快要消失的时候,出现了一个非常好用的co模块,无论是实现方式还是使用方式简直和后来出现的async/await一模一样,很难不怀疑js官方是不是直接抄了co模块。
Co的使用
首先,通过命令npm install co
安装co模块
其次,在代码中引入co
js
var co = require('co');
接着,将能够返回promise对象的函数放入generator构造器中。
js
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('a完成');
resolve()
}, 1000);
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('b完成');
resolve()
}, 500);
})
}
function* c(f1, f2) {
yield f1()
yield f2()
}
最后,通过co处理异步
js
co(c(a, b))
结果如下图:
总结
Generator函数作为JavaScript语言的重要特性,不仅极大提升了代码的可读性和可维护性,还为异步编程提供了新的思路和解决方案。通过灵活运用yield、yield*以及与Promise、async/await的结合,开发者可以更高效地管理复杂的数据流和异步逻辑,构建出更加健壮和优雅的应用程序。随着JavaScript生态的不断进化,掌握Generator的精髓对于每一位前端工程师来说都是不可或缺的技能。