Promise,Generator,Await/Async

上节中忘记讲:Iterator接口和Generator函数的关系了,Symbol.iterator方法的最简单的实现就是通过Generator函数:

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:Generator

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,就变成了一个单纯的暂缓执行的函数。

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讲太多,篇幅过长~~~


 

posted @ 2018-05-16 11:34  tangjiao_Miya  阅读(242)  评论(0编辑  收藏  举报