理解 Generator 的执行

简介: Generator & yield开局官宣:sec-generatoryield,这是对yield的介绍。同样巴拉巴拉列了9条,将以上链接中的说明简化成3条:1. 在GeneratorFunction内,当遇到yield关键字的时候,先将执行上下文设置为yield之后的表达式进行执行,并且将该表达式返回值作为当前迭代的结果;2. 将gen上下文从上下文堆栈中移除,将上级(gen之外)上下文(依次)恢复为当前执行的上下文,此过程中需设置gen上下文的评估状态,以便在上下文恢复时(下一次调用.next)继续操作迭代;3. 通过.next方法依次执行迭代器。

Generator & yield

开局官宣:sec-generatoryield,这是对yield的介绍。

同样巴拉巴拉列了9条,将以上链接中的说明简化成3条:

1. 在GeneratorFunction内,当遇到yield关键字的时候,先将执行上下文设置为yield之后的表达式进行执行,并且将该表达式返回值作为当前迭代的结果;

2. 将gen上下文从上下文堆栈中移除,将上级(gen之外)上下文(依次)恢复为当前执行的上下文,此过程中需设置gen上下文的评估状态,以便在上下文恢复时(下一次调用.next)继续操作迭代;

3. 通过.next方法依次执行迭代器。

先对上面3点有点印象,再来看看 Generator。

Generator 对象是通过 GeneratorFunction 执行返回的对象,具有可迭代的特性(迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值),关于迭代器详见"迭代器"

GeneratorFunction => Generator

GeneratorFunction.prototype
    next 返回迭代结果
    return 传入参数作为迭代结果的value并返回该迭代项,并且结束Generator对象的迭代
    throw 抛出错误值,并且结束Generator对象的迭代

每个迭代结果都包含 donevalue

  1. done 表示生成器是否被完成;

  2. value 表示当前的值。

来个例子:

function* iterator1(){
    console.log(1);
    yield '1';
    console.log(2)
    yield *iterator2();
    yield '2';
    console.log(3);
}

function* iterator2(){
    yield '3';
    console.log(4);
    yield '4';
}

function fn1(){
    console.log(5)
    fn2();
    console.log(6)
}

function fn2(){
    console.log(7)
    var iter = iterator1();
    console.log(iter.next());
    console.log(iter.next());
    console.log(iter.next());
    console.log(8);
    console.log(iter.next());
    console.log(iter.next());
    console.log(9);
}
fn1();
/*
* 输出顺序
* var iter = iterator1();    // before : 5 7
* console.log(iter.next());  // 1    {value:1,done:false}
* console.log(iter.next());  // 2    {value:3,done:false}
* console.log(iter.next());  // 4    {value:4,done:false}
* console.log(8);            // 8
* console.log(iter.next());  //      {value:2,done:false}   
* console.log(iter.next());  // 3    {value:undefined,done:true}
* console.log(9);            // 9 after : 6
*/

看输出顺序(多个Generator嵌套可看作为在外部Generator的某个索引位置插入内部Generator的元素作为迭代项):

  1. fn1被执行,首先输出 5;

  2. 进入fn2,输出 7;

  3. fn2中生成iter,并首次调用iter.next(),执行了iterator1里面第一个yield之前的console,输出 1,然后输出 {value: "1", done: false};

  4. 调用第二个iter.next(),进入iterator2中,输出 2,然后输出 {value:'3',done:false};

  5. 调用第三个iter.next(),还是进入iterator2,输出 4,然后输出 {value:'4',done:false};

  6. 调用fn2中的console.log(8),输出 8;

  7. 调用第四个iter.next(),这时候iterator2里面执行完了,继续执行iterator1的后续代码,输出 {value:2,done:false};

  8. 调用第五个iter.next(),继续iterator1的后续代码,输出 3,这时候iterator1的迭代结束,输出 {value:undefined,done:true};

  9. 调用fn2中的console.log(9),输出 9;

  10. 调用fn1中的console.log(6),输出 6。

Generator的任务执行器

Generator通过.next方法来依次做迭代的执行,然而每次都需要手动写方法调用是个问题。然后便有了迭代任务的执行器,在执行器内将主动调用.next以执行迭代。

如下面例子:

function run(gen){
    const task = gen();
    // 定义一个对象用于存每个迭代结果,传入result.value 赋值到指定对象上
    let result = task.next();

    // 如果迭代未结束,则继续执行next(),获取下个迭代结果,以此类推...
    function step(){
        if(!result.done){
            result = task.next(result.value);
            step();
        }
    }
    step();
}

run(function*(){
    let i = 0;
    while(i<10) { 
        yield ++i,
        console.log(i);
    } 
}); 
// 1 2 3 4 5 6 7 8 9 10 

在run(function*(/* ... */))中,先执行GeneratorFunction迭代对象返回Generator,然后用一个变量来存每次迭代结果...执行过程如下:

  1. result={value:1,done:false},打印 1;

  2. 在step内,result={value:2,done:false},打印 2;

  3. 在step内,result={value:3,done:false},打印 3;

  ...

  10. 在step内,result={value:10,done:false},打印 10;

  11. 在step内,result={value:undefined,done:true},迭代对象被完成。

如果yield后跟的是异步表达式呢?

代码如下:

// 基于上面的run函数
run(function*(){ 
    const value1=yield fn1();
    console.log('v1',value1);
    const value2 = yield fn2();
    console.log('v2',value2)
})
function fn1(){
    const promise = new Promise(resolve => setTimeout(()=> resolve(' success'),3000));
    promise.then(res=> console.log(res) )
    return promise;
};
function fn2(){
    console.log('fn2');
    return 'fn2';
}
// v1 Promise
// fn2
// v2 fn2
// 3s 后 success

