Promise,Generator,Await/Async

上節中忘記講:Iterator接口和Generator函數的關係了,Symbol.iterator方法的最簡單的實現就是經過Generator函數:node

let myIterable = {
    [Symbol.iterator]:function* (){
        yield 1;
        yield 2;
        yield 3;
    }
}
let aa = [...myIterable];
aa    //[1, 2, 3]

用Generator函數寫Symbol.iterator方法幾乎不用部署任何代碼,只要yield命令給出每一步的返回值便可。接下來開始寫Generator函數吧,貌似沒有promise好用,看下圖,Await/Async函數是最膩害的,因此掌握最強的方法,你就能夠所向披靡了。編程

在瀏覽知乎的中一個有關node.js是用來作什麼的時候,看到有關ES6的新特性,而後就寫個博客吧!
Part One:promise
數組

Part Two:Generatorpromise

Generator函數是個狀態機,封裝多個內部狀態。執行Generator函數會返回一個遍歷器對象。安全

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

var hw = hello();
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函數,變量hw是一個指向hello函數內部狀態的指針對象。函數的調用必須調用遍歷器對象的next方法,使得指正移向下一個狀態。調用next方法時候,內部指針從函數頭部或者上一次停下來的地方開始執行。Generator函數是分段執行的,yield表達式是暫停執行的標記,next是恢復執行。數據結構

yield表達式:異步

在Generator函數內部,yield表達式只是暫停的標識。函數式編程

yield和return的類似之處在於都能返回緊跟在語句後面的表達式的值,區別在於每次遇到yeild,函數暫停執行,下一次再從該位置繼續向後執行。return不具有位置記憶的功能。Generator函數能夠返回一系列的值,由於內部能夠有多個yield。(Generator在英文中是「生成器」的意思)函數

若是Generator內部不用yield,就變成了一個單純的暫緩執行的函數。this

yield只能用在Generator函數內部,其餘地方是會報錯的!!!

在用for/of進行遍歷Generator時候,不須要再調用next方法,例如最上面的例子:Generator和Iterator接口的關係時,舉個栗子:

 

function* foo(){
    yield 1;
    yield 2;
    yield 3;
    return 4;
}
for(var i of foo()){
    console.log(i)
}
//1 2 3

 

一旦next方法的返回對象的done屬性爲true時,for/of循環就會停止,且不包含返回值,因此上面的return語句返回的4不包含在循環中。

這裏使用Generator函數寫了一個斐波那契數列的方法,遍歷時候將小於1000的數字打印出來:

function* fibonacci(){
    let [prev,curr] = [0,1];
    for(;;){
        [prev,curr] = [curr,prev+curr];
        yield curr;
    }
}
for(let n of fibonacci()){
    if(n>1000) break;
    console.log(n);
}

如何遍歷一個沒有Iterator接口的對象呢?上一節中給對象加上Symbol.iterator屬性就能夠,而在Generator當中呢?

function* objectEntires(obj){
    let propKeys = Reflect.ownKeys(obj);

    for(let propKey of propKeys){
        yield [propKey,obj[propKey]];
    }
}

let jane = {first: 'Jane',last: 'Ostin'};
for(let [key,value] of objectEntires(jane)){
    console.log(`${key}->${value}`)
}
VM4211:11 first->Jane
VM4211:11 last->Ostin

或者
let jane = {first: 'Jane',last: 'Ostin'};
function* objectEn(){
    let propKeys = Object.keys(this);
    for(let propKey of propKeys){
       yield [propKey,this[propKey]];
    }
}
jane[Symbol.iterator] = objectEn;
for(let [key,value] of jane){
    console.log(`${key}->${value}`)
}

函數當中涉及到Reflect,有時間寫一篇有關Reflect的博文~~~

拓展運算符(...),解構賦值和Array.from()等都是遍歷器接口,能夠將Generator函數做爲參數。

function* numbers(){
    yield 1;
    yield 2;
    return 3;
    yield 4;
}
let aa = [...numbers()]
let bb = Array.from(numbers());
let [x,y] = numbers();

aa   //[1, 2]
bb   //[1, 2]
x    //1
y    //2

這裏有個須要特別注意的點:一旦Generator執行過程當中拋出錯誤,且沒有被內部捕獲,就不會再執行下去了,若是還調用next方法,value就是undefined,done就是true了。即JavaScript引擎認爲這個Generator已經運行結束了。

注意:在Generator內部調用一個Generator函數是不會起做用的,因此就須要用到yield*表達式,用於在一個Generator函數 裏面執行另一個Generator函數。

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

