首頁  >  文章  >  web前端  >  Javascript中的Generator函數和yield關鍵字

Javascript中的Generator函數和yield關鍵字

高洛峰
高洛峰原創
2016-11-15 14:48:451326瀏覽

在Javascript中,大家討論的最多的就是非同步程式設計的操作,如何避免回呼的多次巢狀。非同步操作的回調一旦嵌套很多,不僅程式碼會變的臃腫,還很容易出錯。各種各樣的非同步程式設計解決方案也被不斷提出,例如大家所熟知的Promise,co等等。今天所講的Generator和yield就是和非同步程式設計有關,可以幫助我們把非同步程式設計同步化。

Generator簡介

Generator在形式上和函數差不多,只是在function和函數名之間多了一個*。 Generator內部必須使用yield關鍵字。例如:

function * gen(){
  var result1 = yield 'hello';
  var result2 = yield 'world';
  return result1 + result2;
}

當呼叫Generator函數時,並不會執行函數內部的程式碼,而是傳回一個遍歷器,該遍歷器包含一個next方法。每次執行next方法,Generator函數體會開始執行,直到遇到yield語句,執行該語句並在此暫停。用法如下:

var g = gen();
g.next(1);
//{value : 'hello', done : false}
g.next(2);
//{value : 'world', done : false}
g.next();
//{value : 3, done: true}
g.next();
//{value : undefined, done: true}

呼叫next方法會傳回一個對象,這個物件包含兩個屬性,value和done,value即是目前yield語句的值。 done表示Generator函數體是否被執行完。 next方法同時接受一個參數,這個參數會作為yield語句的回傳值,可以被後面的程式所使用。當程式執行完或遇到return語句,value即為函數體的回傳值,done就變成了true。至此,如果再執行next方法,value就為undefined, done還是true。

Generator在遍歷中的應用

在js中,我們要遍歷一個數組,我們可以用for...of這樣的語句來進行遍歷,這其實也是因為數組中包含了一個Generator遍歷器。如果我們的自己定義的物件也包含一個遍歷器,我們也就可以透過for...of等遍歷語句來遍歷自訂的物件。這個遍歷器被存在著Symbol.iterator屬性中。

var myArray = {
  0: '你',
  1: '的',
  2: '名字',
  length: 3
};

myArray[Symbol.iterator] = function * (){
  for(var i = 0; i < this.length; i++) {
    yield this[i];
  }
};

for(var item of myArray) {
  console.log(item);
}
//你
//的
//名字

Generator在非同步編程中的應用

Javascript的核心就是非同步編程,每個非同步操作都會提供一個callback回呼函數來傳回執行的結果。假設我們有幾個操作,後一個操作依賴前一個操作的結果,如果採用回調的方式:

step1(function(value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3)) {
      //some code
    }
  });
})

這樣的程式碼一單回呼的嵌套變多,會讓程式變的非常難理解,同時也很容易出錯。我們要做的就是將回調變的扁平化。 Promise物件就是這樣的功能,將上述的操作Promise化:

step1().then(function(value1){
  return step2(value1);
}).then(function(value2){
  return step3(value2);
}).then(function(){
  //some code
})

我們可以看到嵌套變少了,但是這並不是最理想的解決方案,如果我們能將異步操作變成同步操作那樣,即沒了嵌套,程式也會變的好理解。 Generator函數就給我們提供的這樣的機會。

function *workflow(){
  var value1 = yield step1();
  var value2 = yield step2();
  var value3 = yield step3();
  //some code
}

這樣就是我們希望結果,非同步程式設計了同步程式設計的形式。我們接下來要做的是讓這個Generator執行起來,所以我們需要一個執行器。 co就是一個執行器,讓Generator自動執行。

co(function *workflow(){
  var value1 = yield step1();
  var value2 = yield step2();
  var value3 = yield step3();
  //some code
});

co有個限制,yield語句後面跟的只能是Promise物件或Thunk函數,關於co更詳細的介紹,可以參考阮老師的文章co 函式庫的意義和用法。然而這樣的方法依然需要依賴外在的函式庫函數,於是ES6中提出了async和await關鍵字。 async和await其實就是Generator的語法糖。只是它自帶執行器。將上面的程式碼改寫成async形式:

async function workflow(){
  var value1 = await step1();
  var value2 = await step2();
  var value3 = await step3();
  //some code
}

var result = workflow();

async没有了co的限制。await关键字后面可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn