ES6筆記(5)-- Generator生成器函數

 

系列文章 -- ES6筆記系列html

 

接觸過Ajax請求的會遇到過異步調用的問題,爲了保證調用順序的正確性,通常咱們會在回調函數中調用,也有用到一些新的解決方案如Promise相關的技術。ajax

在異步編程中,還有一種經常使用的解決方案,它就是Generator生成器函數。顧名思義,它是一個生成器,它也是一個狀態機,內部擁有值及相關的狀態,生成器返回一個迭代器Iterator對象,咱們能夠經過這個迭代器,手動地遍歷相關的值、狀態,保證正確的執行順序。編程

 

1、簡單使用

1. 聲明

Generator的聲明方式相似通常的函數聲明,只是多了個*號,而且通常能夠在函數內看到yield關鍵字數組

function* showWords() {
    yield 'one';
    yield 'two';
    return 'three';
}

var show = showWords();

show.next() // {done: false, value: "one"}
show.next() // {done: false, value: "two"}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}

如上代碼,定義了一個showWords的生成器函數,調用以後返回了一個迭代器對象(即show)promise

調用next方法後,函數內執行第一條yield語句,輸出當前的狀態done(迭代器是否遍歷完成)以及相應值(通常爲yield關鍵字後面的運算結果)網絡

每調用一次next,則執行一次yield語句,並在該處暫停,return完成以後,就退出了生成器函數,後續若是還有yield操做就再也不執行了異步

2. yield和yield*

有時候,咱們會看到yield以後跟了一個*號,它是什麼,有什麼用呢?異步編程

相似於生成器前面的*號,yield後面的星號也跟生成器有關,舉個大栗子:函數

function* showWords() {
    yield 'one';
    yield showNumbers();
    return 'three';
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: showNumbers}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}

增添了一個生成器函數,咱們想在showWords中調用一次,簡單的 yield showNumbers()以後發現並無執行函數裏面的yield 10+1url

由於yield只能原封不動地返回右邊運算後值,但如今的showNumbers()不是通常的函數調用,返回的是迭代器對象

因此換個yield* 讓它自動遍歷進該對象

function* showWords() {
    yield 'one';
    yield* showNumbers();
    return 'three';
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: 11}
show.next() // {done: false, value: 12}
show.next() // {done: true, value: "three"}

要注意的是,這yield和yield* 只能在generator函數內部使用,通常的函數內使用會報錯

function showWords() {
    yield 'one'; // Uncaught SyntaxError: Unexpected string
}

雖然換成yield*不會直接報錯,但使用的時候仍是會有問題,由於’one'字符串中沒有Iterator接口,沒有yield提供遍歷

function showWords() {
    yield* 'one'; 
}

var show = showWords();

show.next() // Uncaught ReferenceError: yield is not defined

在爬蟲開發中,咱們經常須要請求多個地址,爲了保證順序,引入Promise對象和Generator生成器函數,看這個簡單的栗子:

var urls = ['url1', 'url2', 'url3'];

function* request(urls) {
    urls.forEach(function(url) {
        yield req(url);
    });

//     for (var i = 0, j = urls.length; i < j; ++i) {
//         yield req(urls[i]);
//     }
}

var r = request(urls);
r.next();

function req(url) {
    var p = new Promise(function(resolve, reject) {
        $.get(url, function(rs) {
            resolve(rs);
        });
    });

    p.then(function() {
        r.next();
    }).catch(function() {

    });
}

上述代碼中forEach遍歷url數組,匿名函數內部不能使用yield關鍵字,改換成註釋中的for循環就好了

3. next()調用中的傳參

參數值有注入的功能,可改變上一個yield的返回值,如

function* showNumbers() {
    var one = yield 1;
    var two = yield 2 * one;
    yield 3 * two;
}

var show = showNumbers();

show.next().value // 1
show.next().value // NaN
show.next(2).value // 6

第一次調用next以後返回值one爲1,但在第二次調用next的時候one實際上是undefined的,由於generator不會自動保存相應變量值,咱們須要手動的指定,這時two值爲NaN,在第三次調用next的時候執行到yield 3 * two,經過傳參將上次yield返回值two設爲2,獲得結果

另外一個栗子:

因爲ajax請求涉及到網絡,很差處理,這裏用了setTimeout模擬ajax的請求返回,按順序進行,並傳遞每次返回的數據

 1 var urls = ['url1', 'url2', 'url3'];
 2 
 3 function* request(urls) {
 4     var data;
 5 
 6     for (var i = 0, j = urls.length; i < j; ++i) {
 7         data = yield req(urls[i], data);
 8     }
 9 }
10 
11 var r = request(urls);
12 r.next();
13 
14 function log(url, data, cb) {
15     setTimeout(function() {
16         cb(url);
17     }, 1000);
18     
19 }
20 
21 
22 function req(url, data) {
23     var p = new Promise(function(resolve, reject) {
24         log(url, data, function(rs) {
25             if (!rs) {
26                 reject();
27             } else {
28                 resolve(rs);
29             }
30         });
31     });
32 
33     p.then(function(data) {
34         console.log(data);
35         r.next(data);
36     }).catch(function() {
37         
38     });
39 }

達到了按順序請求三個地址的效果,初始直接r.next()無參數,後續經過r.next(data)將data數據傳入

注意代碼的第16行,這裏參數用了url變量,是爲了和data數據作對比

由於初始next()沒有參數,如果直接將url換成data的話,就會由於promise對象的數據判斷 !rs == undefined 而reject

因此將第16行換成 cb(data || url);

經過模擬的ajax輸出,可瞭解到next的傳參值,第一次在log輸出的是 url = 'url1'值,後續將data = 'url1'傳入req請求,在log中輸出 data = 'url1'值

 

4. for...of循環代替.next()

除了使用.next()方法遍歷迭代器對象外,經過ES6提供的新循環方式for...of也可遍歷,但與next不一樣的是,它會忽略return返回的值,如

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

for (var n of show) {
    console.log(n) // 1 2
}

此外,處理for...of循環,具備調用迭代器接口的方法方式也可遍歷生成器函數,如擴展運算符...的使用

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

[...show] // [1, 2, length: 2]

5. 更多使用

更多使用可參考 MDN - Generator

相關文章
相關標籤/搜索