理解 Generator 的执行
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对象的迭代
每个迭代结果都包含 done 和 value :
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);
}
});
};
可能有些关联的文章:
文章仅供参考!!!关于更多Generator知识,以阅读文章开头官方文档为准,如更多的术语以及它们各代表什么过程...
学习过程中,多写几次总是会记得深刻些。
理解 Generator 的执行的更多相关文章
- 深入理解javascript中执行环境(作用域)与作用域链
深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...
- 深入理解JavaScript系列+ 深入理解javascript之执行上下文
http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html http://blog.csdn.net/hi_kevin/article/d ...
- 前端知识体系:JavaScript基础-原型和原型链-理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题
理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题(原文文档) 1.什么是执行上下文: 简而言之,执行上下文就是当前JavaScript代码被解析和执行时所在环境的抽象概念,Java ...
- 【CUDA 基础】3.2 理解线程束执行的本质(Part I)
title: [CUDA 基础]3.2 理解线程束执行的本质(Part I) categories: CUDA Freshman tags: 线程束分化 CUDA分支 toc: true date: ...
- 深入理解JS:执行上下文中的this(二)
目录 序言 Function.prototype.bind() 方法 箭头函数 参考 1.序言 在 深入理解JS:执行上下文中的this(一) 中,我们主要深入分析全局环境和函数环境中函数调用的 th ...
- 从底层理解Python的执行
摘要:是否想在Python解释器的内部晃悠一圈?是不是想实现一个Python代码执行的追踪器?没有基础?不要怕,这篇文章让你初窥Python底层的奥妙. [编者按]下面博文将带你创建一个字节码级别的追 ...
- 理解Javascript之执行上下文(Execution Context)
1>什么是执行上下文 Javascript中代码的运行环境分为以下三种: 全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境. 函数级别的代码 - 当执行一 ...
- 理解JS的执行环境
执行环境(Execution context,EC)或执行上下文,是JS中一个极为重要的概念 EC的组成 当JavaScript代码执行的时候,会进入不同的执行上下文,这些执行上下文会构成了一个执行上 ...
- 理解 Python 的执行方式,与字节码 bytecode 玩耍 (下)
上次写到,Python 的执行方式是把代码编译成bytecode(字节码)指令,然后由虚拟机来执行这些 bytecode 而 bytecode 长成这个样子: b'|\x00\x00d\x01\x0 ...
随机推荐
- 16-margin的用法
margin塌陷问题 当时说到了盒模型,盒模型包含着margin,为什么要在这里说margin呢?因为元素和元素在垂直方向上margin里面有坑. 我们来看一个例子: html结构: <div ...
- java基础-day19
第08天 异常 今日内容介绍 u 异常体系&异常处理 u Throwable常用方法&自定义异常 u 递归 第1章 异常产生&异常处理 1.1 异常概述 什 ...
- noip第16课作业
1. 猴子吃桃 [问题描述] 猴子第一天摘了若干个桃子,当即吃了一半,还不解馋,又多吃了一个:第二天,吃剩下的桃子的一半,还不过瘾,又多吃了一个:以后每天都吃前一天剩下的一半多一个,到第10天想再吃时 ...
- Swagger生成WebAPI文档
WebAPI2.0 项目可以使用Swagger能够轻易查看API文档,查看以下配置 1.打开程序包管理控制台输入: Install-Package Swashbuckle 2.在对应项目里的App_S ...
- Codeforces Round #265 (Div. 2) E. Substitutes in Number
http://codeforces.com/contest/465/problem/E 给定一个字符串,以及n个变换操作,将一个数字变成一个字符串,可能为空串,然后最后将字符串当成一个数,取模1e9+ ...
- JMS学习以及jms的实现activeMq
1.JMS规范介绍: http://www.cnblogs.com/hapjin/p/5431706.html http://elim.iteye.com/blog/1893038 http://bl ...
- 第5件事 做一个有taste的产品人
1.taste的意思是品位,也就是说产品经理应该是一个有品位的产品人.什么叫品位呢?品位指的是对事物有分辨与鉴赏的能力.品位是形象的展示,品位是内在气质的复出,品位是人生价值的体验,品位是道德修养的内 ...
- impress.js 中文注释
impress.js 中文注释 玄魂 /** * impress.js *(本翻译并未完全遵照原作者的注释翻译) * Impress.js 是受 Prezi启发,基于现代浏览器的 CSS3 与 Jav ...
- 实验1 单片机IO口应用及数码管显示
1. 单片机驱动蜂鸣器的实验: a) 说明:Lab51单片机实验板的蜂鸣器连接到单片机的P1.5 b) 基本要求:控制蜂鸣器每2秒响0.5秒. #include &l ...
- 在Asp.Net MVC中利用快递100接口实现订阅物流轨迹功能
前言 分享一篇关于在电商系统中同步物流轨迹到本地服务器的文章,当前方案使用了快递100做为数据来源接口,这个接口是收费的,不过提供的功能还是非常强大的,有专门的售后维护团队.也有免费的方案,类似于快递 ...