ES6學習筆記--Generator和async函數

Generator

Generator 函數是一個狀態機,封裝了多個內部狀態。執行 Generator 函數會返回一個遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。html

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
};

var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

Generator函數調用後不會馬上執行,而是返回一個指向內部狀態的指針對象。
調用該對象的next()方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield語句(或return語句)爲止。
yield語句是暫停執行的標記,而next方法能夠恢復執行。
遍歷器對象的next方法的運行邏輯以下。node

(1)遇到yield語句,就暫停執行後面的操做,並將緊跟在yield後面的那個表達式的值,做爲返回的對象的value屬性值。git

(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield語句。github

(3)若是沒有再遇到新的yield語句(最後一個yield返回的對象的done爲false),就一直運行到函數結束,直到return語句爲止,並將return語句後面的表達式的值,做爲返回的對象的value屬性值(對象的done爲true)。shell

(4)若是該函數沒有return語句,則返回的對象的value屬性值爲undefined。json

yield句自己沒有返回值,或者說老是返回undefined。next方法能夠帶一個參數,該參數就會被看成上一個yield語句的返回值。api

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
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 }

for...of循環能夠自動遍歷Generator函數時生成的Iterator對象,且此時再也不須要調用next方法。
一旦next方法的返回對象的done屬性爲true,for...of循環就會停止,且不包含該返回對象,因此下面代碼的return語句返回的6,不包括在for...of循環之中。異步

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

Generator函數返回的遍歷器對象,還有一個return方法,能夠返回給定的值,而且終結遍歷Generator函數。async

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 }

若是return方法調用時,不提供參數,則返回值的value屬性爲undefined。函數

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

var g = gen();

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

若是Generator函數內部有try...finally代碼塊,那麼return方法會推遲到finally代碼塊執行完再執行。

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }

在一個 Generator 函數裏面執行另外一個 Generator 函數要用到yield*語句。

function* foo() {
  yield 'a';
  yield 'b';
}

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異步

異步任務的封裝

var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

Generator自動執行器

function run(fn) {
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }

  next();
}

var g = function* (){
  var f1 = yield readFile('fileA');
  var f2 = yield readFile('fileB');
  // ...
  var fn = yield readFile('fileN');
};

run(g);

基於 Promise 對象的自動執行

var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) return reject(error);
      resolve(data);
    });
  });
};

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
function run(gen){
  var g = gen();

  function next(data){
    var result = g.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

run(gen);

async 函數

async 函數,一句話,它就是 Generator 函數的語法糖。
上面的例子,寫成async函數,就是下面這樣:

var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) return reject(error);
      resolve(data);
    });
  });
};

var asyncReadFile = function (){
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
asyncReadFile();

async函數對 Generator 函數的改進,體如今如下四點。
(1)內置執行器。
(2)更好的語義。
(3)更廣的適用性。
(4)返回值是 Promise。
async函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。

async函數內部return語句返回的值,會成爲then方法回調函數的參數。

async function f() {
    return 'hello world';
}
f().then(v => console.log(v))

async函數內部報錯錯誤會致使返回的Promise對象變爲reject狀態,拋出的錯誤會被catch方法接受到

async function f() {
    throw new Error('this is an error');
}
f().then(
    v => console.log(v),
    e => console.log(e)
)

async函數返回的 Promise 對象,必須等到內部全部await命令後面的 Promise 對象執行完,纔會發生狀態改變,除非遇到return語句或者拋出錯誤。也就是說,只有async函數內部的異步操做執行完,纔會執行then方法指定的回調函數。

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

上面代碼中,函數getTitle內部有三個操做:抓取網頁、取出文本、匹配頁面標題。只有這三個操做所有完成,纔會執行then方法裏面的console.log。

await命令

正常狀況下,await命令後面是一個 Promise 對象。若是不是,會被轉成一個當即resolve的 Promise 對象。

async function f() {
  return await 123;
}

f().then(v => console.log(v))
// 123

上面代碼中,await命令的參數是數值123,它被轉成 Promise 對象,並當即resolve。

await命令後面的 Promise 對象若是變爲reject狀態,則reject的參數會被catch方法的回調函數接收到。

async function f() {
  await Promise.reject('出錯了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出錯了

只要一個await語句後面的 Promise 變爲reject,那麼整個async函數都會中斷執行。

async function f() {
  await Promise.reject('出錯了');
  await Promise.resolve('hello world'); // 不會執行
}

有時,咱們但願即便前一個異步操做失敗,也不要中斷後面的異步操做。這時能夠將第一個await放在try...catch結構裏面,這樣無論這個異步操做是否成功,第二個await都會執行。

async function f() {
  try {
    await Promise.reject('出錯了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

另外一種方法是await後面的 Promise 對象再跟一個catch方法,處理前面可能出現的錯誤。

async function f() {
  await Promise.reject('出錯了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出錯了
// hello world

若是有多個await命令,能夠統一放在try...catch結構中。

async function main() {
  try {
    var val1 = await firstStep();
    var val2 = await secondStep(val1);
    var val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

多個await命令後面的異步操做,若是不存在繼發關係,最好讓它們同時觸發。

//getFoo和getBar是兩個獨立的異步操做(即互不依賴),被寫成繼發關係。這樣比較耗時,由於只有getFoo完成之後,纔會執行getBar,徹底可讓它們同時觸發。
let foo = await getFoo();
let bar = await getBar();

// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
相關文章
相關標籤/搜索