ES6 中的 generator 函数究竟是什么

ES6 中的 generator 函数究竟是什么

我们在学习 js 的时候应该都知道一个概念:一旦函数开始执行,它将运行直至完成,没有其他的代码可以在运行期间干扰它。

但是在 ES6 中引入了一种新型的函数,它不按照“运行至完成”的规则。这种新型的函数称为“generator”。

var a = 1;function func() {a++;func2();console.log(a)
}
function func2() {a++;
}
func();    
// 3

在上面的 func 函数中,a++ 运行完后会执行 func2() 函数,最终 a 的值为 3。

要是 func2() 不存在,但以某种方式依然可以在 a++ 和 console.log(a) 语句之间运行呢?这可能吗?

在抢占式(preemptive) 多线程语言中,func2() 去“干扰”并正好在两个语句之间那一时刻运行,实质上是可能的。但 JS 不是抢占式的,也不是多线程的。但是,如果 func() 本身可以用某种办法在代码的这一部分指示一个“暂停”,那么这种“干扰”(并发)的协作形式就是可能的。

比如:

var a = 1;
function *func() {a++;yield;console.log(a);
}
function bar() {a++
}

我们很可能在大多数其他的JS文档/代码中看到,一个 generator 的声明的格式为 function* func() { .. } 而不是上面的 function *func() { .. },它们之间唯一的区别是*的位置不同。这两种形式在功能性/语法上是完全一样的,还有第三种function*foo() { .. }(没空格)形式。

现在我们来运行一下上面的代码:

// 构建一个迭代器it来控制generator
let it = func();
a;                // 1
it.next();
a;                // 2
bar();
a;                // 3
it.next();
// 3

看起来很陌生对吧,我们来讲解一下这个过程:

  1. it = func(); func 不是一个普通函数,func() 并不会运行它,而是构建了一个用来控制它执行的迭代器(iterator);
  2. 我们观察 a 的值,还是 1;
  3. it.next() 启动了 func 函数的执行,并且运行到 func 函数的第一行,也就是 a++;之后在 yield 语句暂停,此时第一个 it.next() 调用结束,此时 func 函数还是运行的,不会被垃圾回收机制回收掉,但是它现在处于暂停节点,等待下一次的 next() 方法重新启动;
  4. 再次观察 a 的值,变成 2;说明运行了 a++;
  5. 此时执行 bar() 方法,再次对 a 进行递增;
  6. 最后执行 it.next(),重新从暂停的地方启动函数,执行 console.log(a),把 a 的值打印出来。

generator 是一种函数,它可以开始和停止一次或多次(在遇到 yield 关键词时会暂停,执行 next() 方法启动),甚至没必要一定要完成。

generator 是一个函数,这也就意味着它是可以接受参数及返回值的:

function *func(x, y) {return x * y;
}
// 传参跟普通函数一样
let it = func(1, 2)
it.next()           // {value: 2, done: true}

我们可以很明显发现,虽然 func(1, 2) 这样的传参跟普通函数一样,但是它实际上并不会执行,我们只是创建了迭代器对象,将它赋值给变量 it,当我们调用 it.next() 时,它指示 func(…) 从现在的位置向前推进,直到遇到一个 yield 或者到函数的最后。

next(…) 调用的结果是一个带有 value 属性的对象,它持有从 func(…) 返回的任何值(如果有的话)。换句话说,yield 导致在 generator 运行期间,一个值被从中发送出来,有点儿像一个中间的 return。

generator 除了接收参数和拥有返回值,它们还内置有更强大,更吸引人的输入/输出消息能力,这是通过使用 yield 和 next(…) 实现的:

function *func(x) {let y = x * (yield);return y;
}let it = func( 1 );// 开始执行 func(..)
it.next();          // {value: undefined, done: false}it.next( 2 );       // {value: 2, done: true}

it = func(1) 中将 1 作为参数 x 传入。之后调用 it.next() 开始启动 func()

之后在 func 内部,开始执行 let y = x * (yield),但是它遇到 yield 后暂停了,在下一个 next 方法中传递了一个值 2,此时 2 作为 yield 的结果。因此,赋值语句实际上是 let y = 1 * 2,最后把 y return 出去,作为 next() 方法的结果。

从上面的几个实例中我们可以看到,next 总是比 yield 多一个,因为第一次 next 是用于启动 generator 的,之后的其他 next 才会跟 yield 相对应。

