ES6新特性 iterators and Generators

ES6新特性 iterators and Generators

ES6中引入了許多新特性,目前大量的JavaScript項目已經使用了ES6來進行開發,那麼熟悉這些新的特性是十分必要的,例如Redux-Saga中大量的使用了Iterator和generator。這篇文章總結和介紹一下ES6中的Iterator和Generator。chrome

iterators and Generators

第一個問題什麼是iterator?答案很簡單, Iterator是一個object,可是含有特定的接口,它有next method能夠返回一個result object,這個result object有兩個屬性第一個是value,表明這個迭代的值, 第二個是done,表明迭代是否結束。若是咱們本身來簡單實現一個Iterator,它是這樣的。json

function createIterator(items) {
    var i = 0;
    return {
        next : function () {

            var done = i >= (items.length)
            var value = items[i++]

            return {
                done: done,
                value: value
            }
        }
    }
}

const items = [1,2,3]
const iteratorA = createIterator(items)
iteratorA.next() // {result:1, done: false}

那麼Generator又是什麼?Generator 是一個函數能夠產生iterator。Generator函數用function關鍵字後邊帶*來表示。在函數定義上使用yield關鍵字來表示next方法調用時返回的值。例如異步

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

let iterator = createIterator();
console.log(iterator.next().value);  //1
console.log(iterator.next().value);  //2
console.log(iterator.next().value);  //3

iterables

上邊介紹了什麼是Iterator,什麼是generator,下邊再介紹一個概念iterable。iterable是一個有Symbol.iterator屬性的object。這個symbol指向一個generator函數,這個函數返回關於這個對象的iterator。在ES6中全部的集合類對象(array, set, maps)和字符串都是iterable,而且有本身默認的iterator。當咱們在使用 for-of時候其實是利用了這些對象上的iterator,每次調用了next方法,將返回的result上的value返回回來。函數

let values = [1, 2, 3];
for (let num of values) { 
    console.log(num);
}

例如這段簡單的代碼,實際上調用了values上的iterator的next方法,將result上的value拿出來賦給num。既然是這樣咱們能夠採用這樣的方法來得到默認的iterator。ui

let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();

在ES6中對於集合類型的Object,其上定義了一些內置的iterator,分別是;code

  • entries() - 返回一個返回key-value pair的iterator對象

  • values() - 返回一個返回collection對應值的iterator // chrome not supported
    MDN接口

  • keys() - 返回一個返回collecttion對應key的iteratorip

以上就是iterator和generator的一些基本概念,下邊咱們來看一下一些高階應用。ci

向iterator中傳遞參數

上邊的例子中咱們在調用iterator的next方法都是無參數調用的,可是咱們一樣能夠向next方法中傳遞參數。例如這樣。

function* createIterator() {
    let first = yield 1;
    let second = yield first + 2;
    yield second + 3;
}

let i= createItreator()

i.next() // {value:1 done: false}
i.next(5) // {value: 7 done: false}
i.next(3) // {value: 6 done: false}

咱們看上邊這個例子,在第二次調用中咱們傳進去了5,返回值是7,這個傳進去的參數能夠理解爲上一次yield的返回值。注意yield自己是不返回任何值的,它只向外部產生值。若是咱們查看yield在英語詞典中的意思,produce or generate (a result, gain, or financial return 因此yield的值是向外產生值。因此在第一次next後 first的值依舊是undefined。可是向next中傳遞參數,這個參數表明咱們想要上一次yield在generator函數中的值。因此在第二次next後 返回值的value就是7(5+2)了。第三例子同理。因此基於上邊的緣由咱們向第一個next函數中傳入任何值都是沒有意義的。咱們變化一下再看

function* createIterator() {
    yield 1;
    let first;
    let second = yield first + 2;
    yield second + 3;
}

i.next() // {value:1 done: false}
i.next(5) // {value: NaN done: false}
i.next(3) // {value: 6 done: false}

在第二個next中咱們的返回是NaN, 爲何呢?這是由於first是Undefined,第一次的yield並無給first賦值。因此在yeild中的執行順序是每一次執行到相應的yield就完了,下次繼續向下執行。

在Iterator中Throw Error

在iterator中咱們能夠來throw error 來達到控制執行的目的。例如上邊一個例子。

function* createIterator() {
    let first = yield 1;
    let second = yield first + 2;
    yield second + 3;
}

let i= createItreator()

i.next() // {value:1 done: false}
i.next(5) // {value: 7 done: false}
i.throw(new Error('error')) // error thrown done is set to true after throw error

Generator function中的Return

一樣在generator 咱們可使用 return來返回。

function* createIterator() {
    yield 1;
    return;
    yield 2;
    yield 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

第一次next後已經結束了因此 咱們第二次next後done就已是true了。

Generator 和 Iterator的應用實例:Task Runner

咱們可使用generator和Iterator來實現一個task runner,可讓咱們不用手動的next,而是一次執行結束。代碼以下:

function run(taskDef) {
    let task = taskDef();
    let value = task.next()

    function step() {
        if (!value.done) {
            value = task.next(value.value)
            step()
        }
    }

    step()
}

run(function*(){
    let first = yield 1;
    let second = yield first + 3;
    yield second + 4;
})

上邊就是一個例子,這樣定義的run function就能夠順序執行這些generator定義的步驟。

實際上generator和Iterator最爲實際的做用是能夠控制異步函數的執行,下邊咱們能夠簡單的例子。

function run(taskDef) {
    let task = taskDef();
    let result = task.next()

    function step() {
        if (!result.done) {
            if (typeof result.value === "function") {
                result.value(function(err, data) {
                    if (err) {
                        console.log('err', err);
                        task.throw(err)
                        return
                    }
                    console.log('err', data);  
                    result = task.next(data);
                    step()
                    
                })
            } else {
                result = task.next(result.value)
                step()
            }
            
        }
    }

    step()
}

let fs = require("fs");

function readFile(filename) {
  return function (callback) {
    fs.readFile(filename, callback);
  };
}

run(function* () {
  let contents = yield readFile("abc.json");
  console.log(contents);
  console.log("Done");
});

首先咱們定義了一個task runner run function 在其中當發現result中的value是function的時候,就執行這個function, 而且在異步函數的callback中,當沒有error的時候執行下一步。

在看咱們的ReadFile function,fs模塊中的readFile是一個異步的函數,而在這裏咱們將其進行了封裝成爲一個新的函數。讓其返回一個function給在task runner中使用。那麼在咱們的generator函數中,咱們看上去的代碼就和同步的同樣了,先readfile,完成後將其輸出。這樣使用Iterator和generator能夠幫助咱們寫出一個比較好看的異步執行函數。

相關文章
相關標籤/搜索