function* bar(){
    yield 'x';
    yield* foo();
    yield 'y';
}
[...bar()]    //["x", "a", "b", "y"]

若是不用yield* 而使用yield後面跟一個Generator函數的話,返回的就是一個遍歷器對象了。在使用yield*時候(Generator裏面沒有return語句),就相似於for/of的簡寫形式。在有return語句時候呢?
任何數據結構只要有Iterator接口,就能夠被yield*遍歷。

let read = (function* (){
    yield 'hello';
    yield* 'hello';
}())
[...read]    //["hello", "h", "e", "l", "l", "o"]
function* AA(){
    yield 1;
    return 2;
}
function* BB(){
    yield 3;
    let re = yield* AA();
    console.log(re)
    yield 4;
}
[...BB()]
//2
// [3, 1, 4]

在運行內部有return的Generator函數時候,若是將它賦值爲一個變量指針,則打印結果是會有return的結果輸出。

出個題:若是遍歷二維數組?

const tree = ['a',['b','c'],['d','e']];
function* iterTree(arr){
    for(let item of arr){
       if(Array.isArray(item)){
          yield* iterTree(item);
       }else{
        yield item;
       } 
    }
}
[...iterTree(tree)]    // ["a", "b", "c", "d", "e"]

若是對象的屬性是Generator函數,能夠進行簡寫:

let obj = {
    * myMethod(){
        yield 1;
        yield 2;
    }
}
[...obj.myMethod()]    //[1, 2]

相似於==》
let obj = {
    myMethod:function* (){
        yield 1;
        yield 2;
    }
}
[...obj.myMethod()]    //[1, 2]

注意:Generator函數和普通構造函數的區別?

相同點:實例化後的Generator函數對象,會繼承Generator函數的原型上的方法,這點跟構造函數蕾西。

區別:Generator函數返回的是遍歷器對象,而不是this對象,因此函數上的固有方法,實例是不繼承的,也不能new一個Generator對象,會報錯。

如何將Generator函數返回一個正常的實例對象,既可使用next方法,也能夠得到正常的this對象?例如:F是一個Generator函數,能夠將F內部的this對象綁定obj對象,而後調用next方法,返回Iterator對象,給obj上面添加屬性,將全部內部的屬性都綁定在obj對象上面,所以obj對象也就是F的實例了。

如何將obj和f統一?

能夠將F的this指向它本身的原型對象。以下:

function* F(){
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
}


var f = F.call(F.prototype);
f.next();
f.next();
f.next();
console.log(f.a,f.b,f.c);  //1,2,3

就是將Generator函數裏面的this指向了它自己的原型對象上面,在調用了next方法以後給原型上面添加屬性。

如何讓f是能夠用構造函數new出來的對象,還可使用Generator函數的next方法?

function* F(){
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
}

function gen(){
    return F.call(F.prototype);
}
var f = new gen();
f.next();
f.next();
f.next();
console.log(f.a,f.b,f.c);
VM910:14 1 2 3

就是真正的再新建一個構造函數,裏面return F的實例,而後就能夠new一個對象並使用Generator的方法,還有原型上面的屬性了。

用Generator函數 實現一個狀態機就更加簡潔化,並且比較安全(狀態不會給非法篡改),更加符合函數式編程的思想。

var clock = function* (){
    while(true){
        console.log("Tick!");
        yield;
        console.log("Tock!")
        yield;
    }
}
var c = clock();
c.next()  //Tick!
c.next()  //Tock!
c.next()  //Tick!

Generator能夠暫停函數執行,返回任意表達式的值,應用場景:

1,處理異步操做,改寫回調函數

2,能夠經過Generator函數逐步讀取文本文件

3,控制流管理,多部操做很是耗時,採用回調函數的方式,函數嵌套的模式。若是是promise的話,多是回調在then裏面的嵌套,若是用Generator函數的話:

function scheduler(task){
    var taskObj = task.next(task.value);
    if(!taskObj.done){
        task.value = taskObj.value;
        scheduler(task);
    }
}
function* longRunningTask(v1){
    try{
      var v2 = yield step1(v1);
      var v3 = yield step2(v2);
      var v4 = yield step3(v3);
      var v5 = yield step4(v4);
    }catch(e){
      console.log(`ERR${e}`)
    }
}

scheduler(longRunningTask(initialValule))

那麼異步的處理方法呢??下一節講Await/Async方法,本節的Generator講太多,篇幅過長~~~

相關文章
相關標籤/搜索