• 深入探讨ES6生成器


        如果对于ES6生成器不熟悉,请先阅读并运行下http://www.cnblogs.com/linda586586/p/4282359.html里面的代码。当你感觉掌握了基础之后,我们可以深入探讨一些细节。

    错误处理

        在ES6生成器设计中最强大的是一个生成器内部代码的语义是同步的,即使外部迭代控制是异步的。

        可以使用简单的也许你很熟悉的错误处理技术--就是try-catch机制。

        例如:

    function *foo() {
        try {
            var x = yield 3;
            console.log( "x: " + x ); // may never get here!
        }
        catch (err) {
            console.log( "Error: " + err );
        }
    }

        即使函数会在yield3处暂停,也许会保持暂停状态任意一段时间,如果回传给生成器错误,try-catch会捕获它!试着使用普通异步方法处理,像回调函数。

        但是,错误回传给这个生成器有多精确?

    var it = foo();
    
    var res = it.next(); // { value:3, done:false }
    
    // instead of resuming normally with another `next(..)` call,
    // let's throw a wrench (an error) into the gears:
    it.throw( "Oops!" ); // Error: Oops!

        可以看到,在迭代器中用到的另外一个方法--throw(),会向生成器抛错,就好像正好发生在生成器yield暂停位置的点上。try-catch语句就像所期望的那样捕获了错误!

        注:如果向生成器内抛错,但是没有try-catch捕获它,错误会传播回去。所以:

    function *foo() { }
    
    var it = foo();
    try {
        it.throw( "Oops!" );
    }
    catch (err) {
        console.log( "Error: " + err ); // Error: Oops!
    }

          显然,反方向的错误处理也起作用了:

    function *foo() {
        var x = yield 3;
        var y = x.toUpperCase(); // could be a TypeError error!
        yield y;
    }
    
    var it = foo();
    
    it.next(); // { value:3, done:false }
    
    try {
        it.next( 42 ); // `42` won't have `toUpperCase()`
    }
    catch (err) {
        console.log( err ); // TypeError (from `toUpperCase()` call)
    }

    代理生成器

         另外想要做的是从生成器函数内部调用另外一个生成器。这意思不仅仅是用普通的方法实例化生成器,实际上是对于其他生成器代理迭代控制。为了这样做,可以使用yield关键字的一个变形:yield *("yield star").例子:

    function *foo() {
        yield 3;
        yield 4;
    }
    
    function *bar() {
        yield 1;
        yield 2;
        yield *foo(); // `yield *` delegates iteration control to `foo()`
        yield 5;
    }
    
    for (var v of bar()) {
        console.log( v );
    }
    // 1 2 3 4 5

        就像前面一篇介绍的,这里也使用yield *foo()代替了其他文章里面的yield* foo()。我认为它可以更确切的说明在发生的事情。
        让我们看看这个是怎么工作的。yield 1和yield 2直接把他们的值送出到了for of循环的next()调用,像我们所了解和期望的那样。

        但是yield*被碰到了,你会注意到我们正通过实例化foo()进入另外一个生成器.所以我们在为另外一个生成器迭代进行代理。

        当yield*从*bar()到*foo()代理时,for-of循环的next()调用是控制foo(),然而yield 3和yield4将他们的值送出去到for-of循环。

        当*foo()结束的时候,控制返回到原始的生成器中,最后调用yield 5。

        简化下,这个例子只向外yields值。但是当然,如果不用for-of循环,只是手动调用迭代器的next(),并且传递信息到里面,那些信息会以同样期望的方式通过yield*代理传递。

    function *foo() {
        var z = yield 3;
        var w = yield 4;
        console.log( "z: " + z + ", w: " + w );
    }
    
    function *bar() {
        var x = yield 1;
        var y = yield 2;
        yield *foo(); // `yield*` delegates iteration control to `foo()`
        var v = yield 5;
        console.log( "x: " + x + ", y: " + y + ", v: " + v );
    }
    
    var it = bar();
    
    it.next();      // { value:1, done:false }
    it.next( "X" ); // { value:2, done:false }
    it.next( "Y" ); // { value:3, done:false }
    it.next( "Z" ); // { value:4, done:false }
    it.next( "W" ); // { value:5, done:false }
    // z: Z, w: W
    
    it.next( "V" ); // { value:undefined, done:true }
    // x: X, y: Y, v: V

        即使我们只在这里演示了一层代理,*foo()没有理由不能为另外的生成器迭代器yield代理,然后再一个,以此类推。

        另外一个yield可以玩的把戏是从代理生成器接收return值。

    function *foo() {
        yield 2;
        yield 3;
        return "foo"; // return value back to `yield*` expression
    }
    
    function *bar() {
        yield 1;
        var v = yield *foo();
        console.log( "v: " + v );
        yield 4;
    }
    
    var it = bar();
    
    it.next(); // { value:1, done:false }
    it.next(); // { value:2, done:false }
    it.next(); // { value:3, done:false }
    it.next(); // "v: foo"   { value:4, done:false }
    it.next(); // { value:undefined, done:true }

        可以看到,yield *foo()代理了迭代控制(next()调用)直到他结束,然后来自foo()的任何返回值被置为yield*表达式的结果,然后赋值给了本地变量v.

        yield和yield *有一个有意思的区别:用yield表达式,结果是被随后的next()送进来的,但是用yield*,它只从它的代理的return值接受结果。

        也可以从两个方向上进行错误处理,通过yield*代理:

    function *foo() {
        try {
            yield 2;
        }
        catch (err) {
            console.log( "foo caught: " + err );
        }
    
        yield; // pause
    
        // now, throw another error
        throw "Oops!";
    }
    
    function *bar() {
        yield 1;
        try {
            yield *foo();
        }
        catch (err) {
            console.log( "bar caught: " + err );
        }
    }
    
    var it = bar();
    
    it.next(); // { value:1, done:false }
    it.next(); // { value:2, done:false }
    
    it.throw( "Uh oh!" ); // will be caught inside `foo()`
    // foo caught: Uh oh!
    
    it.next(); // { value:undefined, done:true }  --> No error here!
    // bar caught: Oops!

        可以看到,throw("Uh oh!")在*foo()内通过yield*代理抛错到了try-catch。在*foo()内的throw "Oops!"抛出来回到了*bar(),然后通过另外一个try-catch捕获了。要是没有捕获到他们其中的一个,错误会像期望的那样继续向外传播。

    总结

        生成器有同步处理机制,即可以通过yield声明使用try-catch错误处理。生成器迭代器也有一个throw()方法在生成器暂停的位置抛错,当然也可以被生成器内部的try-catch捕获。

        yield允许从当前的生成器为另外一个代理迭代控制。结果是yield*在两个方向上传递,可以是信息也可以是错误。

        但是,还有一个基本问题遗留了,没有回答:生成器怎么通过同步代码模式帮助我们?所有我们现在看到的这两篇文章是生成器函数的同步迭代器。

        关键是构建一个生成器暂停来启动异步任务的机制,然后在异步任务最后恢复。我们将探讨很多通过生成器生成这样异步控制的方法。
    英文原文:http://davidwalsh.name/es6-generators-dive

  • 相关阅读:
    JavaScript获取浏览器高度和宽度值
    机器学习2
    2014.7.23
    2014.7.22
    STM32 定时器
    STM32 外部中断
    STM32--systick延时
    STM32 时钟
    输入捕获
    DAC
  • 原文地址:https://www.cnblogs.com/linda586586/p/4284754.html
Copyright © 2020-2023  润新知