除了可以通过 next 给 yield 传值外,还可以通过 yield 给 next 的结果赋值:

function *func(x) {let y = x * (yield "leo");return y;
}let it = func(1);
it.next();         // {value: 'leo', done: false}it.next(2);        // {value: 2, done: true}

因为只有一个暂停的 yield 才能接收这样一个被 next(…) 传递的值,但是当我们调用第一个 next() 时,在generator 的最开始并没有任何暂停的 yield 可以接收这样的值。语言规范和所有兼容此语言规范的浏览器只会忽略任何传入第一个 next() 的值。传递这样的值是一个坏主意,因为我们只不过创建了一些令人困惑的代码。所以,我们要记得总是用一个无参数的 next() 来启动 generator。

迭代器

一个 generator 本身在技术上讲并不是一个 iterable,但是当我们执行 generator 时,我们就能得到一个迭代器:

我们写一个无限数字序列生成器:

function *genNumber() {let nextNumber;while(true) {if(nextNumber === undefined) {nextNumber = 1;} else {nextNumber += 1;}yield nextNumber;}
}

通常来说在一个真实的 JS 程序中含有一个 while…true 循环通常是一件非常不好的事情,如果它没有一个 break 或 return 语句,那么它就很可能永远运行,并同步阻塞/锁定浏览器 UI。但是在 generator 函数中,如果循环中含有 yield,那它就是完全没有问题的,因为 generator 将在每次迭代后暂停,可以重新回到主程序或事件循环队列中。

let gen = genNumber();
for (var v of gen) {console.log( v );// 停止循环if (v > 500) {break;}
}

genNumber 是一个 generator 函数,调用这个 generator 可以生成一个迭代器给 for…of 使用,这个迭代器中有一个 Symbol.iterator 函数,会把 next 方法中的 value 读取出来。

而在循环中的 break 被调用后,func 实例基本上被留在了一个永远挂起的状态。

gen           // genNumber {<closed>}
gen.next();   // {value: undefined, done: true}

for…of 循环的“异常完成”(或者叫做 “提前终结”),一般是由break,return,或未捕捉的异常导致的——会向 generator 的迭代器发送一个信号,以使它终结。技术上讲,for…of 循环也会在循环正常完成时向迭代器发送这个信号。

虽然一个 for…of 循环将会自动发送这种信号,但是我们也可以通过调用 generator 的 return() 方法来手动发送:

let gen = genNumber();
for (var v of gen) {console.log( v );// 停止循环if (v > 500) {console.log(gen.return('停止循环'))    // {value: '停止循环', done: true}}
}

在 generator 中还有个特性,如果在内部指定一个 try…finally ,它将总是被执行,即便是 generator 从外部被完成。

function* genNumber() {try {let nextNumber;while (true) {if (nextNumber === undefined) {nextNumber = 1;} else {nextNumber += 1;}yield nextNumber;}} finally {console.log('finally');}
}
let gen = genNumber();
for (var v of gen) {console.log( v );// 停止循环if (v > 500) {console.log(gen.return('停止循环'))}
}
// ...
// finally
// {value: '停止循环', done: true}

从上面的打印可以看到,在执行 generator 的 return 方法后,会先触发内部的 finally 块(如果它存在的话),之后才打印出 return 的返回结果(返回的 value 设置为传入 return(…) 的任何值)。我们现在也不必再包含一个 break,因为 generator 的迭代器会被设置为 done:true,所以 for…of 循环会在下一次迭代时终结。

generator 处理异步

我们先来看一段代码:

function foo(x,y,cb) {ajax("http://xxxx?x=" + x + "&y=" + y,cb);
}
foo( 1, 2, function(err,text) {if (err) {console.error( err );}else {console.log( text );}
});

使用 generator 实现相同的逻辑:

function foo(x,y,cb) {ajax("http://xxxx?x=" + x + "&y=" + y,function (err, data) {if(err) {it.throw(err);} else [it.next(data)]});
}function *main() {try {let data = yield foo(1 ,2);console.log(data);} catch(err) {console.log(err);}
}var it = main();
it.next();

在generator内部的代码看起来完全是同步的(除了yield关键字本身),但实际上在 foo(…) 内部,操作可以完全是异步的。

除了写法上看起来跟同步的一样外,它还可以使用 try…catch 捕获。
看上面的代码,在 yield 之后,我们在 foo 函数内部使用 it.throw 抛出一个错误,使得这个错误被传递给 yield,最终被 try…catch 捕获。同样,也可以通过 yield 把错误传递给 next:

