ES6生成器函式generator

龍恩0707發表於2017-08-13

ES6生成器函式generator

    generator是ES6新增的一個特殊函式,通過 function* 宣告,函式體內通過 yield 來指明函式的暫停點,該函式返回一個迭代器,並且函式執行到 yield語句前面暫停,之後通過呼叫返回的迭代器next()方法來執行yield語句。
如下程式碼演示:

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

如上程式碼:generator函式的呼叫方法與普通函式一樣,也是在函式名後面加上一對圓括號。不同的是,呼叫generator函式後,該函式並不執行,返回的也不是函式執行的結果,而是一個指向內部狀態的指標物件,我們可以通過呼叫next方法,使指標移向下一個狀態。

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

如上程式碼,返回的迭代器每次呼叫next會返回一個物件 {value: "xxx", done: true/false}, value是返回值,done表示的是否達到迭代器的結尾,true是代表已經到了結尾,false代表沒有。

我們可以通過 返回的物件的done是否為true來判斷生成器是否執行結束,通過返回物件的value可以得到yield的返回值。如果返回物件的done值為true的話,那麼該值value就為undefined。
迭代器next()方法還可以傳入一個引數,這個引數會作為上一個yield語句的返回值,如果不傳引數,yield語句中生成器函式內的返回值是 undefined。

如下程式碼演示:

function* test(x) {
  let y = yield x;
  console.log(1111)
  yield y;
}
var t = test(3);
console.log(t.next()); // {value:3, done: false}
console.log(t.next()); // {value: undefined, done: false}
console.log(t.next()); // {value: undefined, done: true}

下面我們向第二個next方法傳入值為5, 因此y的值被賦值為5,如下程式碼:

function* test(x) {
  let y = yield x;
  yield y;
}
var t = test(3);
console.log(t.next()); // {value:3, done: false}
console.log(t.next(5)); // {value: 5, done: false}

我們也可以通過 for...of迴圈可以自動遍歷 generator函式生成物件,此時不需要呼叫next方法。如下程式碼:

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 4+1;
  yield 6;
}
for (let v of foo()) {
  console.log(v); // 1, 2, 3, 4, 5, 6
}

1-2 異常處理 

下面是一個簡單的列子來捕獲generator裡的異常

function* error() {
  try {
    throw new Error('error');
    yield 11;
  } catch(err) {
    console.log(err);
  }
}
var it = error();
it.next();

1-3 Generators的非同步的應用

    Generators 最主要的特點是單執行緒執行,同步風格程式碼編寫,同時又允許將 程式碼非同步的特性隱藏在程式的實現細節中。通過generators函式,我們將程式具體的實現細節從非同步程式碼中抽離出來,可以很好的實現了功能和關注點的分離。
下面是使用ajax的demo,非同步ajax請求,當請求完成後的資料,需要將返回的資料值傳遞給下一個請求去,也就是說下一個請求要依賴於上一個請求返回的資料依次傳遞下去程式碼如下:

function requestURL(url, cb) {
  // ajax 請求, 請求完成後 呼叫cb函式
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(d);
    }
  })
}
var url = 'http://httpbin.org/get';
requestURL(url, function(res) {
  console.log(res);
  var url2 = 'http://httpbin.org/get?origin='+res.origin;
  requestURL(url2, function(res2) {
    console.log(res2);
  });
});

如上程式碼,我們看到非同步ajax請求依次巢狀回撥。
下面我們可以使用 generator去重新實現一下,如下程式碼:

function requestURL(url, cb) {
  // ajax 請求, 請求完成後 呼叫cb函式
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(d);
    }
  })
}
var url = 'http://httpbin.org/get';
function request(url) {
  requestURL(url, function(res) {
    it.next(res);
  })
}
function* main() {
  var res1 = yield request(url);
  console.log(res1);
  var res2 = yield request(url + '?origin='+res1.origin);
  console.log(res2);
}

var it = main();
it.next(); 

如上程式碼是使用Generator實現的非同步程式碼請求;實現思路如下:
首先 requestURL 函式,發一個ajax請求,請求成功後呼叫回撥函式 cb;request函式是封裝requestURL函式,請求成功後回撥,呼叫it.next()方法,
將指標指向下一個yield,main函式是請求啟動程式碼,當 var it = main()時候,會呼叫main函式,但是隻是會呼叫,但是並不會執行程式碼,因此需要 it.next()方法執行第一個 yield request(url) 方法,因此會呼叫request方法,再繼續呼叫requestURL方法,最後會輸出res1, 然後 requestURL方法的回撥,又呼叫 
it.next()方法,因此會執行main裡面yield request的第二句程式碼,程式碼執行和第一次一樣,最後輸出res2.

上面的程式碼有一個地方需要理解的是,ajax請求成功以後 會繼續呼叫 it.next()方法,那麼成功後的資料如何傳遞到 res1呢?我們看到沒有return語句返回的?
這是因為當 在ajax的callback呼叫的時候,它首先會傳遞ajax返回的結果。返回值傳送到我們的generator時候,var res1 = yield request(url);給暫停了,然後在呼叫it.next()方法,會執行下一個請求。
1-4 Generator+Promise 實現非同步請求

