zoukankan      html  css  js  c++  java
  • 深入探讨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

  • 相关阅读:
    Java学习二十九天
    Java学习二十八天
    47. Permutations II 全排列可重复版本
    46. Permutations 全排列,无重复
    subset ii 子集 有重复元素
    339. Nested List Weight Sum 339.嵌套列表权重总和
    251. Flatten 2D Vector 平铺二维矩阵
    217. Contains Duplicate数组重复元素
    209. Minimum Size Subarray Sum 结果大于等于目标的最小长度数组
    438. Find All Anagrams in a String 查找字符串中的所有Anagrams
  • 原文地址:https://www.cnblogs.com/linda586586/p/4284754.html
Copyright © 2011-2022 走看看