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 - 1yield 字段开始执行, 若第 n - 1yield 字段为赋值语句的右表达式时, 会将 next(val) 中的 val 赋值给左边的变量
  • 到第 nyield/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
posted @ 2022-11-19 22:03  小阁下  阅读(22)  评论(0编辑  收藏  举报