上面的generator程式碼可以做一些簡單的非同步請求,但是會受到一些限制,比如如下:
    1. 沒有明確的方法來處理請求error;ajax請求有時候會超時或失敗的情況下,這個時候我們需要使用generator中的it.throw(),同時還需要使用try catch來處理錯誤的邏輯。
   2. 一般情況下我們請求不會涉及到並行請求,但是如果有的需求是並行的話,比如說同時發2個或者多個ajax請求的話,由於 generator yidle機制都是逐步暫停的,無法同時執行另一個或多個任務,
因此,generator不太容易操作多個任務。

我們現在使用promise修改下 request的方法,讓yield返回一個promise,所有程式碼如下:

function requestURL(url, cb) {
  // ajax 請求, 請求完成後 呼叫cb函式
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(d);
    }
  })
}
var url = 'http://httpbin.org/get';
function request(url) {
  return new Promise((resolve, reject) => {
    requestURL(url, resolve);
  });
}
function runGenerator(cb) {
  var it = cb(),
    ret;
  // 非同步遍歷generator
  (function iterator(val){
    console.log(111)
    console.log(val);  // undefined
    // 返回一個promise
    ret = it.next(val); 
    console.log(ret);  // {value: Promise, done: false}
    if (!ret.done) {
      if('then' in ret.value) {
        // 等待接受promise
        ret.value.then(iterator);
      } else {
        setTimeout(function() {
          iterator(ret.value);
        }, 0)
      }
    }
  })();
}
runGenerator(function *main() {
  var res1 = yield request(url);
  console.log(res1);
  var res2 = yield request(url + '?origin='+res1.origin);
  console.log(res2);
});

上面程式碼 :

function request(url) {
  return new Promise((resolve, reject) => {
    requestURL(url, resolve);
  });
}

當ajax請求完成後,會返回這個promise,同時接收 yield request(url); 然後通過next()方法恢復generator執行,並把他們傳遞下去,第一次執行iterator方法後,val值為undefined,然後呼叫 ret = it.next(val); 返回一個promise ,繼續列印下 console.log(ret); 返回如下: {value: Promise, done: false}
雖然上面程式碼執行正常,但是我們還未處理程式碼異常的情況下,因此下面處理下程式碼異常的情況下:

function requestURL(url, cb) {
  // ajax 請求, 請求完成後 呼叫cb函式
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(null, d);
    },
    error: function(err) {
      cb(err, text);
    }
  })
}
var url = 'http://httpbin.org/get';
function request(url) {
  return new Promise((resolve, reject) => {
    requestURL(url, function(err, text){
      if (err) {
        reject(err);
      } else {
        resolve(text);
      }
    });
  });
}
function runGenerator(cb) {
  var it = cb(),
    ret;
  // 非同步遍歷generator
  (function iterator(val){
    console.log(111)
    console.log(val);  // undefined
    // 返回一個promise
    ret = it.next(val); 
    console.log(ret);  // {value: Promise, done: false}
    if (!ret.done) {
      if('then' in ret.value) {
        // 等待接受promise
        ret.value.then(iterator);
      } else {
        setTimeout(function() {
          iterator(ret.value);
        }, 0)
      }
    }
  })();
}

runGenerator(function *main() {
  try {
    var res1 = yield request(url);
  } catch(err) {
    console.log(err);
    return;
  }
  console.log(res1);
  try {
    var res2 = yield request(url + '?origin='+res1.origin);
  } catch(err) {
    console.log(err);
    return;
  }
  console.log(res2);
})

第一步處理異常的情況下 程式碼如上已經完成了,現在來解決第二步 使用Promise + generator 解決多個請求同時執行,需要使用Promise.all()方法來解決。
如下程式碼:

function requestURL(url, cb) {
  // ajax 請求, 請求完成後 呼叫cb函式
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(null, d);
    },
    error: function(err) {
      cb(err, text);
    }
  })
}
var url = 'http://httpbin.org/get';
function request(url) {
  return new Promise((resolve, reject) => {
    requestURL(url, function(err, text){
      if (err) {
        reject(err);
      } else {
        resolve(text);
      }
    });
  })
  // 獲取返回值後
  .then(function(d) {
    console.log('dddddd');
    console.log(d);
    return d
  });
}
function runGenerator(cb) {
  var it = cb(),
    ret;
  // 非同步遍歷generator
  (function iterator(val){
    // 返回一個promise
    ret = it.next(val); 
    if (!ret.done) {
      if('then' in ret.value) {
        // 等待接受promise
        ret.value.then(iterator);
      } else {
        setTimeout(function() {
          iterator(ret.value);
        }, 0)
      }
    }
  })();
}

runGenerator(function *main() {
  var res1 = yield Promise.all([
    request(url),
    request(url)
  ]);
  console.log(2222)
  console.log(res1);
  var result = yield request(url + '?origin='+res1[0].origin);
  console.log(result);
})

相關文章