Generator与Promise的完美结合 -- async await函数诞生记

Generattor函数的yield next双向通信
function *foo(x) {
    var y = x * (yield "Hello");     // <-- yield一个值!
    return y;
}

var it = foo( 6 );

var res = it.next();    // 第一个next(),并不传入任何东西
res.value;              // "Hello"

res = it.next( 7 );     // 向等待的yield传入7
res.value;              // 42
复制代码

为了较为清晰的理解这个过程,特意做了一个简单的图示,加深自己的记忆。

这个过程就好像是丝绸之路上的商人之间的贸易一样,next从yield哪里拿到一些东西同时也会给yield一些东西。

多个迭代器

每次构建一个迭代器 ,实际上就隐式构建了生成器的一个实例,通过这个迭代器 来控制的是这个生成器实例

function *foo() {
    var x = yield 2;
    z++;
    var y = yield (x * z);
    console.log( x, y, z );
}

var z = 1;

var it1 = foo();
var it2 = foo();

var val1 = it1.next().value;           // 2 <-- yield 2
var val2 = it2.next().value;           // 2 <-- yield 2

val1 = it1.next( val2 * 10 ).value;    // 40   <-- x:20,  z:2
val2 = it2.next( val1 * 5 ).value;     // 600  <-- x:200, z:3

it1.next( val2 / 2 );                  // y:300
                                       // 20 300 3
it2.next( val1 / 4 );                  // y:10
                                       // 200 10 3
复制代码
迭代器深入理解

如果一个对象包含一个可以迭代的迭代器(iterator),那么这个对象就是一个iterable(可迭代)

从一个iterable中回去迭代器的方法:

// 以数组为例
var a = [1,2,3]
var it = a[Symbol.iterator]();
it.next().value // 1
it.next().value // 2
it.next().value // 3
复制代码

每一个iterable中都有一个函数Symbol.iterator。调用这个函数会返回一个迭代器。

for of 循环可以自动调用Symbol.iterator函数来构建一个迭代器。

让我们手动实现一个iterator(同时也是一个iterable)

var something = (function() {
    let val
    return {
        // something对象是含有一个Symbol.iterator函数的,所以something是一个iterable;
        // 这个Symbol.iterator函数返回的就是something本身,而它本身是有next()方法,所以实际上something本身也是一个迭代器(iterator)
        [Symbol.iterator]: function() {return this},
        next: function() {
            val = val ? val + 2 : 1 
            return {
                // 这里一直返回false,所以这个迭代器没有终点
                done: false, value: val
            }
        }
    }
})
复制代码

真正的iterator可以具有三个方法next, return, throw;我们自己实现的iterator可以按照需要省略return和throw

  • return方法: 如果for...of循环提前退出(通常是因为出错,或者有break语句)
var something = (function() {
    let val
    return {
        // something对象是含有一个Symbol.iterator函数的,所以something是一个iterable;
        // 这个Symbol.iterator函数返回的就是something本身,而它本身是有next()方法,所以实际上something本身也是一个迭代器(iterator)
        [Symbol.iterator]: function() {return this},
        next: function() {
            val = val ? val + 2 : 1 
            return {
                // 这里一直返回false,所以这个迭代器没有终点
                done: false, value: val
            }
        },
        return() {
            console.log('return 被触发')
            return {done: true}
        }
    }
})

for(let i of something()) {
    if(i > 10) {break}
    console.log(i)
}
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// return 被触发
复制代码
  • throw方法主要是配合生成器函数一起使用,一般的iterator用不到这个方法。 next()、throw()、return()在生成器函数中这三个方法本质上是一样的, 都是让Generator函数恢复执行,并且使用不同的语句替换yield表达式。

    next()是将一个值传递给yield

    const g = function* (x, y) {
      let result = yield x + y;
      return result;
    };
    
    const gen = g(1, 2);
    gen.next(); // Object {value: 3, done: false}
    
    gen.next(1); // Object {value: 1, done: true}
    // 相当于将 let result = yield x + y
    // 替换成 let result = 1;
    复制代码

    throw()是将一个错误传递给yield

    gen.throw(new Error('出错了')); // Uncaught Error: 出错了
    // 相当于将 let result = yield x + y
    // 替换成 let result = throw(new Error('出错了'));
    复制代码

    return()是将return语句传递给yield

    gen.return(2); // Object {value: 2, done: true}
    // 相当于将 let result = yield x + y
    // 替换成 let result = return 2;
    复制代码
