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


 

系列文章 -- ES6筆記系列

 

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

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

 

一、簡單使用

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)

調用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+1

因為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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM