Generator 和 函數異步應用 筆記

Generator
> ES6 提供的一種異步編程解決方案
> Generator 函數是一個狀態機,封裝了多個內部狀態。仍是一個遍歷器對象生成函數。返回<label>遍歷器對象</label>,能夠依次遍歷 Generator 函數內部的每個狀態。
 
Generator 函數特徵
- `function `關鍵字和函數名之間 有 `*` ES6 沒有規定,function關鍵字與函數名之間的星號,寫在哪一個位置。
- 函數體內部有 `yield` 表達式
 1 function* generator() {
 2 yield 'Owen';
 3 yield 18;
 4 return 'end';
 5 }
 6 
 7 
 8 //調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象 Iterator
 9 let person = generator();
10 
11 
12 person.next() //{value:'Owen',done:false}
13 person.next() //{value:18,done:false}
14 person.next() //{value:'end',done:true}
15 person.next() //{value:undefined,done:true}
16 person.next() //{value:undefined,done:true}

yield

> yield 後面的表達式以分號做爲結束語句
> 一種能夠暫停函數執行的表達式,配合 `next`方法使用。
> 只能再在 `Generator` 函數中使用,普通函數中使用會報錯.
> `yield` 若是用<label>在另外一個表達式中,必須用圓括號包起來</label>,做爲函數參數或者賦值表達式右邊能夠不用加括號。
 
`next` 運行邏輯

- 調用 `next` 遇到 `yield`表達式 暫停執行函數後面的操做,而且 <label> 緊跟 `yield` 後面的表達式的值</label>做爲返回對象 `value` 屬性的值。javascript

- 再次調用 `next`,繼續執行`yield`表達式 後面的邏輯代碼,直到下一個 `yield`表達式 或者 `return` 語句,返回值爲 對象 `value` 屬性的值。
- 若是沒有 `return` 語句 ,則 `value` 屬性的值爲 `undefined`。
- <label>只有調用`next`方法,纔會執行對應 `yield` 後面的表達式</label>
function* g(){
yield 1 + 2
}
let num = g()
num //g {<suspended>}
num.next() //{value:3,done:false}
num.next() //{value:undefined,done:true}

 

  使用 Interator
> 因爲 `Generator` 函數是遍歷器生成函數,所以 把它賦值給對象的 `[Symbol.interator]`屬性,該對象就能夠被遍歷
let iter = {};
iter[Symbol.iterator] = function* (){
yield 1;
yield 2;
yield 3;
}


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

 

next 參數
> `yield` 自己的返回值爲 undefined, 而 `next`方法能夠帶一個參數,看成<label>上一個 `yield`的返回值<label>
 1 let g = fn();
 2 g.next() //{value:0,done:false}
 3 g.next() //{value:1,done:false}
 4 g.next() //{value:2,done:false}
 5 g.next(1) //{value:0,done:false}
 6 
 7 function* fn (){
 8 for (let i = 0; true; i++){
 9 let reset = yield i;
10 if(reset) i = -1;
11    }
12 }
13 function* dataConsumer() {
14 console.log('Started');
15 console.log(`1. ${yield}`);
16 console.log(`2. ${yield}`);
17 return 'result';
18 }
19 let genObj = dataConsumer();
20 genObj.next();
21 // Started
22 //{value: "undefined", done: fales}
23 genObj.next('a')
24 //1. a
25 //{value: "undefined", done: fales}
26 genObj.next('b')
27 //2. b
28 //{value: "result", done: true}

 

 for...of 中使用 Generator
> 能夠不用調用 `next` 方法
> <label> 若是 返回對象 done屬性值爲 true, 循環終而且不包含該對象返回值;
 1 function* fn(){
 2   yield 1;
 3   yield 2;
 4   yield 3;
 5   yield 4;
 6   yield 5;
 7   return 6;
 8 }
 9 
10 for (let i of fn()){
11     console.log(i);
12     //1 2 3 4 5 undefined
13 }
14 
15 
16 //斐波那契數列
17 
18 function* fibonacci() {
19   let  [prev, curr] = [0, 1];
20   for (;;) {
21       yield curr;
22       [prev,curr] = [curr, prev + curr];
23   }
24 }
25 
26 for (let f of fibonacci()) {
27     if (f > 1000) break;
28     console.log(f)
29 }
30 
31 //遍歷任意對象方法
32 
33 function* objectEntries(obj) {
34     let propKeys = Reflect.ownKeys(obj);
35 
36     for (let key of propKeys){
37          yield [key,obj[key]];
38     }
39 }
40 
41 let person = {name:'Owen', age:18};
42 
43 for (let [key,value] of Object.entries(person)){
44     console.log(key,value);
45     //name Owen
46     // age 18
47 }
48 
49 for (let [key,value] of objectEntries(person)){
50     console.log(key,value);
51      //name Owen
52     // age 18
53 }
54 //或者
55 
56 person[Symbol.interator] = objectEntries
57 
58 for (let [key,value] of person){
59     console.log(key,value);
60      //name Owen
61     // age 18
62 }

 

  Generator throw
> 在函數體外拋出錯誤,再在 函數體內捕獲。
> throw方法拋出的錯誤要被內部捕獲,前提是必須至少執行過一次next方法。由於只有執行一次 `next`方法,函數纔開始執行。
function* g() {
       try {  yield;  } catch (e) {
          console.log('內部1',e )
      };

     try { yield; } catch (e) {
          console.log('內部2',e)
      }; 
  }
  
  let  t = g();
  t.next(); //{value: undefined, done: false}
  try{
      t.throw('a');
      t.throw('b');
      t.throw('c');
  } catch(e){
      console.log('外部',e)
  }

  //內部1 a
  //內部2 b
  //外部 c

 

 Generator returnjava

> 結束 Generator 函數 而且給定對象返回值
> 若是有 `try...finally`,且在執行中,return方法會等待`finally` 執行完畢,再執行。
function* g(){
    yield 1;
    yield 2;
}

let ge = g();

ge.next(); // { value: 1, done: false }
ge.return('Owen'); // { value:"Owen", done: true }
ge.next() // { value: undefined, done: true }

// try...finally
function* numbers () {
  yield 1;

  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }

  yield 6;
}
var nun = numbers();
nun.next() // { value: 1, done: false }
nun.next() // { value: 2, done: false }
nun.return(7) // { value: 4, done: false }
nun.next() // { value: 5, done: false }
nun.next() // { value: 7, done: true }

 

**next()、throw()、return()**

> 都是讓 Generator 恢復執行,而且使用不一樣語句替換 `yield`

yield*node

> Generator 函數默認不在內部調用另外一個 Generator 函數 是沒有效果的,若是<label>放到 `yield` 後面 會返回一個遍歷器對象</label>
> `yield*` 後面的 `Generator` 函數(沒有return語句時),等同於在 `Generator` 函數內部,部署一個 `for...of` 循環。
> 若是 `yield*` 後面緊跟數組,會遍歷數組成員(數組原始支持遍歷器)
//默認
function* f(){
    yield 1;
    yield 2;
}

function* g(){
    yield 'x';
    f();
    yield 'y';
}

for(let key of  g()){
    console.log(key)
    //"x" "y"
}

//yield*

function* y(){
    yield 'x';
    yield* f();
    yield 'y';
}

for (let k of y()) {
    console.log(k);
    // "x" "y" 1 2
}

//無 return
function* concat(iter1, iter2) {
  yield* iter1;
  yield* iter2;
}

// 等同於

function* concat(iter1, iter2) {
  for (var value of iter1) {
    yield value;
  }
  for (var value of iter2) {
    yield value;
  }
}

// yield* array 若是去掉 * 會返回整個數組
//任何數據結構只要有 Iterator 接口,就能夠被yield*遍歷。
function* gen(){
  yield* ["a", "b", "c"];
}
var g = gen();

g.next() //{value: "a", done: false}
g.next() //{value: "b", done: false}
g.next() //{value: "c", done: false}
g.next() //{value: undefined, done: true}

//取出嵌套數組的全部成員
function* iterTree(tree) {
    if (Array.isArray(tree)){
         for (let arr of tree) {
             yield* iterTree(arr)
         }
    }else{
        yield tree
    }
}

const tree = [1,[2,3],[4,[5,6],7],8];

for (let v of iterTree(tree)){
console.log(v)
}
//1 2 3 4 5 6 7 8

[...iterTree(tree)] //[1, 2, 3, 4, 5, 6, 7, 8]

//遍歷徹底二叉樹

// 下面是二叉樹的構造函數,
// 三個參數分別是左樹、當前節點和右樹
function Tree(left, label, right) {
  this.left = left;
  this.label = label;
  this.right = right;
}

// 下面是中序(inorder)遍歷函數。
// 因爲返回的是一個遍歷器,因此要用generator函數。
// 函數體內採用遞歸算法,因此左樹和右樹要用yield*遍歷
function* inorder(t) {
  if (t) {
    yield* inorder(t.left);
    yield t.label;
    yield* inorder(t.right);
  }
}

// 下面生成二叉樹
function make(array) {
  // 判斷是否爲葉節點
  if (array.length == 1) return new Tree(null, array[0], null);
  return new Tree(make(array[0]), array[1], make(array[2]));
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);

// 遍歷二叉樹
var result = [];
for (let node of inorder(tree)) {
  result.push(node);
}

result // ['a', 'b', 'c', 'd', 'e', 'f', 'g']

 

 對象屬性中的 Generator 函數

 

let obj = {
    * generator(){
        
    }
}
//or

let obj1 = {
    generator :function* () {
    }
}
```
### Generator 函數的this
> Generator 函數不能和 new 一塊兒使用
> <label>函數總返回一個遍歷器,而且它指向 函數實例,同時繼承 函數原型對象上的方法</label>
```javascript
function* g() {
    this.say = function(){
        return 18
    };
}

g.prototype.say = function () {
    return "Owen"
}

let obj =g() //g {<suspended>}
obj instanceof g //true
obj.say() //"Owen"

