0

0

在JavaScript中如何使用Generator的方法

亚连

亚连

发布时间:2018-06-14 16:08:54

|

1608人浏览过

|

来源于php中文网

原创

generator 是一种非常强力的语法,但它的使用并不广泛。这篇文章主要介绍了如何在 javascript 中使用 generator,需要的朋友可以参考下

Generator 是一种非常强力的语法,但它的使用并不广泛(参见下图 twitter 上的调查!)。为什么这样呢?相比于 async/await,它的使用更复杂,调试起来也不太容易(大多数情况又回到了从前),即使我们可以通过非常简单的方式获得类似体验,但是人们一般会更喜欢 async/await。

 

然而,Generator 允许我们通过 yield 关键字遍历我们自己的代码!这是一种超级强大的语法,实际上,我们可以操纵执行过程!从不太明显的取消操作开始,让我们先从同步操作开始吧。

我为文中提到的功能创建了一个代码仓库 —— github.com/Bloomca/obs…

批处理 (或计划)

立即学习Java免费学习笔记(深入)”;

执行 Generator 函数会返回一个遍历器对象,那意味着通过它我们可以同步地遍历。为什么我们想这么做?原因有可能是为了实现批处理。想象一下,我们需要下载 1000 个项目,并在表格中逐行的显示它们(不要问我为什么,假设我们不使用框架)。虽然立刻展示它们没有什么不好的,但有时这可能不是最好的解决方案 —— 也许你的 MacBook Pro 可以轻松处理它,但普通人的电脑不能(更别说手机了)。所以,这意味着我们需要用某种方式延迟执行。

请注意,这个例子是关于性能优化,在你遇到这个问题之前,没必要这样做 ——过早优化是万恶之源!

// 最初的同步实现版本
function renderItems(items) {
 for (item of items) {
 renderItem(item);
 }
}
// 函数将由我们的执行器遍历执行
// 实际上,我们可以用相同的同步方式来执行它!
function* renderItems(items) {
 // 我使用 for..of 遍历方法来避免新函数的产生
 for (item of items) {
 yield renderItem(item);
 }
}

没有什么区别是吧?那么,这里的区别在于,现在我们可以在不改变源代码的情况下以不同方式运行这个函数。实际上,正如我之前提到的,没有必要等待,我们可以同步执行它。所以,来调整下我们的代码。在每个 yield 后边加一个 4 ms(JavaScript VM 中的一个心跳) 的延迟怎么样?我们有 1000 个项目,渲染将需要 4 秒 —— 还不错,假设我想在 2 秒之内渲染完毕,很容易想到的方法是每次渲染 2 个。突然使用 Promise 的解决方案将变得更加复杂 —— 我们必须要传递另一个参数:每次渲染的项目个数。通过我们的执行器,我们仍然需要传递这个参数,但好处是对我们的 renderItems 方法完全没有影响。

function runWithBatch(chunk, fn, ...args) {
 const gen = fn(...args);
 let num = 0;
 return new Promise((resolve, promiseReject) => {
 callNextStep();
 function callNextStep(res) {
  let result;
  try {
  result = gen.next(res);
  } catch (e) {
  return reject(e);
  }
  next(result);
 }
 function next({ done, value }) {
  if (done) {
  return resolve(value);
  }
  // every chunk we sleep for a tick
  if (num++ % chunk === 0) {
  return sleep(4).then(proceed);
  } else {
  return proceed();
  }
  function proceed() {
  return callNextStep(value);
  }
 }
 });
}
// 第一个参数 —— 每批处理多少个项目
const items = [...];
batchRunner(2, function*() {
 for (item of items) {
 yield renderItem(item);
 }
});

正如你所看到的,我们可以轻松改变每批处理项目的个数,不去考虑执行器,回到正常的同步执行方式 —— 所有这些都不会影响我们的 renderItems 方法。

取消

我们来考虑下传统的功能 —— 取消。在我promises cancellation in general ( 译文:如何取消你的 Promise? ) 这篇文章中已经详细谈到了。所以我会使用其中一些代码:

function runWithCancel(fn, ...args) {
 const gen = fn(...args);
 let cancelled, cancel;
 const promise = new Promise((resolve, promiseReject) => {
 // define cancel function to return it from our fn
 // 定义 cancel 方法,并返回它
 cancel = () => {
  cancelled = true;
  reject({ reason: 'cancelled' });
 };
 onFulfilled();
 function onFulfilled(res) {
  if (!cancelled) {
  let result;
  try {
   result = gen.next(res);
  } catch (e) {
   return reject(e);
  }
  next(result);
  return null;
  }
 }
 function onRejected(err) {
  var result;
  try {
  result = gen.throw(err);
  } catch (e) {
  return reject(e);
  }
  next(result);
 }
 function next({ done, value }) {
  if (done) {
  return resolve(value);
  }
  // 假设我们总是接收 Promise,所以不需要检查类型
  return value.then(onFulfilled, onRejected);
 }
 });
 return { promise, cancel };
}

这里最好的部分是我们可以取消所有还没来得及执行的请求(也可以给我们的执行器传递类似AbortController 的对象参数,所以它甚至可以取消当前的请求!),而且我们没有修改过自己业务逻辑中的一行的代码。

暂停/恢复

Shopxp网上购物系统
Shopxp网上购物系统

Shopxp购物系统历经多年的考验,并在推出shopxp免费购物系统下载之后,收到用户反馈的各种安全、漏洞、BUG、使用问题进行多次修补,已经从成熟迈向经典,再好的系统也会有问题,在完善的系统也从在安全漏洞,该系统完全开源可编辑,当您下载这套商城系统之后,可以结合自身的技术情况,进行开发完善,当然您如果有更好的建议可从官方网站提交给我们。Shopxp网上购物系统完整可用,无任何收费项目。该系统经过

下载

另一个特殊的需求可能是暂停/恢复功能。你为什么想要这个功能?想象一下,我们渲染了 1000 行数据,而且速度非常慢,我们希望给用户提供暂停/恢复渲染的功能,这样他们就可以停止所有的后台工作读取已经下载的内容了。让我们开始吧!

// 实现渲染的方法还是一样的
function* renderItems() {
 for (item of items) {
 yield renderItem(item);
 }
}
function runWithPause(genFn, ...args) {
 let pausePromiseResolve = null;
 let pausePromise;
 const gen = genFn(...args);
 const promise = new Promise((resolve, reject) => {
 onFulfilledWithPromise();
 function onFulfilledWithPromise(res) {
  if (pausePromise) {
  pausePromise.then(() => onFulfilled(res));
  } else {
  onFulfilled(res);
  }
 }
 function onFulfilled(res) {
  let result;
  try {
  result = gen.next(res);
  } catch (e) {
  return reject(e);
  }
  next(result);
  return null;
 }
 function onRejected(err) {
  var result;
  try {
  result = gen.throw(err);
  } catch (e) {
  return reject(e);
  }
  next(result);
 }
 function next({ done, value }) {
  if (done) {
  return resolve(value);
  }
  // 假设我们总是接收 Promise,所以不需要检查类型
  return value.then(onFulfilledWithPromise, onRejected);
 }
 });
 return {
 pause: () => {
  pausePromise = new Promise(resolve => {
  pausePromiseResolve = resolve;
  });
 },
 resume: () => {
  pausePromiseResolve();
  pausePromise = null;
 },
 promise
 };
}

调用这个执行器,可以给我们返回一个具有暂停/恢复功能的对象,所有这些都可以轻松得到,还是使用我们之前的业务代码!所以,如果你有很多"沉重"的请求链,需要耗费很长时间,而你想给你的用户提供暂停/恢复功能的话,你可以随意在你的代码中实现这个执行器。

错误处理

我们有个神秘的 onRejected 调用,这是我们这部分谈论的主题。如果我们使用正常的 async/await 或 Promise 链式写法,我们将通过 try/catch 语句来进行错误处理,如果不添加大量的逻辑代码就很难进行错误处理。通常情况下,如果我们需要以某种方式处理错误(比如重试),我们只是在 Promise 内部进行处理,这将会回调自己,可能再次回到同样的点。而且,这还不是一个通用的解决方案 —— 可悲的是,在这里甚至 Generator 也不能帮助我们。我们发现了 Generator 的局限 —— 虽然我们可以控制执行流程,但不能移动 Generator 函数的主体;所以我们不能后退一步,重新执行我们的命令。一个可行的解决方案是使用command pattern, 它告诉了我们 yield 结果的数据结构 —— 应该是我们需要执行此命令需要的所有信息,这样我们就可以再次执行它了。所以,我们的方法需要改为:

function* renderItems() {
 for (item of items) {
 // 我们需要将所有东西传递出去:
 // 方法, 内容, 参数
 yield [renderItem, null, item];
 }
}

正如你所看到的,这使得我们不清楚发生了什么 —— 所以,也许最好是写一些 wrapWithRetry 方法,它会检查 catch 代码块中的错误类型并再次尝试。但是我们仍然可以做一些不影响我们功能的事情。例如,我们可以增加一个关于忽略错误的策略 —— 在 async/await 中我们不得不使用 try/catch 包装每个调用,或者添加空的 .catch(() => {}) 部分。有了 Generator,我们可以写一个执行器,忽略所有的错误。

function runWithIgnore(fn, ...args) {
 const gen = fn(...args);
 return new Promise((resolve, promiseReject) => {
 onFulfilled();
 function onFulfilled(res) {
  proceed({ data: res });
 }
 // 这些是 yield 返回的错误
 // 我们想忽略它们
 // 所以我们像往常一样做,但不去传递出错误
 function onRejected(error) {
  proceed({ error });
 }
 function proceed(data) {
  let result;
  try {
  result = gen.next(data);
  } catch (e) {
  // 这些错误是同步错误(比如 TypeError 等)
  return reject(e);
  }
  // 为了区分错误和正常的结果
  // 我们用它来执行
  next(result);
 }
 function next({ done, value }) {
  if (done) {
  return resolve(value);
  }
  // 假设我们总是接收 Promise,所以不需要检查类型
  return value.then(onFulfilled, onRejected);
 }
 });
}

关于 async/await

Async/await 是现在的首选语法(甚至 co 也谈到了它 ),这也是未来。但是,Generator 也在 ECMAScript 标准内,这意味着为了使用它们,除了写几个工具函数,你不需要任何东西。我试图向你们展示一些不那么简单的例子,这些实例的价值取决于你的看法。请记住,没有那么多人熟悉 Generator,并且如果在整个代码库中只有一个地方使用它们,那么使用 Promise 可能会更容易一些 —— 但是另一方面,通过 Generator 某些问题可以被优雅和简洁的处理。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在JS中实现点击下拉菜单内容同步输入框

在vue中使用cli如何实现重构多页面脚手架

在JS中如何实现通过拖拽改变物体大小

在JS中如何改变单物体透明度

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

js

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

28

2026.02.28

Golang 工程化架构设计:可维护与可演进系统构建
Golang 工程化架构设计:可维护与可演进系统构建

Go语言工程化架构设计专注于构建高可维护性、可演进的企业级系统。本专题深入探讨Go项目的目录结构设计、模块划分、依赖管理等核心架构原则,涵盖微服务架构、领域驱动设计(DDD)在Go中的实践应用。通过实战案例解析接口抽象、错误处理、配置管理、日志监控等关键工程化技术,帮助开发者掌握构建稳定、可扩展Go应用的最佳实践方法。

23

2026.02.28

Golang 性能分析与运行时机制:构建高性能程序
Golang 性能分析与运行时机制:构建高性能程序

Go语言以其高效的并发模型和优异的性能表现广泛应用于高并发、高性能场景。其运行时机制包括 Goroutine 调度、内存管理、垃圾回收等方面,深入理解这些机制有助于编写更高效稳定的程序。本专题将系统讲解 Golang 的性能分析工具使用、常见性能瓶颈定位及优化策略,并结合实际案例剖析 Go 程序的运行时行为,帮助开发者掌握构建高性能应用的关键技能。

27

2026.02.28

Golang 并发编程模型与工程实践:从语言特性到系统性能
Golang 并发编程模型与工程实践:从语言特性到系统性能

本专题系统讲解 Golang 并发编程模型,从语言级特性出发,深入理解 goroutine、channel 与调度机制。结合工程实践,分析并发设计模式、性能瓶颈与资源控制策略,帮助将并发能力有效转化为稳定、可扩展的系统性能优势。

16

2026.02.27

Golang 高级特性与最佳实践:提升代码艺术
Golang 高级特性与最佳实践:提升代码艺术

本专题深入剖析 Golang 的高级特性与工程级最佳实践,涵盖并发模型、内存管理、接口设计与错误处理策略。通过真实场景与代码对比,引导从“可运行”走向“高质量”,帮助构建高性能、可扩展、易维护的优雅 Go 代码体系。

18

2026.02.27

Golang 测试与调试专题:确保代码可靠性
Golang 测试与调试专题:确保代码可靠性

本专题聚焦 Golang 的测试与调试体系,系统讲解单元测试、表驱动测试、基准测试与覆盖率分析方法,并深入剖析调试工具与常见问题定位思路。通过实践示例,引导建立可验证、可回归的工程习惯,从而持续提升代码可靠性与可维护性。

2

2026.02.27

漫蛙app官网链接入口
漫蛙app官网链接入口

漫蛙App官网提供多条稳定入口,包括 https://manwa.me、https

164

2026.02.27

deepseek在线提问
deepseek在线提问

本合集汇总了DeepSeek在线提问技巧与免登录使用入口,助你快速上手AI对话、写作、分析等功能。阅读专题下面的文章了解更多详细内容。

8

2026.02.27

AO3官网直接进入
AO3官网直接进入

AO3官网最新入口合集,汇总2026年可用官方及镜像链接,助你快速稳定访问Archive of Our Own平台。阅读专题下面的文章了解更多详细内容。

309

2026.02.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 9.8万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号