ES6系列之Iterator &Generator & Async

Iterator

JavaScript原有的四種表示'集合'的數據結構,Object、Array、Set、Map。shell

遍歷器(Iterator)是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署了Iterator接口,就能夠完成遍歷操做。編程

Iterator 的做用有三個:promise

  1. 爲各類數據結構,提供一個統一的、簡便的訪問接口;
  2. 使得數據結構的成員可以按某種次序排列;
  3. ES6 創造了一種新的遍歷命令for...of循環,Iterator 接口主要供for...of消費。

遍歷器原理

遍歷器提供了一個指針,指向當前對象的某個屬性,使用next方法,就能夠將指針移動到下一個屬性。next方法返回一個包含value和done兩個屬性的對象。其中,value屬性是當前遍歷位置的值,done屬性是一個布爾值,表示遍歷是否結束。
原生具有 Iterator 接口的數據結構以下:數據結構

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數的 arguments 對象
  • NodeList 對象

對於原生部署 Iterator 接口的數據結構,不用本身寫遍歷器生成函數,for...of循環會自動遍歷它們。其餘數據結構(主要是對象)的 Iterator 接口,都須要本身在Symbol.iterator屬性上面部署,這樣纔會被for...of循環遍歷。併發

class RangeIterator {
    constructor(start, stop){
        this.value = start;
        this.stop = stop;
    }
    
    [Symbol.iterator]{return this;}
    
    next(){
        let value = this.value;
        if(value < this.stop){
            this.value++;
            return {done : false, value : value};
        }
        return {done : true, value : undefined};
    }
}

function range(start, stop){
    return new RangeIterator(start, stop);
}

for(let v of range(0,3)) {
    console.log(value);  // 0 1 2
}

Generator

Generator 函數是 ES6 提供的一種異步編程解決方案。異步

yield 表達式

因爲 Generator 函數返回的遍歷器對象,只有調用next方法纔會遍歷下一個內部狀態,因此其實提供了一種能夠暫停執行的函數。yield表達式就是暫停標誌。async

遍歷器對象的next方法的運行邏輯以下。異步編程

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

(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield表達式。post

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

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

須要注意的是,yield表達式後面的表達式,只有當調用next方法、內部指針指向該語句時纔會執行,所以等於爲 JavaScript 提供了手動的「惰性求值」(Lazy Evaluation)的語法功能。

Generator 函數能夠不用yield表達式,這時就變成了一個單純的暫緩執行函數。
yield表達式只能用在 Generator 函數裏面,用在其餘地方都會報錯。

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

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 方法的參數

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

與 Iterator 接口的關係

因爲 Generator 函數就是遍歷器生成函數,所以能夠把 Generator 賦值給對象的Symbol.iterator屬性,從而使得該對象具備 Iterator 接口。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

實例方法

Generator.prototype.return()

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

Generator.prototype.throw()

Generator 函數返回的遍歷器對象,都有一個throw方法,能夠在函數體外拋出錯誤,而後在 Generator 函數體內捕獲。

yield* 表達式

若是在 Generator 函數內部,調用另外一個 Generator 函數,默認狀況下是沒有效果的。
這個就須要用到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 函數

若是一個對象的屬性是 Generator 函數,能夠簡寫成下面的形式。

let obj = {

  • myGeneratorMethod() {
    ···

}
};

Generator 函數的this

Generator 函數老是返回一個遍歷器,ES6 規定這個遍歷器是 Generator 函數的實例,也繼承了 Generator 函數的prototype對象上的方法。

function* g() {}

g.prototype.hello = function () {
  return 'hi!';
};

let obj = g();

obj instanceof g // true
obj.hello() // 'hi!'

上面代碼代表,Generator 函數g返回的遍歷器obj,是g的實例,並且繼承了g.prototype。可是,若是把g看成普通的構造函數,並不會生效,由於g返回的老是遍歷器對象,而不是this對象。

function* g() {
  this.a = 11;
}

let obj = g();
obj.next();
obj.a // undefined

上面代碼中,Generator 函數g在this對象上面添加了一個屬性a,可是obj對象拿不到這個屬性。

Async

async 函數是 Generator 函數的語法糖。
async函數就是將 Generator 函數的星號(*)替換成async,將yield替換成await,僅此而已。

const fs = require('fs');

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

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

寫成async函數,就是下面這樣。

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

async函數對 Generator 函數的改進,體如今如下四點。

  1. 內置執行器。
    Generator 函數的執行必須靠執行器,因此纔有了co模塊,而async函數自帶執行器。也就是說,async函數的執行,與普通函數如出一轍,只要一行。
    asyncReadFile();
  2. 更好的語義。
    async和await,比起星號和yield,語義更清楚了。async表示函數裏有異步操做,await表示緊跟在後面的表達式須要等待結果。
  3. 更廣的適用性。
    co模塊約定,yield命令後面只能是 Thunk 函數或 Promise 對象,而async函數的await命令後面,能夠是 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操做)。
  4. 返回值是 Promise。
    async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你能夠用then方法指定下一步的操做。

await 命令

正常狀況下,await命令後面是一個 Promise 對象。若是不是,就返回對應的值。
一、await命令後面的Promise對象,運行結果多是rejected,因此最好把await命令放在try...catch代碼塊中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另外一種寫法
async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}

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

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

// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面兩種寫法,getFoo和getBar都是同時觸發,這樣就會縮短程序的執行時間。
三、await命令只能用在async函數之中,若是用在普通函數,就會報錯。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 報錯
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

四、若是確實但願多個請求併發執行,可使用Promise.all方法。當三個請求都會resolved時,下面兩種寫法效果相同。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的寫法
async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

注意事項

async:

  • async函數返回promise對象
  • 沒有返回值 ,返回Promise.resolve(undefined)
  • 一旦遇到await命令就會先返回,等到異步操做完成,再接着執行後面的語句
  • 任何一個await語句的promise對象變成reject狀態,async會中斷執行

await:

  • await只能在async內使用
  • 異常處理,try catch
相關文章
相關標籤/搜索