假如需求需要fn2的执行依赖fn1的异步返回值,简单改造一下run执行器试试:

// 修改上面的run函数
function run(gen){
    const iter = gen();
    // result用来存储每一次迭代结果
    let result = iter.next();

    step();

    function step(){
        // 如果迭代对象未完成
        if(!result.done){
            // 如果是Promise,则在.then之后执行next
            if(result.value instanceof Promise){
                result.value.then(res =>{
                    result = iter.next(res);
                    step();
                })
            }else{
                result = iter.next(result.value);
                step();
            }
        }
    }
}

以上是没看co代码之前针对问题"如果Generator对象迭代过程中某个迭代处理依赖上一个迭代结果该怎么办"想到的方法... 在实现方式上是差了些,但也可以用...

co实现的更好看且适用,每次迭代(function, promise, generator, array, object)都包装成Promise处理,针对不同场景/类型则关注toPromise层的判断。

对比一下ES7 async/await通过tsc --target es5 后的代码。

  1. 首先是个__awaiter方法,里面是 new Promise;

  2. 然后是个__generator方法,里面是GeneratorFunction。

也是用Promise包装Generator的模式实现... 把__awaiter摘出来后的代码:

var run = function (thisArg,_arguments,generator) {
    return new Promise(function (resolve, reject) {
        generator = generator.apply(thisArg, _arguments || [])

        function fulfilled(value) {
            try {
                step(generator.next(value));
            } catch (e) {
                reject(e);
            }
        }

        function rejected(value) {
            try {
                step(generator["throw"](value));
            } catch (e) {
                reject(e);
            }
        }

        step(generator.next());

        function step(result) {
            result.done ? resolve(result.value) : new Promise(function (resolve) {
                resolve(result.value);
            }).then(fulfilled, rejected);
        }
    });
};

可能有些关联的文章:

理解 async/await 的执行

分步理解 Promise 的实现

Generator关系图

co

文章仅供参考!!!关于更多Generator知识,以阅读文章开头官方文档为准,如更多的术语以及它们各代表什么过程...

学习过程中,多写几次总是会记得深刻些。

相关文章
|
缓存 NoSQL Linux
Linux调试
本文介绍了Linux调试、性能分析和追踪的培训资料,涵盖调试、性能分析和追踪的基础知识及常用工具。
782 63
Linux调试
|
人工智能 前端开发 API
一种基于通义千问prompt辅助+Qwen2.5-coder-32b+Bolt.new+v0+Cursor的无代码对话网站构建方法
本文介绍了当前大模型应用的趋势,从单纯追求参数量转向注重实际应用效果与效率,重点探讨了结合大模型的开发工具,如Bolt.new、v0、Cursor等,如何形成完整的AI工具链,助力开发者高效构建、优化和部署应用。通过实例演示了从项目创建、前端优化到后端代码改写的全过程,强调了提示词设计的重要性,并推荐了适用于不同场景的工具组合方案。
生物降解塑料:环境友好的包装解决方案
【10月更文挑战第16天】在当今社会,塑料污染已成为严重的环境问题,威胁生态与健康。生物降解塑料作为一种创新材料,能在自然条件下被微生物分解成无害物质,分为生物基和石化基两类。它不仅减少了环境污染,还降低了资源消耗,推动了循环经济的发展。目前,生物降解塑料已广泛应用于食品、农业、医疗及零售包装领域,有效减轻了传统塑料带来的环境负担。尽管面临成本和性能等方面的挑战,但随着科技进步和环保意识提升,生物降解塑料有望成为包装行业的主流材料,助力实现绿色可持续发展。
|
存储 弹性计算 安全
阿里无影云电脑适合什么人群?
阿里无影云电脑适合什么人群?阿里云无影云电脑可用于高数据安全管控、高性能计算等要求的金融、设计、视频、教育等领域,适用于多种办公场景,如远程办公、多分支机构、安全OA、短期使用、专业制图等
392 0
|
存储 分布式计算 分布式数据库
大数据技术原理与应用 期末复习 知识点全总结(林子雨版
大数据技术原理与应用 期末复习 知识点全总结(林子雨版
3159 1
|
消息中间件 编解码 JavaScript
我终于决定要放弃 okhttp、httpClient,选择了这个牛逼的神仙工具!贼爽! 上
我终于决定要放弃 okhttp、httpClient,选择了这个牛逼的神仙工具!贼爽! 上
|
机器学习/深度学习 缓存 物联网
RFID 阅读器介绍 | 学习笔记
快速学习 RFID 阅读器介绍
RFID 阅读器介绍 | 学习笔记
|
大数据 分布式数据库 数据库
云栖专辑 | 阿里开发者们的20个感悟,一通百通
2018年12月20日,云栖社区3岁。阿里巴巴常说“晴天修屋顶”,所以我们特别制作了这个专辑——分享给开发者们20个阿里故事,50本书籍。
268143 0
|
JSON Kubernetes Cloud Native
如何在 20 分钟内给你的 K8s PaaS 上线一个新功能?
上个月,KubeVela 正式发布了, 作为一款简单易用且高度可扩展的应用管理平台与核心引擎,可以说是广大平台工程师用来构建自己的云原生 PaaS 的神兵利器。 那么本文就以一个实际的例子,讲解一下如何在 20 分钟内,为你基于 KubeVela 的 PaaS “上线“一个新能力。
如何在 20 分钟内给你的 K8s PaaS 上线一个新功能?
|
传感器 人工智能 监控
物联网在教育领域的应用
有了物联网,可能性是无限的。未来的教育机构将提供更大的安全性、更多的学生机会、更多的互动性和更好的学习机会。