1. 程式人生 > >ES6生成器函數generator

ES6生成器函數generator

$.ajax 等待 遍歷 fun 針對 狀態 應用 res -h

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

ES6生成器函數generator