还只是听说过Generator函数?那就来全面了解它吧

Generator函数

基本概念

Generator函数时ES6提供的一种异步编程解决方案;

一、语法上:

  1. 可以理解为一个状态机,封装了多个内部状态
  2. 执行Generator函数会返回一个遍历器对象

二、形式上的特点:

  1. function命令和函数名之间有一个星号*(星号位置没有明确规定,我们一般紧跟function后)
  2. 函数内部使用yield定义不同的状态
      function* genetatorTest() {yield "start";yield "running";return "finished";}

三、调用与执行-分段

调用也是使用小括号,但是调用Generator函数后,该函数并不执行,返回的也不是函数运行的结果,而是一个遍历器对象 Iterator Object

Generator函数分段执行,yield语句是暂停执行的标记,而next 方法可以恢复执行

Generator函数返回的遍历器对象每次调用 next() 方法,内部指针就从函数头部或上一次停下来的方法开始执行,直到遇到下一条 yield 或 return 语句为止

next方法返回一个对象 { value: 状态值, done: 遍历是否完成boolean}

      function* genetatorTest() {console.log(1);yield "start";console.log(2);yield "running";return "finished";}const obj = genetatorTest();console.log(obj);const re1 = obj.next();  // {value: 'start', done: false}console.log(re1);const re2 = obj.next();  // {value: 'running', done: false}console.log(re2);const re3 = obj.next();  // {value: 'finished', done: true}console.log(re3);console.log(obj.next());  // {value: undefined, done: true}

调用Generator函数的时候,函数并没有执行,只是返回了一个遍历器对象,因此并没有打印1

  1. 第一次调用next(),函数开始执行,打印出1,直到遇到yield停下; 返回一个对象包含value(当前内部状态值,yield 或 return 后面的值)和done(布尔值,遍历是否结束)属性
  2. 第二次调用next(), 函数接着执行,遇到yield停下
  3. 第三次调用next(), 函数接着执行,遇到return,函数执行完成;如果没有return语句,就执行到函数结束
  4. 第四次调用,函数已经执行完成了,返回对象属性为undefined,done为true,以后调用都是这个值

yield表达式

一、暂停的标志

只有调用了next 方法才会执行 yield 语句后面的表达式,因此等于为 JS 提供了惰性求值的功能

二、yield和return的比较

相似点:都可以返回紧跟后面的表达式的值

区别:每次遇到yield就会暂停执行,下一次会从该位置继续往下执行;而return没有位置记忆的功能,遇到return整个函数终止执行没有多个返回值页不能暂停

三、Generator函数可以不使用yield,此时就是一个单纯的暂缓执行函数

      function* generator() {console.log("命运的此轮停止旋转");}const iObj = generator();iObj.next();

四、yield表达式只能用在 Generator 函数里面,用在其他地方会报错

      function fn() {console.log('这样会报错');yield 'test'  // Uncaught SyntaxError: Unexpected string}fn()

五、yield表达式如果用在另一个表达式中,必须放在小括号中

      function* demo(x) {console.log(1 + (yield));console.log(2 + (yield 1 + x));}

六、yield表达式用作函数的参数或放在复制表达式的右边,可以不加小括号

      function* demo(x) {const yy = yield 11;generator(yield x);}

next()方法

next方法的运行逻辑

  1. 遇到yield语句就暂停执行后面的操作,并将紧跟在yield后的表达式的值作为返回对象 value 的属性值
  2. 下一次调用 next 方法继续往下执行,直到遇到下一条 yield 语句
  3. 如果没有遇到新的 yield 语句,就一直运行到函数结束,直到遇到 return 语句为止,并将 return 语句后的表达式的值作为返回对象 value 的属性值
  4. 如果函数没有 return语 句,则返回对象 value 的属性值为 undefined

next方法的参数

yield语句本身没有返回值,或者说总是返回undefined,而next可以带一个参数,这个参数会被当作上一条yield的返回值

因此通过next方法的参数可以实现在Generator函数运行的不同阶段从外部注入不同值

next不传入参数:

      function* fn(x) {const y = yield x + 2;const z = 2 * (yield x);return x + y + z;}const fnObj = fn(2);fnObj.next();fnObj.next();const result = fnObj.next(); // {value: NaN, done: true}

分析:第一次调用next启动遍历器对象,遇到yield暂停,返回一个对象{value:4,done:false};第二次调用next,由于yield返回值为undefined,所以 y=undefined,遇到yield暂停,返回 {value:2,done:false};第三次调用next,z为2与undefined相乘,所以为NaN,最后返回对象 {value:NaN,done:true}

next传入参数:

      function* fn(x) {const y = yield x + 2;const z = 2 * (yield x);return x + y + z;}const fnObj = fn(2);fnObj.next();fnObj.next(4);fnObj.next(2);console.log(result);

分析:第一次调用next启动遍历器对象,遇到yield暂停,返回一个对象{value:4,done:false}; 第二次调用next,传入的参数4作为上一条yield表达式的返回值,所以 y=4,遇到yield暂停,返回 {value:2,done:false};第三次调用next,传入参数2,所以z=2*2=4;最后返回对象 {value:10,done:true}

第一次使用next方法传参是无效的,只是用来启动遍历器对象

与Iterator接口的关系

Symbol.iterator方法等于该对象的遍历器对象生成函数,调用该函数会返回该对象的一个遍历器对象

由于Generator函数就是遍历器生成函数,因此可以把Generator函数赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口

      const myIterable = {};myIterable[Symbol.iterator] = function* () {yield 1;yield "一";yield "one";};console.log(...myIterable); // 1 "一" "one"

Generator函数执行后,返回一个遍历器对象,改对象本身也具有 [Synbol.iterator] 属性,执行后返回自身

      function* gen(params) {}const genObj = gen();genObj[Symbol.iterator]() === genObj;  // true

for of循环

for…of 循环可以自动遍历 Generator 函数生成的 Iterator 对象,并且此时不再需要调用next方法

一旦next方法返回的对象done属性为true for…of 就会终止循环,并且不包含该返回对象;所以下面代码return语句返回的值没有打印

      function* genFn() {yield 1;yield 2;yield "hello";yield "everybody";return "have a nice day";}for (let k of genFn()) {console.log(k); // 1 2 "hello" "everybody"}

原生的 JavaScript 对象没有遍历接口,无法使用 for… of 循环,通过Generator函数为它加上这个接口就可以用了

      function* objectEntries(obj) {let propKeys = Reflect.ownKeys(obj);for (let key of propKeys) {yield [key, obj[key]];}}const tempObj = { name: "如花", age: 28, gender: "未知" };const arr = [];for (let k of objectEntries(tempObj)) {  // for...of遍历遍历器对象arr.push(k);console.log(k);}console.log(arr);// console.log(Object.entries(tempObj));

Generator.prototype.throw

Generator函数返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获

      function* genFunc(params) {try {yield;} catch (err) {console.log("函数内部捕获异常", err);}}const g = genFunc();g.next();try {g.throw("错误信息msg1");g.throw("错误信息msg2");} catch (err) {console.log("函数外部捕获异常", err);}

上面代码遍历器对象连续抛出两个异常,第一个被Generator函数内部捕获,第二个异常由于Generator函数内部的catch已经执行过了,不会再捕获这个错误了,所以这个错误抛出了Generator函数体,被外部的catch捕获

注意:这里都是使用遍历器对象的throw方法抛出异常,而不是全局的throw(),如果上面是使用全局的throw抛出异常,只有函数外的catch能捕获

如果于Generator函数内部没有 try… catch ,异常会被函数外的 catch 捕获

      function* genFunc(params) {yield;}const g = genFunc();g.next();try {g.throw("错误信息msg1");} catch (err) {console.log("函数外部捕获异常", err);}

如果于Generator函数内部有 try… catch ,那么遍历器的throw方法抛出的错误不影响下一次遍历,否则遍历直接终止

      function* genFunc(params) {try {yield 1;yield 2;yield 3;} catch (err) {console.log("内部捕获", err);}}const g = genFunc();g.next();console.log(g.throw());  // {value: undefined, done: true}console.log(g.next());  // {value: undefined, done: true}

这里捕获到错误,try中的代码是不会执行了的,如果generator中,错误之后还有代码会执行;这里后面没有了,所以执行了throw后整个函数就执行完了

抛出错误没捕获和throw一样,程序终止执行了

      function* genFunc(params) {yield 1;yield 2;}const g = genFunc();g.next();g.throw();  // 报错:Uncaught undefined

g.throw被捕获后会执行catch中代码,执行完后继续执行,直到遇到yield暂停;throw也会触发一次next的启动,让函数往下走

      function* genFunc(params) {try {yield 1;} catch (err) {console.log("内部捕获", err);}yield 2;yield 3;}const g = genFunc();g.next();console.log(g.throw()); // {value: 2, done: false}console.log(g.next()); // {value: 3, done: false}

Generator.prototype.return

返回给定的值,并中介Generator函数的遍历

使用return方法状态直接变为done,后面再调用next,就是 {value: undefined, done: true}

      function* genFunc(params) {yield 1;yield 2;yield 3;}const g = genFunc();g.next();console.log(g.return("终止了"));  // {value: '终止了', done: true}console.log(g.next());  // {value: undefined, done: true}

当Generator函数内部有try…finally ,调用return方法就开始执行finally代码,finally中代码执行完再执行return方法

      function* generatorFn() {yield 1;try {yield 2;yield 3;} finally {yield 4;yield 5;}}const iteratorObj = generatorFn();const obj11 = iteratorObj.next(); //{value: 1, done: false}const obj22 = iteratorObj.next(); //{value: 2, done: false}const obj33 = iteratorObj.return(66); // {value: 4, done: false}const obj44 = iteratorObj.next(); // {value: 5, done: false}const obj55 = iteratorObj.next(); // {value: 66, done: true}const obj66 = iteratorObj.next(); // {value: undefined, done: true}

yield*表达式

yield* 语句可以Generator函数执行另一个Generator函数

如果直接调用gen1()是没有效果的;如果没有加*, 直接使用 yield 调用,就是返回一个遍历器对象

      function* gen1(params) {yield 1;yield 2;}function* gen2(params) {yield 3;yield* gen1();yield 4;}

等同于

      function* gen2(params) {yield 3;yield 1;yield 2;yield 4;}
      for (let k of gen2()) {console.log(k);  // 3 1 2 4}

Generator函数this

Generator 函数返回的总是遍历器对象,不是this对象

问题:

  1. Generator 函数在this对象上面添加了一个属性,但是该函数的实例对象拿不到这个属性
  2. Generator 函数不能跟new命令一起用,会报错

解决:首先,使用call方法将 Generator 函数的this绑定成自身的protoytype,再将 Generator 函数改成构造函数,就可以对它执行new命令了, Generator 函数中this通过call指向了gen的原型

      function* gen() {this.a = 1;  // this === gen.prototypeyield (this.b = 2);}function F() {return gen.call(gen.prototype);}var f = new F();f.next(); // Object {value: 2, done: false}f.next(); // Object {value: undefined, done: true}console.log(f.a); // 1console.log(f.b); // 2

Generator函数与状态机制

generator是实现状态机的最佳解构

调用一次函数就修改一次bool的状态值

      var bool = true;var changeStatus = function () {console.log(bool);if (bool) {console.log("1");} else {console.log("2");}bool = !bool;};

等价与generator的写法

      function* statusGen(params) {while (true) {console.log("1");yield;console.log("2");yield;}}

这种写法不需要保存状态的外部遍历,更加简洁,安全(状态不会被非法篡改)

应用

异步操作的同步化表达

      function* loadFn() {const dom = document.querySelector(".box");dom.innerText = "加载中";const re = yield asyncTask();dom.innerText = re;}function asyncTask() {setTimeout(() => {loadingObj.next("result:请求到的数据");}, 3000);}const loadingObj = loadFn();loadingObj.next();

控制流管理

      step1(function (value1) {step2(value1, function (value2) {step3(value2, function (value3) {});});});

promise改写:

      Promise.resolve(step1).then(step2).then(step3).catch((e) => {});

generator写法:

      function* runTask(params) {try {yield step1(value1);yield step1(value1);yield step1(value1);} catch (e) {}}

部署Iterator接口

利用Generator函数可以在任意对象上部署Iterator接口

Generator函数的异步应用

异步

异步:可以理解为一个任务不是连续完成的,任务完成了一部分之后,过了会才执行剩余部分

ES6之前,异步编程的方案大概有以下几种方案:回调函数、事件监听、订阅发布、promise

回调函数

      function fn2(err) {if (err) throw err;console.log("执行第二部分");}function fn(callback = fn2) {try {console.log(ff);console.log("执行第一部分");callback();} catch (err) {callback(err);}}fn();

将任务分段处理,通过调用回调函数,执行其他部分

问题:多个函数的嵌套,多层函数嵌套形成回调地狱,不好阅读、维护

promise

promise就是为了解决回调地狱而被提出来的

它将回调函数的嵌套改成了链式调用(.then和.catch)

问题:代码冗余,多个then的堆积

generator与协程

协程是程序运行的一种方式,是一种多任务的解决方案,可以理解为 “协作的线程” 或 “协作的函数”

子例程与协程的区别:

子例程采用堆栈式先进后出的执行方式,只有当调用的子函数完全执行完毕,才会结束执行夫函数;多个线程(或函数)可以并行执行,但是只有一个处于正在运行状态,其它处于暂停状态,线程(或函数)之间可以交换执行权

在内存中,子例程只是用一个栈,而协程使用多个栈,但是只有一个栈处于正在运行状态,协程是以多占用内存为代价实现多任务的并行运行

由于JS是单线程语言,只能保持一个调用栈。引入协程后,每个任务可以保持自己的调用栈。好处就是抛出错误的时候可以找到原始的调用栈

协程有点像函数,又有点像线程,它的大概执行流程:

  1. 协程A开始执行
  2. 协程A执行到一半,进入暂停状态,执行权转移到携程B中
  3. 一段时间后,协程B交换执行权
  4. 协程A恢复执行

Generator函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因;另外函数体内外的数据交互和错误处理机制也使得它能够更好处理成异步编程

generator对于异步任务的封装

      const fetch = require("node-fetch");function* gen() {const url = "https://api.github.com/users/github";const result = yield fetch(url);return result;}const genObj = gen();genObj.next().value.then((res) => {return data.json();}).then((res) => {genObj.next(res);});

Thunk函数

Thunk函数是自动执行Generator函数的一种方法

Thunk函数介绍

      const x = 1;function fn(params) {return params + 1;}fn(x + 2);

参数求值策略:什么时候计算x+2

1》传值调用

在进入函数体之前就计算参数的值

2》传名调用

直接将表达式传入函数体,只在用到它的时候求值

Thunk函数是“传名调用”的一种实现方式;就是将函数参数放到一个临时函数中,再将这个临时函数传入函数体,这个临时函数就是Thunk函数

      const x = 1;function fn(params) {return params + 1;}fn(x + 2);

等同于

      const thunk = function () {return x + 2;};function fn() {return thunk() + 1;}

JS中的Thunk函数

JS语言是传值调用,它的Thunk函数含义有所不同

它是将多参函数替换成一个只接受回调函数作为参数的单参函数

      //   正常的多参函数fs.readFile(fileName, callback);//   thunk版本const Thunk = function (fileName) {return function (callback) {fs.readFile(fileName, callback);};};const readThunk = Thunk(fileName);readThunk(callback);

流程管理:

      function* gen(params) {// ……}const g = gen(0);const res = g.next();while (!res.done) {g.next();}

前面有看过这段代码:Generator函数gen回自动执行完所有步骤

但是不适合异步操作,这时Thunk函数就派上用处了

基于回调的自动执行

基于Promise的自动执行

      function run(gen) {const genObj = gen();function next(data) {const result = genObj.next(data);if (result.done) return result.value;result.value.then((data) => {next(data);});}next();}function* gen(params) {re1 = yield asncPromise(11);console.log(1);re2 = yield asncPromise(22);console.log(2);re3 = yield asncPromise(33);console.log(3);re4 = yield asncPromise(44);console.log(4);return re4;}function asncPromise(num) {return new Promise((resolve) => {setTimeout(() => {console.log(num);resolve(12);},  Math.random() * 1000);});}run(gen);

async、await与generator

Generator函数:

      function asyncOperate(num) {return new Promise((resolve) => {const time = Math.random() * 2000;setTimeout(() => {console.log(num, time);resolve(num * 2);}, time);});}function* gen() {const re1 = yield asyncOperate(1);const re2 = yield asyncOperate(2);console.log(re1, re2);}function run(gen) {const genObj = gen();function next(data) {const result = genObj.next(data);if (result.done) return result.value;result.value.then((data) => {next(data);});}next();}run(gen);

async await:

      async function fn() {const re1 = await asyncOperate(1);const re2 = await asyncOperate(2);console.log(re1, re2);}function asyncOperate(num) {return new Promise((resolve) => {const time = Math.random() * 2000;setTimeout(() => {console.log(num, time);resolve(num * 2);}, time);});}fn();

async函数实际上就是将Generator函数的星号 * 替换成 async ; 将 yield 替换成 await

async函数对Generator函数的改进

1》自带执行器,而Generator函数的执行依靠执行器,co模块就是用于Generator函数的自动执行

2》语义更加清晰,async说明函数中有异步操作,await是等待异步执行

3》返回值是promise,更加方便;Generator返回的是Iterator对象

4》适用性更广,co模块规定yield后面跟异步或promise,保证执行顺序,await后面可以是promise,也可以是原始类型值

await后面一般是跟一个Promise,如果不是,会被转为一个resolved状态的Promise

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

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

相关文章

word表格左侧边线明明有,但却不显示

如题,解决方法: 方案一: 1)选中表格 2)布局菜单--->自动调整 3)自动调整中,选择“根据窗口自动调整表格” 4)表格左侧边线就显示出来了。 方案二:把表格粘贴到新…

Kubernetes技术--Kubernetes架构组件以及核心概念

1.Kubernetes集群架构组件 搭建一个Kubernetes环境集群,其架构如下所示: 内容详解: Master:控制节点,指派任务、决策 Node:工作节点,实际干活的。 Master组件内容:

国产芯片设备达到3纳米,还打入台积电,美日荷被彻底赶出市场

由于众所周知的原因,荷兰和日本的光刻机对中国供应面临限制,其他芯片设备和材料也受到很大的限制,这促使国产芯片产业链积极完善,以实现纯国产芯片工艺,虽然在光刻机方面还稍微落后,不过有一项国产芯片设备…

day-03 基于TCP的服务器端/客户端

一.理解TCP和UDP TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种常见的传输层协议,用于在计算机网络中提供可靠的数据传输。 1.TCP: 连接导向:TCP是一种面向连接的…

Android深思如何防止快速点击

前言 其实快速点击是个很好解决的问题,但是如何优雅的去解决确是一个难题,本文主要是记录一些本人通过解决快速点击的过程中脑海里浮现的一些对这个问题的深思。 作者:流浪汉kylin 链接:https://juejin.cn/post/7197337416096055…

Cookie中的SameSite标示是什么

目录 背景介绍 原理 MDN上有比较完整的描述 如何确定cookie是否已经设置相关属性 背景介绍 SameSit

Numerical Calculation 数值计算

Numerical Calculation 数值计算 数值计算第一次实验(C语言版) C语言编程常用数值计算的高性能实现 /* 高位全0&#xff0c;低N位全1 */ #define Low_N_Bits_One(N) ((1<<N) -1)/* 高位全1&#xff0c;低N位全0 */ #define Low_N_Bits_Zero(N) (~((1<<N)…

Mac 卸载 PyCharm 方法

Mac 系统下 PyCharm 没有一键卸载程序&#xff0c;也没有完全卸载的插件&#xff0c;若要彻底删除&#xff0c;除了在应用&#xff08;Application&#xff09;里删除 PyCharm 到垃圾桶外&#xff0c;还需要在终端&#xff08;Terminal&#xff09;执行删除相应的文件及文件夹。…

vue3+ts封装弹窗,分页封装

定义defaultDialog .vue <script lang"ts" setup> import { ref,toRefs,onUpdated } from vue import { ElMessageBox } from element-plus const props defineProps({//接收参数&#xff0c;父组件传的时候用:msg123的形式msg:String,show:{type:Boolean,defa…

Cesium Entity、dataSource添加与删除

Cesium Entity、dataSource添加与删除 一、Entity1. 添加实体2. 获取实体3. 删除实体 二、dataSource1. 添加数据源&#xff1a;2. 删除数据源 一、Entity 在Cesium中&#xff0c;要添加和删除实体和数据源可以使用以下代码&#xff1a; 1. 添加实体 var entity viewer.ent…

c++ qt--线程(一)(第八部分)

c qt–线程&#xff08;一&#xff09;&#xff08;第八部分&#xff09; 一.进程&#xff08;Process&#xff09; 在任务管理器中的进程页下&#xff0c;可以看到进程&#xff0c;任务管理器将进程分为了三类&#xff0c;应用、后台进程、window进程 应用&#xff1a; 打开…

针对java中list.parallelStream()的多线程数据安全问题我们采用什么方法最好呢?

当使用List.parallelStream()方法进行多线程处理时&#xff0c;可能会涉及到数据安全问题。下面是一些常见的方法来处理parallelStream()的多线程数据安全问题&#xff1a; 1. 使用线程安全的集合&#xff1a;Java中提供了线程安全的集合类&#xff0c;如CopyOnWriteArrayList…

Docker(三) 创建Docker镜像

一、在Docker中拉取最基本的Ubuntu系统镜像 搜索Ubuntu镜像 Explore Dockers Container Image Repository | Docker Hub 下载镜像 docker pull ubuntu:22.04 二、在镜像中添加自己的内容 使用ubuntu镜像创建容器 docker run -it ubuntu:20.04 /bin/bash 在容器中创建了一个文…

【高性能计算】opencl语法及相关概念(二):索引,队列,核函数

目录 数据并行及任务并行异构编程语言的共性opencl的划分方式opencl上下文定义以字符串为主的程序对象同一设备&#xff0c;多个命令队列同时执行多个核函数的示例 数据并行及任务并行 数据并行是将大规模的计算任务划分为多个子任务&#xff0c;并将这些子任务同时应用于不同…

Android AGP版本

做个记录&#xff1a; Android AGP版本 https://developer.android.com/studio/releases/gradle-plugin?hlzh-cn

Android动态可编辑长度列表

概述 在界面实现一个列表&#xff0c;用户可以随意给列表新增或者删除项目&#xff0c;在开发中比较常用&#xff0c;但是真正做起来又有点花时间&#xff0c;今天花时间做一个&#xff0c;以便在以后的开发中用到。 详细 运行效果&#xff1a; 二、实现思路&#xff1a; 1…

官方推荐使用的OkHttp4网络请求库全面解析(Android篇)

作者&#xff1a;cofbro 前言 现在谈起网络请求&#xff0c;大家肯定下意识想到的就是 okhttp 或者 retrofit 这样的三方请求库。诚然&#xff0c;现在有越来越多的三方库帮助着我们快速开发&#xff0c;但是对于现在的程序员来说&#xff0c;我们不仅要学会如何去用&#xff…

uniapp:蓝牙模块

模拟的是蓝牙设备签到/签出&#xff1a; 获取指定蓝牙设备蓝牙初始搜索次数限制&#xff0c;超过限制就停止搜索蓝牙连接失败次数限制&#xff0c;超过限制标识蓝牙连接失败&#xff08;离开蓝牙范围或其他原因&#xff09;自动重连指定蓝牙 const device ref<any>(nu…

MMSegmentation训练自己的语义分割数据集

全流程&#xff0c;训练语义分割数据集 数据标注json转mask 运行源码MMSegmentation模型选择运行部分 数据标注 # 安装 pip install labelme # 启动labelme labelme然后 ctrl N 开启多边形标注即可&#xff0c;命名类为person 之后会保存到同目录下json文件&#xff1a; js…

go-grpc环境编译搭建

1、安装protoc工具 # 地址 https://github.com/protocolbuffers/protobuf/releases # 下载Win64版本的 https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip # 设置环境变量2、安装protoc-gen-go插件 > go get -u github.com…