es6-生成器Generator

迭代器是es6中一個重要的概念,不少新特性都是基於迭代器概念而鋪開的。爲了更加方便的建立自定義的迭代器,es6引入了生成器 (Generator) 的概念。它是一種能夠返回迭代器的特殊函數。有了生成器及它的特性可讓咱們建立更加簡潔的異步代碼。git

基本概念

經過 function 關鍵字後面的星號(*)來表示,函數體用 yield 關鍵字來控制迭代器每次 next() 返回結果:es6

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() 方法纔會繼續執行。github

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

let iterator = createIterator();

iterator.next(); // 1
iterator.next(); // 2 
複製代碼

yield 關鍵字只能在生成器內部使用,嵌套的函數也不行:npm

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

在對象裏面定義生成器函數:異步

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

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

注意,生成器函數不支持箭頭函數寫法ide

高級迭代器用法

迭代器傳參

能夠給迭代器 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。post

注意,第一次調用 next() 傳入參數會被忽略。運行的流程能夠以下圖:fetch

在迭代器拋出錯誤

迭代器除了 next() 方法,還有利用 throw() 拋出一個Error對象。錯誤被拋出後,生成器函數的後面代碼會中止執行:ui

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.next(data);
    } else {
      next(result.value);
    }
  }

  next();
}

run(gen);
複製代碼

其實,上面的自動執行生成器函數的方法只適用於回調形式的異步任務,還要考慮返回Promise 形式的異步任務,而且要處理異常的狀況。這裏推薦一個npm庫,該庫已經兼容全部狀況自動執行 Generator函數。 >>>github地址

參考

ES6 系列之 Generator 的自動執行

>>>原文地址

相關文章
相關標籤/搜索