美文网首页
JS异步编程(4)-Generator

JS异步编程(4)-Generator

作者: Johnson杰 | 来源:发表于2021-06-21 10:58 被阅读0次

    Generator 是与 Promise 同时由 ES6 引入标准的语法,最早由社区提出和实现
    主要用于实现一种新的状态机制管理,让一段代码逻辑可以动态控制分段执行

    Generator 新增关键字及函数

    * 关键字

    * 关键词用于生成 Generator函数* 前后的空格没有影响),但有以下几个限制:

    • 只能用于 function 关键字
    • 不能用于箭头函数
    function * fun1 () {} // work
    function* fun2 () {} // work
    function *fun3 () {} // work
    const fun4 = function * () {} // work
    
    const fun5 = *() => {} // 报错!
    

    一旦函数被生成为 Generator函数,这个函数就不能作为构造函数被 new 关键字使用了

    function * Fun() {}
    const obj = new Fun() {} // 报错!
    

    yield 关键字

    yield 的返回值

    yield 在功能上和 return 有些类似,都用于将值返回,但有些差异:

    • return 语句之后无法进行任何操作;yield 后续的程序可以通过调用 next 继续执行
    • return 无法和赋值符一起使用;yield 可以和赋值符一起使用
    • return 可以独立使用;yield 必须配合 Generator函数next 函数才能使用
    function * test() {
        const first = yield 1
        const second = yield 2
        const third = yield 3
    }
    const obj = test()
    obj.next() // {value: 1, done: false}
    obj.next() // {value: 2, done: false}
    obj.next() // {value: 3, done: false}
    obj.next() // {value: undefined, done: true}
    

    yield 可以拼接的表达式

    yield 后面可以拼接多种类型的值

    • 基本类型:yield '123'yield 123yield { name: 'Peter' } 等等
    • 函数:yield function test(){}
    • 表达式:yield 1 + 2 + 3

    多个 yield 拼接

    yieldyield 拼接时,后一个 yield 作为变量使用,为 undefined

    function * test() {
        yield yield
    }
    const obj = test()
    obj.next() // {value: undefined, done: false}
    

    yield 的类函数形式

    看一段代码

    function * test() {
        yield 1
    }
    // 上面的写法,等价于下方的写法
    function * test() {
        yield(1)
    }
    

    yield(1) 咋一看,会让人觉得是 yield 作为一个函数被调用了
    其实是 yield 返回了一个使用小括号包裹的表达式(1)
    这里纯粹是个人见解,没有找到权威的文档佐证,欢迎指正!

    next 函数

    第一次 next 调用的传参没有作用

    function *test(x) {
        const re = yield 1 + x;
        return re + 2;
    }
    
    const obj = test(5);
    obj.next(2) // {value: 6, done: false}
    obj.next(3) // {value: 5, done: true}
    

    如上

    1. 变量 x 来自生成 Generator对象 时的参数 5
    2. 第一次 next 调用,函数内部开始执行到 yield 返回 1 + 5,因此本次 next 参数无效
    3. 第二次 next 调用,next 的参数 3 赋值给 yield 后面的整个表达式 const re = 3,因此结果为 3 + 2

    next 函数参数是对上一个 yield 及后方整个表达式的值覆盖

    function *test() {
        console.log(yield 5) // 2
    }
    
    const obj = test();
    obj.next()
    obj.next(2)
    

    第二次 next 调用,yield 5 的值为 2,被参数覆盖

    yield 和 next 的返回值

    对于 yieldnext 的返回值问题一直比较模糊
    { value: any, done: Boolean } 到底是 yield 的返回值还是 next 的返回值?

    下面分别引用了 MDN 上对 yieldnext 返回值的描述

    yield关键字实际返回一个IteratorResult对象,它有两个属性,value和done。value属性是对yield表达式求值的结果,而done是false,表示生成器函数尚未完全完成。

    参考自yield

    next() 方法返回一个包含属性 done 和 value 的对象。该方法也可以通过接受一个参数用以向生成器传值。

    参考自Generator.prototype.next()

    说实话,从两段描述上来看,yieldnext 返回值都是 { value: any, done: Boolean }

    所以写了下方的代码做验证

    function *test() {
        console.log(yield 1) // 2
    }
    
    const obj = test();
    obj.next()
    obj.next(2) // { value: undefined, done: true }
    

    从结果上来看

    • yield 返回的是 value
    • next 返回的是 { value: undefined, done: true }

    Generator 执行流程

    1. 生成 Generator对象,这时候 Generator函数 内部不会执行
    function *test() {
        console.log('start')
        let re = yield 1;
        return re + 2;
    }
    
    const testObj = test(); // no console
    const testObj2 = test(); // no console
    

    如果生成多个 Generator对象,则各个 Generator对象 保持着各自的执行阶段,互不影响

    1. Generator对象调用 next 函数
      从函数开头或上一个 yield 开始,执行到下一个 yield 或者 return
    function *test() {
        console.log('before first yield')
        let first = yield 1;
        console.log('after first yield')
        let second = yield 2;
        console.log('after second yield')
        let third = yield 3;
        console.log('after third yield')
        return re + 2;
    }
    
    const testObj = test(); // no console
    const firstResult = testObj.next(); // before first yield
    const secondResult = testObj.next(); // after first yield
    const thirdResult = testObj.next(); // after second yield
    const fourthResult = testObj.next(); // after third yield
    

    yield 委托迭代

    *yield 组合使用,可以将多个生成器连接在一起

    function * anotherGenerator(i) {
      yield i + 1;
      yield i + 2;
      yield i + 3;
    }
    
    function * generator(i) {
      yield* anotherGenerator(i);
    }
    
    var gen = generator(1);
    
    gen.next().value; // 2
    gen.next().value; // 3
    gen.next().value; // 4
    

    类型判断

    业界常见的判断 Generator 对象和函数的方法

    如何判断 Generator 对象

    function isGenerator(obj) {
        return obj && typeof obj.next === 'function' && typeof obj.throw === 'function';
    }
    

    这里运用鸭子模型进行判断
    如果对象中有 nextthrow 两个方法,那么就认为这个对象是一个生成器对象

    如何判断 Generator 函数

    function isGeneratorFunction(obj){
        var constructor = obj.constructor;
        if(!constructor) return false;
        if(
            'GeneratorFunction' === constructor.name ||
            'GeneratorFunction' === constructor.displayName
        ) return true;
        return isGenerator(constructor.prototype);
    }
    

    利用函数的 constructor 构造器的名字来判断(name 与 displayName 为了处理兼容性)
    这里递归调用 isGenerator 判断 constructor 的原型是因为有自定义迭代器的存在

    总结

    Generator 函数作为一种新的状态机制管理,让一段代码逻辑可以动态控制分段执行,有独到的作用和使用场景

    但如果独立使用,操作繁琐,语义也不清晰
    所以在 ES6 引入后,社区使用例如 co 库进行二次封装

    在前端异步编程领域,开阔思路的作用大于实际应用,有点昙花一现的意思
    为 Async/Await 做铺垫

    相关文章

      网友评论

          本文标题:JS异步编程(4)-Generator

          本文链接:https://www.haomeiwen.com/subject/secsyltx.html