ES6 之 Generator函数

149 阅读4分钟

- 什么是Generator函数

Generator 函数式ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。

  • 状态机:封装了多个内部状态。
  • 遍历器对象生成函数:返回值是一个遍历器对象。

- 语法

function * myGenerator() {
  yield 'hello';
  yield 1+1;
  return 'wor' + 'ld';
}

const hw = myGenerator();
console.log(hw);   // Object [Generator] {}
console.log(hw.next());  // { value: 'hello', done: false }
console.log(hw.next());   // { value: 2, done: false }
console.log(hw.next());   // { value: 'world', done: true }
console.log(hw.next());  // { value: undefined, done: true }

特征:

1. 星号: function 关键字和函数名之间有一个星号
function + * + 函数名

2. yield: 函数体内部使用 yield 表达式
yield 定义(产出)不同的内部状态,

3. 返回指针对象: 被调用时不会执行,返回值不是函数运行结果,返回值是一个指向内部状态的指针对象(遍历器对象 Iterator Object

4. 使用next惰性求值: 必须调用遍历器对象的 next 方法,才能使指针移向下一个状态,遇到yield(或return)则暂停执行,重新调用 next 方法可以恢复执行。

5. value 和 done: 调用遍历对象的 next 方法时,next 方法返回一个包含value和done属性的对象,value属性的值是当前yield表达式的值,done属性的值要视乎遍历是否结束,未结束为false,结束为true(遍历结束时value属性的值为undefined)。

6. for...of自动遍历: for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。
这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5

7. 除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。

- yield 和 return

相似之处: 都能返回紧跟在后面的表达式的值。
不同之处:

  1. 遇到 yield 函数会暂停执行,下一次调用next时再从该位置继续向后执行;而 return 不具备位置记忆功能,一个函数内只能执行一次,之后再调用next,不管return语句后面是否还有其他语句,都返回value属性值为undefined,done属性值为true 的对象。
function * myGenerator() {
  yield 'hello';
  yield 'world';
  return '此处是return语句';
  yield 'say';
  return 'bye';
}

const hw = myGenerator();
console.log(hw);
hw.next()   // {value: 'hello', done: false}
hw.next()   // {value: 'world', done: false}
hw.next()   // {value: '此处是return语句', done: true}
hw.next()   // {value: 'undefined', done: true}
hw.next()   // {value: 'undefined', done: true}
  1. yield 表达式只能在 Generator 函数里使用。Generator 函数可以不用yield表达式,此时就变成一个单纯的暂缓执行函数,调用next() 执行

Generator 函数执行后,返回一个遍历器对象

返回的遍历器对象也具有 Symbol.iterator 属性,执行后返回自身。

function * myGenerator () {
    ......
}
var hw = myGenerator();

hw[Symbol.iterator]() === hw   // true

next 方法的参数

next 方法可以带一个参数,该参数会被当做上一个 yield 表达式的返回值。

function* dataConsumer() {
  console.log('Started');
  console.log(`1. ${yield}`);
  console.log(`2. ${yield}`);
  return 'result';
}

let genObj = dataConsumer();
genObj.next();  // Started
genObj.next();  // 1. undefined
genObj.next('a')  // 2. a  
genObj.next('b')  //   ---------此处执行return,无输出
console.log(genObj.next('b'));  // { value: undefined, done: true }

- Generator 函数应用

1. 异步操作的同步化表达,改写回调函数
Generator 函数最大的特点就是可以交出函数的执行权,即暂停执行。
Generator 函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了

function showLoading() {
  console.log('show-loading');
}
function getData() {
  setTimeout(() => {
      console.log('get-data');
      hl.next();   // hide-loading
  }, 1000)
}
function hideLoading() {
  console.log('hide-loading');
}

function * handleLoading(){
  showLoading();
  yield getData();
  hideLoading();
}

var hl = handleLoading();
hl.next();   // show-loading   get-data

2. 控制流管理

传统写法,回调地狱

step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // Do something with value4
      });
    });
  });
});

采用Promise改写

Promise.resolve(step1)
  .then(step2)
  .then(step3)
  .then(step4)
  .then(function (value4) {
    // Do something with value4
  }, function (error) {
    // Handle any error from step1 through step4
  })
  .done();

使用Generator函数进一步改善优化
注意,这种做法,只适合同步操作,即所有的task都必须是同步的,不能有异步操作。如要控制异步操作流程继续往下看。

function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    var value5 = yield step4(value4);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}

3. 部署 Iterator 接口
利用 Generator 函数,可以在任意对象上部署 Iterator 接口。 详情参考

4. 作为数据结构
详情参考

如果对你有帮助了麻烦动动小指头点个赞,欢迎在下方评论区给我留言~~