「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。
大家好,我是L同学。在上篇文章中,我们介绍了什么是生成器以及生成器的使用方法生成器知识点总结
我们了解到,生成器是一种特殊的迭代器,那么我们可以让生成器替代迭代器去使用。
生成器替代迭代器使用
首先我们创建一个迭代器。
function createArrayIterator(arr) {
let index = 0
return {
next: function() {
if (index < arr.length) {
return { done: false, value: arr[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
const names = ['abc', 'cba', 'nba']
const namesIterator = createArrayIterator(names)
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
接下来我们把这个迭代器函数改造成生成器函数,调用生成器函数会生成一个生成器对象,它跟迭代器对象一样可以调用next方法。
function* createArrayIterator(arr) {
yield 'abc'
yield 'cba'
yield 'nba'
}
但是以上写法不具有通用性,如果换个数组,这个函数就不起作用了。我们可以改造成具有通用性的函数。
function* createArrayIterator(arr) {
for(const item of arr) {
yield item
}
除了上述写法,我们还可以使用yeild* 来生产一个可迭代对象。它相当于上面的语法糖。要注意的是,yeild* 后面必须跟上可迭代对象。yeild* 会依次迭代可迭代对象,每次迭代其中一个值。
function* createArrayIterator(arr) {
yield* arr
}
举一个例子,我们要实现一个函数,它接收两个参数,这个函数可以迭代这个范围内的数字。
function createRangeIterator(start, end) {
let index = start
return {
next: function () {
if (index < end) {
return { done: false, value: index++ }
} else {
return { done: true, value: undefined }
}
}
}
}
const rangeIterator = createRangeIterator(10, 20)
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
我们可以将这个函数改造成生成器函数。
function* createRangeIterator(start, end) {
let index = start
while(index < end) {
yield index++
}
}
在迭代器相关知识点总结这篇文章中,我们讲到了自定义类的迭代,通过[Symbol.iterator]方法来使对象默认是可迭代的。现在我们将它改造成生成器。
class Classroom {
constructor(address, name, students) {
this.address = address
this.name = name
this.students = students
}
entry(newStudent) {
this.students.push(newStudent)
}
/* foo = function() {
console.log('foo');
} */
/* [Symbol.iterator] = function*() {
yield* this.students
} */
*[Symbol.iterator]() {
yield* this.students
}
}
const classroom = new Classroom("3幢", "1102", ["abc", "cba"])
// classroom.foo()
for (const item of classroom) {
console.log(item);
}
我们了解到,生成器可以替代迭代器使用,这会让代码更简洁,所以我们尽量使用生成器。
异步处理方案
现在有个需求,我们把url作为参数发送网络请求获取数据(还是url),获取到的数据拼接上字符串aaa,然后把拼接后得到的字符串再作为参数发送网络请求获取数据,获取到的数据再拼接上字符串bbb,然后把拼接后得到的字符串再作为参数发送网络请求,最终获取数据。
function requestData(url) {
// 异步请求的代码会被放入executor中
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => {
// 拿到请求的结果
resolve(url)
}, 2000);
})
}
第一种方案: 多次回调
requestData('haha').then(res => {
// 返回一个Promise
return requestData(res + 'aaa')
}).then(res => {
return requestData(res + 'bbb')
}).then(res => {
console.log(res);
})
我们可以看到回调函数中嵌套着回调函数,形成了回调地狱,这使得代码不具有阅读性和可维护性。
第二种方案:Promise中then的返回值来解决
Promise的相关知识点详见这篇文章Promise知识点总结(二)
requestData('haha').then(res => {
// 返回一个Promise
return requestData(res + 'aaa')
}).then(res => {
return requestData(res + 'bbb')
}).then(res => {
console.log(res);
})
上面的代码阅读性还是比较差的。
第三种方案:Promise + generator实现
生成器的相关知识点详见这篇文章生成器知识点总结
function* getData() {
const res1 = yield requestData('haha')
const res2 = yield requestData(res1 + 'aaa')
const res3 = yield requestData(res2 + 'bbb')
console.log(res3);
}
const generator = getData()
generator.next().value.then(res => {
// res作为res1的结果
generator.next(res).value.then(res => {
// res作为res2的结果
generator.next(res).value.then(res => {
console.log(res);
})
})
})
上面的代码需要我们一次次手动执行生成器函数next,下面我们封装一个自动执行的函数。
function execGenerator(genFn) {
const generator = genFn()
function exec(res) {
const result = generator.next(res)
if(result.done) {
return result.value
}
result.value.then(res => {
exec(res)
})
}
exec()
}
execGenerator(getData)
当然,我们平时不经常写上述代码,我们可以使用第三方包co自动执行,不需要自己写自动执行函数。
首先通过npm install co安装这个包,然后引入这个包。
const co = require('co')
co(getData)
我们可以看到使用这个包,可以很方便地得到结果。
方案四: async/await
我们将方案二:Promise + generator 改造成使用async/await。 我们在function前面加上async,把yield改成await。可以认为async/await是generator的语法糖。
async function getData() {
const res1 = await requestData('haha')
const res2 = await requestData(res1 + 'aaa')
const res3 = await requestData(res2 + 'bbb')
console.log(res3);
}
getData()