生成器替代迭代器使用方法

393 阅读2分钟

「这是我参与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()