iterator/generator 和 async/await 的实现
1. iterator/generator 简介
1.1 iterator
1.1.1 iterator 的作用
通过迭代器可以返回一个集合中的每一个项. 我们常用的 for...of
循环和 ...
扩展运算符都是基于 iterator 实现的
// 这里对数组进行可迭代化
const iterator = array => {
let p = 0
return {
// 当可迭代对象调用 next() 方法时, 可以获取一个 { value: 数组元素, done: 是否迭代完毕 }. 通过该对象可以获取 array 中的一个值
// 当反复调用 next() 方法时, 就可以将一个可迭代对象中的所有元素获取到
next() {
return p < array.length ?
{ value: array[p ++], done: false } :
{ value: undefined, done: true }
}
}
}
测试是否与真正的 Iterator 功能相同
测试代码
const iterator = array => {
let p = 0
return {
next() {
return p < array.length ?
{ value: array[p ++], done: false } :
{ value: undefined, done: true }
}
}
}
const runIt = it => {
let isFinished = false
while (!isFinished) {
const o = it.next()
isFinished = o.done
console.log(o)
}
}
const arr = [1, 2, 3, 4, 5]
const it = iterator(arr)
runIt(iterator(arr))
//=> {value: 1, done: false}
//=> {value: 2, done: false}
//=> {value: 3, done: false}
//=> {value: 4, done: false}
//=> {value: 5, done: false}
//=> {value: undefined, done: true}
runIt(arr[Symbol.iterator]())
//=> {value: 1, done: false}
//=> {value: 2, done: false}
//=> {value: 3, done: false}
//=> {value: 4, done: false}
//=> {value: 5, done: false}
//=> {value: undefined, done: true}
1.1.2 内置可迭代对象以及自定义可迭代对象
可迭代对象有 Map
, Set
, Array
, String
, TypedArray
我们所熟悉的 Object
并没有实现相应的迭代协议. 而当我们需要对 Object
使用 for...of
或者 ...
时, 我们可以为 Object
实例对象添加 [Symbol.iterator]
方法, 使得该 Object
实例对象能够正常迭代
let o = { name: 'saber', gender: 'famale' }
Reflect.getPrototypeOf(o)[Symbol.iterator] = function iterator() {
const keys = Reflect.ownKeys(o)
const len = keys.length
const self = this
let p = 0
return {
next() {
return p < len ?
{ value: self[keys[p ++]], done: false } :
{ value: undefined, done: true }
}
}
}
for (let item of o)
console.log(233, item)
//=> 233 saber
//=> 233 female
console.log(...o)
//=> saber female
1.2 generator
生成器对象是由 Generator Function
返回的, 且生成器对象遵守迭代协议(生成器对象通过 next()
方法所获取到的值为 Generator Function
中所有的 yield
以及 return
的值)
生成器对象第 n
次调用 next()
方法时
- 会从第
n - 1
个yield
字段开始执行, 若第n - 1
个yield
字段为赋值语句的右表达式时, 会将next(val)
中的val
赋值给左边的变量 - 到第
n
个yield/return
字段为止, 并将yield/return
对应的值通过next()
方法返回出去
// 两次 yield + 一次 return => 最多可以有意义地调用三次 next() 方法
function * gen() { // 生成器函数
console.log('start') // 第一次调用 next() 方法, 执行第一个 yield 以上的代码
const res1 = yield 100 // 第二次调用 next(val) 方法时, 会从第一个 yield 字段所对应的赋值语句 const res1 = val 开始执行
console.log(res1)
const res2 = yield res1 << 1 // 第二次调用 next(val) 方法会执行到第二个 yield 字段, 并在赋值之前停止执行, 并将 res1 << 1 通过 next 方法返回出去
console.log(res2)
}
const g = gen() // 生成器对象
let o = g.next()
//=> start
o = g.next(o.value)
//=> 100
o = g.next(o.value)
//=> 200
console.log(o)
//=> { value: undefined, done: true }
生成器对象除了 next
方法以外, 还有两个方法
-
return
:g.return(v)
会强行结束生成器的迭代过程, 并返回对象{ value: v, done: true }
function * gen() { yield 1 yield 2 return 3 } const g = gen() console.log(g.next()) //=> { value: 1, done: false } console.log(g.return('finish')) //=> { value: 'finish', done: true } console.log(g.next()) //=> { value: undefined, done: true }
-
throw
: 执行g.throw(err)
会向上一次g.next(v)
方法执行的停止处抛出一个异常.- 如若仍能正常运行(有
try...catch
处理), 则执行g.next()
方法, 且g.throw(err)
的返回值即为next()
的返回值 - 否则直接报错,
g.throw(err)
以后的代码无法执行
function * gen() { try { yield 2333 // 执行 throw 时, 在 yield 2333 处抛出了一个异常 } catch(e) { // 异常被 try...catch 捕获到后, 代码没有被中断, 此时继续执行 next() 方法 console.log('catch error!', e) // 输出异常 yield 3333 // throw 方法返回对象 { value: 3333, done: false } } } const g = gen() console.log(g.next()) //=> { value: 2333, done: false } console.log('throw step', g.throw(new Error('throw error'))) //=> catch error! Error: throw error //=> throw step { value: 3333, done: false }
- 如若仍能正常运行(有
2. async/await 的实现
我们知道: async/await 就是 generator + Promise 的语法糖. 那么这个语法糖该怎么实现呢
2.1 async/await 的使用
假设有如下的例子:
- 有一个查询函数, 经过一定时间后可以获得结果(当然是 Promise)
// 模拟一个网络请求函数(通过参数来决定其多久返回结果) const query = time => { return new Promise(resolve => { setTimeout(resolve, time, time) }) }
- 用 async/awiat 接收 query
const getData = async () => { const data1 = await query(1000) console.log(data1) const data2 = await query(2000) console.log(data2) const data3 = await query(3000) console.log(data3) return data1 + data2 + data3 }
getData().then(result => { console.log('result', result) }, reason => { console.log('reason', reason) }) //=> 1000 //=> 2000 //=> 3000 //=> result 6000
2.2 转换为 generator + Promise 的使用
2.2.1 初步实现
-
将 getData 方法用 generator 实现
function * gen() { const data1 = yield query(1000) console.log(data1) const data2 = yield query(2000) console.log(data2) const data3 = yield query(3000) console.log(data3) return data1 + data2 + data3 }
-
对 gen 进行处理, 模拟 async/await 的执行
const toAwait = gen => { return function retFn(...args) { // 由于返回的函数执行后是一个 Promise 对象, 故而这里需要 new Promise return new Promise((resolve, reject) => { const g = gen(...args) const next = param => { const { value, done } = g.next(param) // 当 done 为 true 时, 说明已经到了 gen 函数的 return 阶段了, param 即是返回的值, 故而只需将 param 记录到 Promise 对象的 result 上即可 if (done) return resolve(value) // 如果 yield 的值为 Promise 对象, 则通过 then 方法将其从 Promise 对象中抽出来, 然后再回传到 gen 函数中以便赋值 if (value instanceof Promise) value.then(next) else next(value) } next() }) } }
-
对 toAwait 方法进行测试
测试代码
const query = time => { return new Promise(resolve => { setTimeout(resolve, time, time) }) } function * gen() { const data1 = yield query(1000) console.log(data1) const data2 = yield query(2000) console.log(data2) const data3 = yield query(3000) console.log(data3) return data1 + data2 + data3 } const toAwait = gen => { return function retFn(...args) { // 由于返回的函数执行后是一个 Promise 对象, 故而这里需要 new Promise return new Promise((resolve, reject) => { const g = gen(...args) const next = param => { const { value, done } = g.next(param) // 当 done 为 true 时, 说明已经到了 gen 函数的 return 阶段了, param 即是返回的值, 故而只需将 param 记录到 Promise 对象的 result 上即可 if (done) return resolve(value) // 如果 yield 的值为 Promise 对象, 则通过 then 方法将其从 Promise 对象中抽出来, 然后再回传到 gen 函数中以便赋值 if (value instanceof Promise) value.then(next) else next(value) } next() }) } } let getData = toAwait(gen) getData().then(result => { console.log('result', result) }, reason => { console.log('reason', reason) }) //=> 1000 //=> 2000 //=> 3000 //=> result 6000
2.2.2 进一步完善
当使用 let res = await xxx
时, 如果 xxx
不是 Promise
实例对象, 其也会被转换为 Promise
实例对象的. 具体如下:
const test = () => {
let res = await 100
console.log(100)
}
test()
console.log(200)
//=> 200
//=> 100
故而, 当 yield
所对应的 value
不是 Promise
实例对象时, 也需要对其进行 Promise
的包裹, 将其加入微任务队列中. 改进后的代码如下:
const toAwait = gen => {
return function retFn(...args) {
// 由于返回的函数执行后是一个 Promise 对象, 故而这里需要 new Promise
return new Promise((resolve, reject) => {
const g = gen(...args)
const next = param => {
const { value, done } = g.next(param)
// 当 done 为 true 时, 说明已经到了 gen 函数的 return 阶段了, param 即是返回的值, 故而只需将 param 记录到 Promise 对象的 result 上即可
if (done) return resolve(value)
Promise.resolve(value).then(next)
}
next()
})
}
}
2.2.3 最终实现
增加出错时的处理过程
const toAwait = gen => {
return function retFn(...args) {
const g = gen(...args)
return new Promise((resolve, reject) => {
const next = (param, key) => {
let gObj
try {
gObj = g[key](param)
} catch(e) {
return reject(e)
}
if (gObj.done) return resolve(param)
Promise.resolve(gObj.value)
.then(val => next(val, 'next'), err => next(err, 'throw'))
}
next(undefined, 'next')
})
}
}
最终测试代码
const toAwait = gen => {
return function retFn(...args) {
const g = gen(...args)
return new Promise((resolve, reject) => {
const next = (param, key) => {
let gObj
try {
gObj = g[key](param)
} catch(e) {
return reject(e)
}
if (gObj.done) return resolve(param)
Promise.resolve(gObj.value)
.then(val => next(val, 'next'), err => next(err, 'throw'))
}
next(undefined, 'next')
})
}
}
function * gen() {
const data1 = yield query(1000)
console.log(data1)
const data2 = yield query(2000)
console.log(data2)
const data3 = yield Promise.reject(3000)
console.log(data3)
return data1 + data2 + data3
}
const query = time => {
return new Promise(resolve => {
setTimeout(resolve, time, time)
})
}
const getData = toAwait(gen)
getData().then(result => {
console.log('result', result)
}, reason => {
console.log('reason', reason)
})
//=> 1000
//=> 2000
//=> reason 3000