相信很多人对迭代器和生成器都不陌生,当提到async和await的原理时,大部分人可能都知道async、await是Promise+生成器的语法糖,其原理具体是怎么做的呢?下面通过这篇文章带你详细了解一下迭代器和生成器,以及带你从生成器一步步推导到async和await。
迭代器是确使用户在容器对象(链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。
迭代器的定义可能比较抽象,简单来说迭代器就是一个对象,可用于帮助我们对某个数据结构(链表、数组)进行遍历;
在JavaScript中,迭代器也是一个具体的对象,并且这个对象必须符合迭代器协议(iterator protocol);
什么是迭代器协议?
在JavaScript中就是指这个对象必须实现一个特定的next方法,并且next方法有如下要求;
next方法可接收0个或者1个参数(在生成器中next可以接收1个参数),并且需返回一个对象,对象包含以下两个属性:
done:值为Boolean,如果迭代器可以迭代产生下一个值,就为false,如果已经迭代完毕,就为true;
value:迭代器返回的值,如果done为true,value一般为undefined;
编写一个最简单的迭代器:
const iterator = {
next: function() {
return { done: false, value: 123 }
}
}
明白了迭代器的基本定义,下面就来实现一下符合迭代器协议的对象吧,并且看看其它的一些基本用法。比如,需要通过迭代器访问一个数组:
(1)创建一个迭代器对象
const names = ['curry', 'kobe', 'klay']
let index = 0 // 通过一个index来记录当前访问的位置
const iterator = {
next() {
if (index < names.length) {
return { done: false, value: names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
console.log(iterator.next()) // { done: false, value: 'curry' }
console.log(iterator.next()) // { done: false, value: 'kobe' }
console.log(iterator.next()) // { done: false, value: 'klay' }
console.log(iterator.next()) // { done: true, value: undefined }
(2)实现生成迭代器的函数
function createIterator(arr) {
let index = 0
return {
next() {
if (index < arr.length) {
return { done: false, value: arr[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
const names = ['curry', 'kobe', 'klay']
// 调用createIterator函数,生成一个访问names数组的迭代器
const namesIterator = createIterator(names)
console.log(namesIterator.next()) // { done: false, value: 'curry' }
console.log(namesIterator.next()) // { done: false, value: 'kobe' }
console.log(namesIterator.next()) // { done: false, value: 'klay' }
console.log(namesIterator.next()) // { done: true, value: undefined }
上面提到了迭代器是一个对象,并且符合迭代器协议,那么什么是可迭代对象呢?它与迭代器又有什么区别?
Symbol.iterator
方法,并且该方法返回一个迭代器对象;如下,iteratorObj
就是一个可迭代对象:
const iteratorObj = {
names: ['curry', 'kobe', 'klay'],
[Symbol.iterator]: function() {
let index = 0
return {
// 注意:这里的next需要使用箭头函数,否则this访问不到iteratorObj
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
// 调用iteratorObj中的Symbol.iterator得到一个迭代器
const iterator = iteratorObj[Symbol.iterator]()
console.log(iterator.next()) // { done: false, value: 'curry' }
console.log(iterator.next()) // { done: false, value: 'kobe' }
console.log(iterator.next()) // { done: false, value: 'klay' }
console.log(iterator.next()) // { done: true, value: undefined }
上面的可迭代对象都是由自己实现的,其实在JavaScript中为我们提供了很多可迭代对象,如:String、Array、Map、Set、arguments对象、NodeList(DOM集合)等。
// 1.String
const str = 'abc'
const strIterator = str[Symbol.iterator]()
console.log(strIterator.next()) // { value: 'a', done: false }
console.log(strIterator.next()) // { value: 'b', done: false }
console.log(strIterator.next()) // { value: 'c', done: false }
console.log(strIterator.next()) // { value: undefined, done: true }
// 2.Array
const names = ['curry', 'kobe', 'klay']
console.log(names[Symbol.iterator])
const namesIterator = names[Symbol.iterator]()
console.log(namesIterator.next()) // { value: 'curry', done: false }
console.log(namesIterator.next()) // { value: 'kobe', done: false }
console.log(namesIterator.next()) // { value: 'klay', done: false }
console.log(namesIterator.next()) // { value: undefined, done: true }
// 3.Map/Set
const set = new Set
set.add(10)
set.add(20)
set.add(30)
const setIterator = set[Symbol.iterator]()
console.log(setIterator.next()) // { value: 10, done: false }
console.log(setIterator.next()) // { value: 20, done: false }
console.log(setIterator.next()) // { value: 30, done: false }
console.log(setIterator.next()) // { value: undefined, done: true }
可迭代对象在实际应用中特别常见,像一些语法的使用、创建一些对象和方法调用都用到了可迭代对象。
JS中的语法:for...of、展开语法、解构等。
for...of可用于遍历一个可迭代对象,其原理就是利用迭代器的next函数,如果done为false,就从返回的对象中拿到value返回给我们,而对象不是一个可迭代对象,所以对象不能使用for...of遍历;
const num = [1, 2, 3]
for (const item of num) {
console.log(item) // 1 2 3
}
// 遍历上面自己定义的可迭代对象iteratorObj也是可以的
for (const item of iteratorObj) {
console.log(item) // curry kobe klay
}
const obj = { name: 'curry', name: 30 }
for (const key of obj) {
console.log(key)
}
...
对数组进行展开时,也是通过迭代器的next去获取数组的每一项值,然后存放到新数组中;const names = ['james', 'green']
// 将数组和iteratorObj通过扩展进行合并
const newNames = [...names, ...iteratorObj]
console.log(newNames) // [ 'james', 'green', 'curry', 'kobe', 'klay' ]
const str = 'abc'
const nums = [1, 2, 3]
const [str1, str2, str3] = str
console.log(str1, str2, str3) // a b c
const [num1, num2, num3] = nums
console.log(num1, num2, num3) // 1 2 3
const [name1, name2, name3] = iteratorObj
console.log(name1, name2, name3) // curry kobe klay
// 1.Set
const set = new Set(iteratorObj)
console.log(set) // Set(3) { 'curry', 'kobe', 'klay' }
// 2.Array.from
const names = Array.from(iteratorObj)
console.log(names) // [ 'curry', 'kobe', 'klay' ]
// 传入的可迭代对象中的每个值,会使用Promise.resolve进行包裹
Promise.all(iteratorObj).then(res => {
console.log(res) // [ 'curry', 'kobe', 'klay' ]
})
扩展:现在我们都知道了for...of可用于遍历一个可迭代对象,如果在遍历过程中终端了呢?因为使用break、continue、return、throw
都是可以中断遍历的,既然for...of遍历的原理是基于迭代器的,那么在for...of中进行中断操作,一定是可以被迭代器监听到的,上面说了,在迭代器中有一个next方法,其实还可以指定一个return方法,如果遍历过程中断了,就会去调用return方法,注意return方法也要返回和next方法一样的对象。这种情况就称之为迭代器的中断。
const iteratorObj = {
names: ['curry', 'kobe', 'klay'],
[Symbol.iterator]: function() {
let index = 0
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] }
} else {
return { done: true, value: undefined }
}
},
return() {
console.log('哎呀,我被中断了!')
return { done: true, value: undefined }
}
}
}
}
for (const item of iteratorObj) {
console.log(item)
if (item === 'kobe') break
}
上面提到了对象不是一个可迭代对象,所以对象不能使用for...of遍历,如果我们想要实现通过for...of遍历对象呢?那么可以自己实现一个类,这个类的实例化对象是可迭代对象。
Symbol.iterator
方法用于返回一个迭代器;p
中包含一个friends数组,通过for...of遍历p
对象时,可以将friends数组的每一项遍历出来;class Person {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.friends.length) {
return { done: false, value: this.friends[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
简单看一下效果:
const p = new Person('curry', 30, ['kobe', 'klay', 'green'])
for (const name of p) {
console.log(name) // kobe klay green
}
生成器是ES6中新增的一种控制函数执行的方案,它可以帮助我们控制函数的暂停和执行。生成器是一种特殊的迭代器,所以生成器也是一个对象,并且可以调用next方法。那么怎么创建一个生成器对象呢?
创建生成器对象需要使用生成器函数,生成器函数和普通函数不一样,主要有以下特点:
*
;yield
关键字来分割函数体代码,控制函数的执行;实现一个生成器函数,该函数的执行可以通过返回的生成器对象进行控制。
function* generatorFn() {
console.log('函数开始执行~')
console.log('函数第一段代码执行...')
yield
console.log('函数第二段代码执行...')
yield
console.log('函数第三段代码执行...')
yield
console.log('函数第四段代码执行...')
console.log('函数执行结束~')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
generator.next()
console.log('------------------------')
generator.next()
console.log('------------------------')
generator.next()
console.log('------------------------')
generator.next()
上面说到了生成器是一种特殊的迭代器,那么调用next方法肯定也是有返回值的,并且返回值是一个包含done、value属性的对象。
function* generatorFn() {
console.log('函数第一段代码执行...')
yield
console.log('函数第二段代码执行...')
yield
console.log('函数第三段代码执行...')
yield
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
从打印结果可以看出来,next返回的对象中value是没有值的,当执行到最后一段代码后,done的值就为true了:
如果需要指定next返回值中的value,那么可以通过在yield
后面跟上一个值或者表达式,就可以将对应的值传递到next返回对象value中了。
function* generatorFn() {
console.log('函数第一段代码执行...')
yield 10
console.log('函数第二段代码执行...')
yield 20
console.log('函数第三段代码执行...')
yield 30
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
观察以上打印结果,在执行完第四段代码后,调用的next返回值为{ value: undefined, done: true }
,原因是后面已经没有yield
了,而且当函数没有指定返回值时,最后会默认执行return undefined
。
在前面介绍迭代器定义时,提到迭代器的next可以传递0个或1个参数,而可以传递1个参数的情况就是生成器的next可以传递一个参数,而给每一段代码传递过去的参数可以通过yield来接收。
function* generatorFn(value) {
console.log('函数第一段代码执行...', value)
const value1 = yield 10
console.log('函数第二段代码执行...', value1)
const value2 = yield 20
console.log('函数第三段代码执行...', value2)
const value3 = yield 30
console.log('函数第四段代码执行...', value3)
}
// 调用generatorFn获取生成器
const generator = generatorFn('参数0')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next('参数1'))
console.log('------------------------')
console.log(generator.next('参数2'))
console.log('------------------------')
console.log(generator.next('参数3'))
next参数传递解释:
yield
接收的,这样可以方便下面代码使用这个参数,所以给next传递参数,需要从第二个next开始传递;return方法也可以给生成器函数传递参数,但是调用return后,生成器函数就会中断,之后再调用next就不会再继续生成值了。
function* generatorFn() {
console.log('函数第一段代码执行...')
yield
console.log('函数第二段代码执行...')
yield
console.log('函数第三段代码执行...')
yield
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.return(123))
console.log('------------------------')
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
上面的执行return方法,相当于函数内部执行了return:
function* generatorFn() {
console.log('函数第一段代码执行...')
yield
console.log('函数第二段代码执行...')
const value = yield
return value
console.log('函数第三段代码执行...')
yield
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next(123))
console.log('------------------------')
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
throw方法可以给生成器函数内部抛出异常。
try catch
捕获异常后,后续的代码还是可以正常执行的;function* generatorFn() {
console.log('函数第一段代码执行...')
yield 10
console.log('函数第二段代码执行...')
try {
yield 20
} catch (err) {
console.log(err)
}
console.log('函数第三段代码执行...')
yield 30
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.throw('err message'))
console.log('------------------------')
console.log(generator.next())
在前面实现了一个生成迭代器的函数,实现过程还需要进行判断,并自己返回对应的对象,下面就用生成器来实现一个生成迭代器的函数。
function* createIterator(arr) {
let index = 0
yield arr[index++]
yield arr[index++]
yield arr[index++]
}
function* createIterator(arr) {
for (const item of arr) {
yield item
}
}
yield*
,后面可以跟上一个可迭代对象,它会依次迭代其中每一个值;function* createIterator(arr) {
yield* arr
}
测试一下以上三种方法,执行结果都是一样的:
const names = ['curry', 'kobe', 'klay']
const iterator = createIterator(names)
console.log(iterator.next()) // { value: 'curry', done: false }
console.log(iterator.next()) // { value: 'kobe', done: false }
console.log(iterator.next()) // { value: 'klay', done: false }
console.log(iterator.next()) // { value: undefined, done: true }
在进行异步请求时,如果出现了这样一个需求,下一次的请求需要拿到上一次请求的结果。如下是使用Promise封装的一个request方法。
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
}, 300)
})
}
实现上面的需求可以怎么做呢?
request('/aaa').then(res => {
request(res + '/bbb').then(res => {
request(res + '/ccc').then(res => {
console.log(res) // /aaa/bbb/ccc
})
})
})
request('/aaa').then(res => {
return request(res + '/bbb')
}).then(res => {
return request(res + '/ccc')
}).then(res => {
console.log(res) // /aaa/bbb/ccc
})
function* getRequestData() {
const res1 = yield request('/aaa')
const res2 = yield request(res1 + '/bbb')
const res3 = yield request(res2 + '/ccc')
console.log(res3)
}
const generator = getRequestData()
generator.next().value.then(res => {
generator.next(res).value.then(res => {
generator.next(res).value.then(res => {
generator.next(res) // /aaa/bbb/ccc
})
})
})
function autoGenerator(generatorFn) {
const generator = generatorFn()
function recursion(res) {
const result = generator.next(res)
// 如果done值为true,说明结束了
if (result.done) return result.value
// 没有结束,继续调用Promise的then
result.value.then(res => {
recursion(res)
})
}
recursion()
}
autoGenerator(getRequestData) // /aaa/bbb/ccc
co
;const co = require('co')
co(getRequestData) // /aaa/bbb/ccc
async和await是我们解决异步回调的最终解决方案,它可以让我们异步的代码,看上去是同步执行的。
async function getRequestData() {
const res1 = await request('/aaa')
const res2 = await request(res1 + '/bbb')
const res3 = await request(res2 + '/ccc')
console.log(res3)
}
getRequestData() // /aaa/bbb/ccc
相信从上面讲述的四个异步请求的处理方案中,就可以看出来async、await和生成器的关系了。
*
换成async
;yield
换成await
;总结:
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.cnblogs.com/MomentYY/p/16100015.html
内容来源于网络,如有侵权,请联系作者删除!