ES6 (10):Generator

異步編程方案:(很實用滴)javascript

Generator 函數是一個普通函數,可是有兩個特徵。一是,function關鍵字與函數名之間有一個星號;二是,函數體內部使用yield表達式,定義不一樣的內部狀態(yield在英語裏的意思就是「產出」)。java

每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法能夠恢復執行編程

 

yield表達式只能用在 Generator 函數裏面,用在其餘地方都會報錯。數據結構

var arr = [1, [[2, 3], 4], [5, 6]];

var flat = function* (a) {
  a.forEach(function (item) {
    if (typeof item !== 'number') {
      yield* flat(item);
    } else {
      yield item;
    }
  });
};

for (var f of flat(arr)){
  console.log(f);
}

 由於是在forEach中(forEach 自己是一個函數,因此會報錯)異步

yield表達式若是用在另外一個表達式之中,必須放在圓括號裏面。ide

function* demo() {
  console.log('Hello' + yield); // SyntaxError
  console.log('Hello' + yield 123); // SyntaxError

  console.log('Hello' + (yield)); // OK
  console.log('Hello' + (yield 123)); // OK
}

 yield表達式用做函數參數或放在賦值表達式的右邊,能夠不加括號。函數式編程

function* demo() {
  foo(yield 'a', yield 'b'); // OK
  let input = yield; // OK
}

 

next方法參數:next方法能夠傳入一個參數,這個參數會被當作上一次yield執行時的初始值(若是不傳參數,默認是undefined異步編程

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

 

主要看第一個實例(b),第一次調用next這次yield 後的值爲6,第二次執行next時,此時設置yield爲12,也就是上面我標紅的地方,(yield (x + 1)) = 12, 因此y = 24,到第二行yield 時,此時yield 回傳爲8,當調用第三次next方法時,設置值爲13,標紅的地方 yield (y / 3) = 13, 因此z值爲13,而後到達return ,此時 上面執行結果就是,x 爲5,y爲24,z爲13,相加獲得42。函數

注意若是yield 後面是一個表達式,那麼初始值表明的是這個表達式的值,也就是yield緊跟着的。而上面的實例a由於在第二次調用next方法的時候沒有傳參,因此yield 的值默認爲undefined,因此值爲NaNthis

 

for...of : 由於Generator 運行時會生成iterator,因此能夠直接遍歷

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

 

遍歷對象:

function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);// 獲取對象的keys

  for (let propKey of propKeys) {// 循環執行,返回key與value
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}

 由於自己就會生成一個iterator 對象,因此能夠直接賦值到對象的Symbol.iterator接口上

function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

 

x.throw:拋出異常(記得用try catch捕獲)

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('內部捕獲', e);
  }
};

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕獲', e);
}
// 內部捕獲 a
// 外部捕獲 b

 catch只會捕獲一次,而且返回捕獲後下一次的yield 值

若是yield 都在try catch中,那麼不會返回。

 

 沒有返回2,由於yield 2 在try catch內(而且不會影響下一次的遍歷上例中在錯誤後繼續遍歷,輸出了4)。

 

 

Generator 函數體外拋出的錯誤,能夠在函數體內捕獲;反過來,Generator 函數體內拋出的錯誤,也能夠被函數體外的catch捕獲。

 

 

一旦 Generator 執行過程當中拋出錯誤,且沒有被內部捕獲,就不會再執行下去了。若是此後還調用next方法,將返回一個value屬性等於undefineddone屬性等於true的對象,即 JavaScript 引擎認爲這個 Generator 已經運行結束了。

 

 

return:終結遍歷Generator

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

var g = gen();

g.next()        // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next()        // { value: undefined, done: true }

 

yield* 表達式:在 Generator 函數內部,調用另外一個 Generator 函數。

對比:

function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同於
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同於
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

 

 另外由於Generator 會生成一個遍歷器對象,因此,會有下面這樣的不一樣

function* inner() {
  yield 'hello!';
}

function* outer1() {
  yield 'open';
  yield inner();
  yield 'close';
}

var gen = outer1()
gen.next().value // "open" gen.next().value // 返回一個遍歷器對象
gen.next().value // "close"

function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}

var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"

 out1 在執行到第二行的時候返回的是一個遍歷器對象。

 

任何數據結構只要有 Iterator 接口,就能夠被yield*遍歷

 

yield*後面的 Generator 函數(沒有return語句時),等同於在 Generator 函數內部,部署一個for...of循環。(若是有return,那麼return的值不會被for ...of 偵測到,可是能夠做爲想代理它的Generator 函數返回數據。)

function* concat(iter1, iter2) {
  yield* iter1;
  yield* iter2;
}

// 等同於

function* concat(iter1, iter2) {
  for (var value of iter1) {
    yield value;
  }
  for (var value of iter2) {
    yield value;
  }
}

 

function* foo() {
  yield 2;
  yield 3;
  return "foo";
}

function* bar() {
  yield 1;
  var v = yield* foo(); console.log("v: " + v);
  yield 4;
}

var it = bar();

it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// "v: foo" // {value: 4, done: false}
it.next()
// {value: undefined, done: true}

 

對象屬性:

let obj = {
  * myGeneratorMethod() {
    ···
  }
};
let obj = { myGeneratorMethod: function* () {  // ··· } };

 

協程:相比線程,因爲 JavaScript 是單線程語言,只能保持一個調用棧。引入協程之後,每一個任務能夠保持本身的調用棧。這樣作的最大好處,就是拋出錯誤的時候,能夠找到原始的調用棧。不至於像異步操做的回調函數那樣,一旦出錯,原始的調用棧早就結束。

若是將 Generator 函數看成協程,徹底能夠將多個須要互相協做的任務寫成 Generator 函數,它們之間使用yield表達式交換控制權。同一時間能夠有多個線程處於運行狀態,可是運行的協程只能有一個,其餘協程都處於暫停狀態。此外,普通的線程是搶先式的,到底哪一個線程優先獲得資源,必須由運行環境決定,可是協程是合做式的,執行權由協程本身分配

 (也就是說能夠本身控制)。

 

異步操做同步化表達:Generator 函數的一個重要實際意義就是用來處理異步操做,改寫回調函數。

function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();
// 加載UI
loader.next()

// 卸載UI
loader.next()

 不用回調函數一個一個套了,看着多清晰~~

 

控制流:

通常若是異步操做,咱們之前會這樣作,一直套

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();

 已經很清晰了。

 

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
  }
}
scheduler(longRunningTask(initialValue));

function scheduler(task) {
  var taskObj = task.next(task.value);
  // 若是Generator函數未結束,就繼續調用
  if (!taskObj.done) { task.value = taskObj.value scheduler(task); }
}

 

 

更加趨於函數式編程。

相關文章
相關標籤/搜索