obj.next() //{value: undefined, done: true}
obj.say() //"Owen"
//由於 next 返回的是遍歷器對象而不是 this 對象,因此不會返回 18


//經過call 綁定this
function* Gen(){
    this.age = 18;
    yield this.name = "Owen";
}
let obj = Gen.call(Gen.prototype);
obj.next()
obj.age // 18
obj.next();
obj.name //"Owen"

// 使用 new 的變通方法

function G(){
    return Gen.call(Gen.prototype)
}
let obj1 = new G();
obj1.next()
obj1.age // 18
obj1.next();
obj1.name //"Owen"

 

Generator 函數異步應用

> 異步: 執行一個任務的時候還不能立刻返回結果,那麼先將其擱置到後臺,執行其餘任務,等到有結果返回以後放到消息隊列中,等主線程任務執行完畢後,再從消息隊列中取出對應任務(callback),執行。
> 同步: 執行一個任務,中間沒法中斷,只能等待任務返回結果,才能執行其餘任務。

 

異步編程

 回調函數
$.ajax({
url:'url',
success:function(res){
console.log(res)
}
})

 

事件監聽
> 起初由網景公司知道一套事件驅動機制(事件捕獲),以後IE 推出本身的驅動機制(事件冒泡)
> 利用驅動機制實現事件代理委託
el.addEventListener(event,function(){

},boolean);

//IE8 如下
el.attachEvent(event,function(){})

//事件代理委託
var parent = document.getElementById('parent');
parent.addEventListener('click',showColor,false);
    function showColor(e){
        var son = e.target;
        if(son.nodeName.toLowerCase() === 'li'){
            console.log('The color is ' + son.innerHTML);
        }
    }

 

發佈/訂閱(觀察者模式)
//實現一個簡單的發佈訂閱
/* 
  訂閱一個事件,發佈對應事件而且執行方法
  須要先有代理主題 message proxy
  而後訂閱者去訂閱
  而後發佈者發佈消息執行方法
 */

   function PubSub () {};
  // message proxy
  PubSub.prototype.message = {};

  // publish
  PubSub.prototype.pub = function () {
     // Turn arguments  into real array
    let args = [].slice.call(arguments,0);
    
    let event = args.shift();

    if(!this.message[event]) return this;
    
    let list = this.message[event];
    
    for (let item of  list) {
       item.apply(this,args);
    }
    return this;
  }

  // subscribe
  PubSub.prototype.sub = function (event,callback){      
    if( !this.message[event]) {
        this.message[event] = [];
     }
    
    this.message[event].push(callback);
    
    return this;
  } 
  // unsubscribe
  PubSub.prototype.unsub = function (event) {
     if (!this.message[event]) return this;

     this.message[event] = null;
  }

 

Promise 對象
> 爲了解決 「回調地獄」(callback hell)而提出的寫法
> 容許將 `callback ` 變成鏈式調用
// read I/O
let readFile = require('fs-readfile-promise');

readFile(fileA)
    .then( (data) => console.log(data.toString()))
    .then( () =>   readFile(fileB))
    .then( (data) => console.log(data.toString()))
    .catch((err) => console.log(err));
Promise 的最大問題是代碼冗餘,原來的任務被 Promise 包裝了一下,無論什麼操做,一眼看去都是一堆then,原來的語義變得很不清楚。
Generator 函數(es6)
  **協程**
> 多個線程相互協做,完成異步任務
步驟
- A方法開始任務
- A方法執行到一半,暫停,將執行權轉移到主線程
- 一段時間後主線程交還執行權
- A方法恢復執行
//example
function* async () {
  //... do something
  let file = yield readFile(fileA);
  //... do something
}

//異步操做須要暫停的地方,都用yield語句註明

 協程代碼的寫法很是像同步操做,Generator 函數最大優勢是能夠交出函數執行權es6

函數經過`next` 進行數據交換,經過 `throw `進行錯誤處理
function* async (num) {
  let n;
  try{
     n =  yield ++num;
  }catch(err){
    console.log(err)
  }
  return n
}
let g = async(1);
g.next()
g.throw('error');

 

封裝異步任務
使用`node-fetch` 模塊 返回一個Promise 對象
let fetch = require('node-fetch');
let g = gen();
let result = g.next();

result.value.then((data) => data.json())
            .then((data) => g.next(data) );

function* gen() {
  let url = '/index.json';
  let result = yield fetch(url);
}

 

 Thunk (形實轉換)
**函數傳值策略**
- 傳值調用(參數在傳入函數前計算),簡單易懂,不過可能會形成性能損失
- 傳名調用(將參數放到一個臨時函數,再將這個臨時函數傳入函數體(`Thunk` 函數))
//傳值
var x = 3;
function fn (x,b) { return x * 3 };
fn(x + 1) //   4 * 3

//傳名
fn(x + 1)  //    (x + 1) * 3 
//等同於
var thunk = function (){ return x + 1}

fn (thunk) // thunk() * 3
相關文章
相關標籤/搜索