生成器(Generator) vs 迭代器

生成器并不是一个iterater,它执行的结果才是一个iterator

function * foo() {}
// it是一个itreator
var it = foo()
复制代码

所以我们尝试用生成器实现上面的something:

function *something() {
    var val
    // 在生成器中使用while..true并没有问题
    while(true) {
        val = val ? val + 2 : 1
        yield val
    }
}
复制代码

这个实现方式跟我们之前的闭包方式相比更加简洁,不需要闭包来保持变量状态了。

for (let i of something()) {
    if(i > 10){break}
    console.log(i)
}
复制代码

前面我们说过something()生成的是一个iterator,而for...of需要的是一个iterable; 实际上生成器something()生成的iterator同时也是一个iterable,其内部的Symbol.iterator实际上也是类似于return this 的做法。

生成器与Promise的完美结合

生成器函数给了我们一个看似同步的流程控制代码配合Promise(可信任可组合)的组合可能是js新世界中最美妙的事情。

所以,你应该想到了,ES78(ES2017)中提供的async/await正式这两者完美结合的语法级支持。 实际上,asyn函数不过是Generator(生成器)函数的一个语法糖

// 手动结合Generator和Promise

// 这里不直接定义promise而是通过foo返回是因为promise在定义的时候就会执行
function foo() {
    return new Promise(function(resolve, reject) {
        resolve(10)
    })
}

function *gen() {
    try{
        let text = yield foo()
        console.log(text)
    } catch(err) {
        console.log(err)
    }
}

let it = gen()
let p =it.next().value

p.then(
    function(res) {
        it.next(res)
    },
    function(err) {
        it.throw(err)
    }
)
// 10
复制代码

可以看到手动结合Promise和Generator是多么的繁琐;虽然带来了同步的流程和可信任可组合的Promsie,这也不是我们愿意看到的。

看看async/await如何实现上述过程

function foo() {
    return new Promise(function(resolve, reject) {
        resolve(10)
    })
}

async function aw() {
    let text = await foo()
    console.log(text)
}
aw() // 10
复制代码

是不是清爽了很多,async/await实际上是把Generator的启动步骤和Promsie内部的next()实现细节隐藏到语法糖中,留给我们一个清爽的世界。如果希望了解实现细节,我们不放手动模仿以下这个语法糖函数

示例来自‘你不知道的javascript(中卷)’

function run(gen) {
    var args = [].slice.call( arguments, 1), it;

    // 在当前上下文中初始化生成器
    it = gen.apply( this, args );

    // 返回一个promise用于生成器完成
    return Promise.resolve()
        .then( function handleNext(value){
            // 对下一个yield出的值运行
            var next = it.next( value );

            return (function handleResult(next){
                // 生成器运行完毕了吗?
                if (next.done) {
                    return next.value;
                }
                // 否则继续运行
                else {
                    return Promise.resolve( next.value )
                        .then(
                            // 成功就恢复异步循环,把决议的值发回生成器
                            handleNext,

                            // 如果value是被拒绝的 promise,
                            // 就把错误传回生成器进行出错处理
                            function handleErr(err) {
                                return Promise.resolve(
                                    it.throw( err )
                                )
                                .then( handleResult );
                            }
                        );
                }
            })(next);
        } );
}


复制代码

转载于:https://juejin.im/post/5c08d30f51882521c8116715

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值