function *main() {var x = yield "leo";yield x.toLowerCase();	// 引发一个异常
}var it = main();it.next().value;			// leotry {// 给 yield 赋值为 2,导致 x 没有 toLowerCase 方法it.next( 42 ); 
}
catch (err) {console.error( err );	// TypeError
}

与 Promise 结合

在 async/await 出现之前,最有意思的就将 generator 与 Promise 进行结合使用:

function sendRequest(x, y) {return request('http:/xxx/?x=' + x + '&y=' + y);
}function *main() {try {let data = yield sendRequest(1, 2);console.log(data);} catch(err) {console.log(err)}
}let it = main();
let p = it.next().value;
p.then(function fulfilled(data) {it.next(data);},function rejected(err) {it.throw(err);}
)

在 async/await 出现之后,我们就有更简便的写法了:

async function main() {try {let data = await sendRequest(1, 2);console.log(data);} catch(err) {console.log(err);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/47691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库相关概念

MySQL 启动停止 MySQL安装完成之后&#xff0c;在系统启动时&#xff0c;会自动启动MySQL服务&#xff0c;无需手动启动。 手动的通过指令启动停止&#xff0c;以管理员身份运行cmd&#xff0c;进入命令行执行如下指令&#xff1a; net start mysql80 net stop mysql80 注意 …

JMeter使用小功能-(持续更新)

1、jmeter在同一个线程组内&#xff0c;uuid的复用 方式一&#xff1a; 方式二&#xff1a; 2、获得jMeter使用的线程总数 ctx.getThreadGroup().getNumberOfThreads()来表示活动线程总数 int threadNumctx.getThreadGroup().getNumThreads(); String threads Integer…

机械学习—零基础学习日志(高数05——函数概念与特性)

零基础为了学人工智能&#xff0c;真的开始复习高数 本小节讲解隐函数&#xff0c;有点神奇&#xff0c;我竟然完全没有隐函数记忆了。 隐函数 隐函数&#xff0c;我个人通俗理解就是&#xff0c;在复杂的环境里&#xff0c;发现纯净天地。例如&#xff0c;在外太空的某个大陆…

工信部信通院首份全景图 | 天空卫士产品26个版块多覆盖

近日&#xff0c;2024全球数字经济大会——数字安全生态建设专题论坛在北京成功举办&#xff0c;论坛由全球数字经济大会组委会主办&#xff0c;中国信息通信研究院和公安部第三研究所共同承办。论坛上&#xff0c;中国信息通信研究院隆重推出了业界首期《数字安全护航技术能力…

Flink集群搭建

&#xff08;1&#xff09;JAVA_HOME 配置 conf/flink-conf.yaml env.java.home &#xff08;2&#xff09;与Hadoop关联&#xff0c;如果确认使用Hadoop相关功能&#xff0c;需要关注对应的版本。如果不使用&#xff0c;则随意 使用flink版本 下载最新版本后&#xff0c;将存…

(四)原生js案例之手风琴效果

手风琴效果也是业务开发中一个比较常见的效果,类似QQ那样的折叠功能 效果预览 代码实现 必要的css * {margin: 0;padding: 0;}body {height: 100vh;background: linear-gradient(200deg, #ffff00 0%, #ee82ee 100%);overflow: hidden;}ul {list-style: none;}#List {margin:…

更新weibo sdk(去掉so以适配Android 15的16K Page Size的版本)记录

1、修改模块的gradle 从 implementation io.github.sinaweibosdk:core:13.6.1aar 改为 implementation io.github.sinaweibosdk:core:13.10.4aar 同步、运行报错&#xff1a; Didnt find class "androidx.core.content.FileProvider" 解决&#xff1a;据此提示…

Stable Diffusion:质量高画风清新细节丰富的二次元大模型二次元插图

今天和大家分享一个基于Pony模型训练的二次元模型&#xff1a;二次元插图。关于该模型有4个不同的分支版本。 1.5版本&#xff1a;loar模型&#xff0c;推荐底模型niji-动漫二次元4.5。 xl版本&#xff1a;SDXL模型版本 mix版本&#xff1a;光影减弱&#xff0c;减少SDXL版本…

C/C++ yaml 库

文章目录 一、yaml 介绍1.1 yaml 介绍1.2 yaml 教程1.3 yaml 在线工具1.4 yaml 出现背景 二、C/C yaml 库选型2.2 libfyaml2.3 yaml-cpp 一、yaml 介绍 1.1 yaml 介绍 YAML&#xff08;YAML Ain’t Markup Language&#xff09;是一种人类可读的数据序列化格式&#xff0c;通…

客户在哪儿AI的ToB获客服务和AI外呼机器人的有何不同

客户在哪儿AI全面提供服务已经快一个月了&#xff0c;我们收到了一些反馈。其中问的最多也是最有意思的问题就是&#xff0c;客户在哪儿AI与市面上其他几类服务于B端的科技产品有什么不同。既然如此&#xff0c;我们决定连出几篇文章把这件事给讲清楚。本期讲——客户在哪儿AI的…

模型训练中出现loss为NaN怎么办?

文章目录 一、模型训练中出现loss为NaN原因1. 学习率过高2. 梯度消失或爆炸3. 数据不平衡或异常4. 模型不稳定5. 过拟合 二、 针对梯度消失或爆炸的解决方案1. 使用torch.autograd.detect_anomaly()2. 使用 torchviz 可视化计算图3. 检查梯度的数值范围4. 调整梯度剪裁 三、更具…

uni-app开发日志:unicloud使用时遇到的问题解决汇总(不断补充)

插件安装后提示与原数据库表冲突&#xff08;2024.7.18&#xff09; 安装uni-admin后再安装uni-cms&#xff0c;在uni-admin中添加好菜单&#xff0c;结果提示该错误 回到hbuilder中uniCloud/database中找到冲突的部分 比较一下&#xff0c;选中老的删除 opendb-news-articl…

HarmonyOS根据官网写案列~ArkTs从简单地页面开始

Entry Component struct Index {State message: string 快速入门;build() {Column() {Text(this.message).fontSize(24).fontWeight(700).width(100%).textAlign(TextAlign.Start).padding({ left: 16 }).fontFamily(HarmonyHeiTi-Bold).lineHeight(33)Scroll() {Column() {Ba…

eclipse免安装版64位 2018版本

前言 eclipse是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言&#xff0c;它只是一个框架和一组服务&#xff0c;用于通过插件组件构建开发环境。 一、下载地址 下载地址&#xff1a;http://source/download 选择如下图红色框文件内容下载 二、安装步骤 1、…

【算法】数组中的第K个最大元素

难度&#xff1a;中等 题目&#xff1a; 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题…

Day16_集合与迭代器

Day16-集合 Day16 集合与迭代器1.1 集合的概念 集合继承图1.2 Collection接口1、添加元素2、删除元素3、查询与获取元素不过当我们实际使用都是使用的他的子类Arraylist&#xff01;&#xff01;&#xff01; 1.3 API演示1、演示添加2、演示删除3、演示查询与获取元素 2 Iterat…

分词任务介绍-(十)

分词任务 中文分词正向最大匹配实现方式一实现方式二 反向最大匹配双向最大匹配jieba分词上述分词方法的缺点总结基于机器学习 总结分词技术经验总结 中文分词 正向最大匹配 分词的步骤 1.收集整理一个词表&#xff0c;类似于字典。如下图 2.对于待分词的句子&#xff0c;或者…

k8s二次开发-kubebuiler一键式生成deployment,svc,ingress

一 Kubebuilder环境搭建 注&#xff1a;必须在当前的K8S集群有 nginx这个ingressclass rootk8s:~# kubectl get ingressclass NAME CONTROLLER PARAMETERS AGE nginx k8s.io/ingress-nginx <none> 19h1.1 下载kubebuilder wget https://gi…

机器学习西瓜书笔记(一)

机器学习西瓜书笔记 第一章(chapter 1) 绪论 (参考机器学习西瓜书)第一节(section 1)引言第二节(section 2)基本术语第三节(section 3)假设空间第四节(section 4)归纳偏好第五节(section 5)发展历程第六节(section 6)应用现状第一章(chapter 1) 绪论 (参考机…

监测vuex中state的变化

在Vuex中&#xff0c;如果你想要监测state的变化并在变化时调用相应的函数&#xff0c;有几种方法可以实现这个需求。但需要注意的是&#xff0c;Vuex官方推荐的方式是通过getter来派生state的新状态&#xff0c;或者通过action来响应state的变化。不过&#xff0c;如果你确实需…