Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

es6-生成器Generator #20

Open
wozien opened this issue Oct 15, 2020 · 0 comments
Open

es6-生成器Generator #20

wozien opened this issue Oct 15, 2020 · 0 comments
Labels

Comments

@wozien
Copy link
Owner

wozien commented Oct 15, 2020

迭代器是es6中一个重要的概念,很多新特性都是基于迭代器概念而铺开的。为了更加方便的创建自定义的迭代器,es6引入了生成器 (Generator) 的概念。它是一种可以返回迭代器的特殊函数。有了生成器及它的特性可以让我们创建更加简洁的异步代码。

基本概念

通过 function 关键字后面的星号(*)来表示,函数体用 yield 关键字来控制迭代器每次 next() 返回结果:

function* createIterator() {
  yield 1;
  yield 2;
  yield 3;
}

let iterator = createIterator();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

通过生成器生成的迭代器每次调用 next()执行函数代码时 ,每次执行 yield 语句完后就会自动停止执行。直到再次调用 next() 方法才会继续执行。

function* createIterator() {
  console.log(1);
  yield;
  console.log(2);
  yield;
  console.log(3);
}

let iterator = createIterator();

iterator.next(); // 1
iterator.next(); // 2 

yield 关键字只能在生成器内部使用,嵌套的函数也不行:

function* createIterator(items) {
  items.forEach(function (item) {
    //  SyntaxError: Unexpected identifier
    yield item;
  })
}

在对象里面定义生成器函数:

let obj = {
  createIterator: function* (items) {
    // ...
  }
}

// 用es6方式
let obj = {
  *createIterator(items) {
    // ...
  }
}

注意,生成器函数不支持箭头函数写法

高级迭代器用法

迭代器传参

可以给迭代器 next() 方法传递一个参数,这个参数的值会替代生成器内部上一条yield 语句的返回值:

function* createIterator() {
  let first = yield 1;
  let second = yield first + 2;
  yield second + 3;
}

let iterator = createIterator();

console.log(iterator.next());  // { value: 1, done: false }
console.log(iterator.next(3)); // { value: 5, done: false }
console.log(iterator.next(5)); // { value: 8, done: false }
console.log(iterator.next());  // { value: undefined, done: true }

第二次调用 next() 传入4,会作为上一条 yield 语句的返回值,此时first的值为3,而不是1,所以第二次 next 的返回值为5。以此类推,第3次 next()传入5,返回值为8。

注意,第一次调用 next() 传入参数会被忽略。运行的流程可以如下图:

在迭代器抛出错误

迭代器除了 next() 方法,还有利用 throw() 抛出一个Error对象。错误被抛出后,生成器函数的后面代码会停止执行:

function* createIterator() {
  let first = yield 1;
  let second = yield first + 2;
  yield second + 3;
}

let iterator = createIterator();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next(3)); // { value: 5, done: false }
console.log(iterator.throw(new Error('boom'))); // 从生成器中抛出错误

在生成器函数内可以用 try...catch 来捕捉错误,后续代码才能继续执行:

function* createIterator() {
  let first = yield 1;
  let second;

  try {
    second = yield first + 2;
  } catch (e) {
    second = 6;
  }

  yield second + 3;
}

let iterator = createIterator();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next(3)); // { value: 5, done: false }
console.log(iterator.throw(new Error('boom'))); // { value: 9, done: false }
console.log(iterator.next());   // { value: undefined, done: true }

生成器返回值

因为生成器也是一个函数,所以可以用 return 返回值。在 return 后,结果对象的done立即变为 true,value为返回的值。后续 yield 语句将不会执行:

function* createIterator() {
  yield 1;
  return 'done';
  yield 2;
}

let iterator = createIterator();

console.log(iterator.next());  // { value: 1, done: false }
console.log(iterator.next());  // { value: 'done', done: true }
console.log(iterator.next());  // { value: undefined, done: true }

return 返回值只获取一次,后续调用 next() 都是返回 undefiend

委托生成器

委托生成器是指在生成器的内用 yield* 语法接上另外一个生成器函数,把数据生成的过程委托给其他迭代器:

function* createNumber() {
  yield 1;
  yield 2;
}

function* createColor() {
  yield 'red';
}

function* combine() {
  yield* createNumber();
  yield* createColor();
  return 'combine';
}

let iterator = combine();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 'red', done: false }
console.log(iterator.next());  // { value: 'combine', done: true }

异步任务执行

我们用 setTimeout() 来模拟一个异步任务:

function fetchData(url, cb) {
  setTimeout(() => {
    cb({ code: 0, data: url });
  }, 1000);
}

把上面的函数改成返回可以接收回调的函数:

function fetchData(url) {
  return (cb) => {
    setTimeout(() => {
      cb({ code: 0, data: url });
    }, 1000);
  }
}

我们有一个异步任务的生成器函数:

function* gen() {
  let res1 = yield fetchData('http://www.baidu.com');
  let res2 = yield fetchData('http://www.inoob.xyz');
  console.log(res1.data + ' ' + res2.data);
}

要让上面的生成器函数正确执行,我们需要这样调用:

let g = gen();

g.next().value(function(data) {
  var r2 = g.next(data);
  r2.value(function(data) {
    g.next(data);
  });
});

通过在回调函数的执行把控制权重新回到生成器函数,继续执行函数到下一条 yield。我们用递归的方式改写执行函数:

function run(gen) {
  let g = gen();

  function next(data) {
    let result = g.next(data);

    if (result.done) return;

    if (typeof result.value === 'function') {
      result.value(next);
    } else {
      next(result.value);
    }
  }

  next();
}

run(gen);

其实,上面的自动执行生成器函数的方法只适用于回调形式的异步任务,还要考虑返回Promise 形式的异步任务,并且要处理异常的情况。这里推荐一个npm库,该库已经兼容所有情况自动执行 Generator函数。 >>>github地址

参考

ES6 系列之 Generator 的自动执行

@wozien wozien added the es6 label Oct 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant