JS 协程与并发模型 – 使用 Generator 实现类似 async 的执行流程

Generator通过yield暂停函数执行,将异步操作结果以Promise形式返回,由执行器接收并等待其解决后,再通过next()将结果传回,实现异步流程的同步化写法。

js 协程与并发模型 - 使用 generator 实现类似 async 的执行流程

JS协程,尤其是通过Generator实现的那种,本质上就是一种手动控制异步流程的巧妙方式,它允许我们在JavaScript中模拟出类似同步代码的执行顺序,从而极大地简化了异步操作的复杂性,尤其是在

async/await

原生支持之前,它几乎是解决“回调地狱”和提升代码可读性的一个优雅方案。

解决方案

要使用Generator实现类似

async

的执行流程,核心思想是利用Generator函数的

yield

关键字来暂停函数的执行,并在外部通过

next()

方法恢复执行,同时处理

yield

出来的异步操作(通常是Promise)。这需要一个“执行器”或者说一个“协程运行器”来协调整个过程。

我们首先定义一个Generator函数,里面用

yield

来“暂停”那些需要等待结果的异步操作。

function* myAsyncFlow() {    console.log('Step 1: 开始获取用户数据...');    const userData = yield fetch('/api/users/1').then(res => res.json());    console.log('Step 2: 用户数据获取成功:', userData.name);    console.log('Step 3: 开始获取用户订单...');    const orders = yield fetch(`/api/users/${userData.id}/orders`).then(res => res.json());    console.log('Step 4: 用户订单获取成功:', orders.length, '个订单');    return { userData, orders };}

然后,我们需要一个执行器来驱动这个Generator。这个执行器会不断调用Generator的

next()

方法,如果

next()

返回的值是一个Promise,它会等待Promise解决,然后将解决的值作为参数传回给下一次

next()

调用。

function run(generatorFunc) {    const generator = generatorFunc(); // 获取迭代器    function step(nextValue) {        const { value, done } = generator.next(nextValue); // 驱动Generator        if (done) {            return Promise.resolve(value); // Generator执行完毕,返回最终结果        }        // 如果yield出来的是一个Promise,等待它解决        return Promise.resolve(value).then(            res => step(res), // Promise解决,将结果传回Generator            err => generator.throw(err) // Promise拒绝,将错误抛回Generator        );    }    return step(); // 启动执行}

现在,我们就可以像这样使用它:

run(myAsyncFlow)    .then(result => {        console.log('所有异步操作完成,最终结果:', result);    })    .catch(error => {        console.error('流程中发生错误:', error);    });// 模拟APIfunction fetch(url) {    return new Promise(resolve => {        setTimeout(() => {            if (url.includes('users')) {                resolve({                    json: () => Promise.resolve({ id: 1, name: 'Alice' })                });            } else if (url.includes('orders')) {                resolve({                    json: () => Promise.resolve([{ id: 101, amount: 50 }, { id: 102, amount: 75 }])                });            }        }, 1000);    });}

通过这种方式,原本需要层层嵌套回调或Promise链的代码,现在可以写成看起来像同步的、自上而下的流程,这在当时(

async/await

普及前)简直是革命性的。

Generator 如何在 JavaScript 中模拟异步流程的暂停与恢复?

Generator在JavaScript中模拟异步流程的暂停与恢复,其核心机制在于

yield

关键字和迭代器的

next()

方法。这不像传统的函数那样,一旦开始执行就必须一口气跑到结束。Generator函数(通过

function*

声明)执行时会返回一个迭代器对象。

当你第一次调用这个迭代器的

next()

方法时,Generator函数会开始执行,直到遇到第一个

yield

表达式。此时,函数的执行会立即“暂停”,

yield

后面跟着的值会被包装在一个

{ value: ..., done: false }

的对象中返回。这个

value

可以是任何东西,但在异步场景中,我们通常会

yield

一个Promise。

外部的执行器(就像我们上面写的

run

函数)接收到这个Promise后,它会负责等待这个Promise解决。一旦Promise解决(或者拒绝),执行器会再次调用迭代器的

next()

方法,并将Promise解决的值作为参数传进去。这个值会成为Generator函数内部

yield

表达式的返回值。

例如:

const result = yield somePromise;

somePromise

解决后,它的结果会通过

next()

的参数传回,并赋值给

result

变量。这样,Generator函数就能在异步操作完成后,从它暂停的地方继续执行,并且能拿到异步操作的结果,就好像那行代码是同步执行的一样。这种“协作式多任务”的模式,让开发者能够以更直观的方式处理复杂的异步逻辑,避免了传统回调或Promise链带来的心智负担。我个人觉得,理解了Generator的这个特性,就等于理解了

async/await

的底层原理,因为

async/await

本质上就是Generator和Promise的语法糖。

构建一个简易的 Generator 异步执行器需要哪些核心步骤?

构建一个简易的Generator异步执行器,通常需要以下几个核心步骤,这些步骤共同构成了一个能够驱动Generator函数,并处理其内部异步操作的完整机制。

获取Generator迭代器实例:执行器的第一步是接收一个Generator函数,并调用它来获取一个迭代器对象。例如

const generator = generatorFunc();

。这个迭代器是执行流程的入口。

定义一个递归或循环的驱动函数:这是执行器的“心脏”。这个函数会负责不断地调用迭代器的

next()

方法,直到Generator函数执行完毕。它通常会接收上一步

yield

表达式的结果作为参数,以便将其传回给Generator。

处理

next()

方法的返回值:每次调用

generator.next(previousValue)

,都会返回一个包含

value

done

属性的对象。

检查

done

属性: 如果

done

true

,说明Generator函数已经执行完毕,执行器应该停止驱动,并返回Generator的最终结果。处理

value

属性: 这是最关键的部分。如果

value

是一个Promise(这是我们期望的异步场景),执行器需要等待这个Promise解决。

等待Promise解决并传递结果:如果

value

是一个Promise,执行器会使用

Promise.resolve(value).then(...)

来等待它。一旦Promise解决,它会把解决的值作为参数,再次调用驱动函数,从而将结果“注入”回Generator函数中,让它从暂停的地方继续执行。

错误处理:异步流程中错误是不可避免的。执行器需要能够捕获Promise的拒绝(rejection),并通过

generator.throw(error)

方法将错误抛回Generator函数内部,这样Generator内部的

try...catch

块就能捕获并处理这些错误,保持了与同步代码类似的错误处理机制。

启动执行器:最后,需要一个初始调用来启动这个驱动函数,通常不带任何参数,因为它第一次调用

next()

时Generator还没有

yield

过任何值。

这些步骤形成了一个闭环,使得Generator函数能够像同步代码一样,在

yield

异步操作时暂停,等待结果,然后带着结果继续执行,极大地提升了异步代码的编写体验。这个模式在很多库中都有体现,比如早期的

co

库,就是基于这种思想实现的。

Generator 实现的协程与原生 async/await 有何异同,各自适用于哪些场景?

Generator实现的协程(或者说基于Generator的异步流程控制)与原生

async/await

在目标上高度一致:都是为了以同步代码的风格编写异步逻辑,摆脱回调地狱,提高代码可读性。然而,它们在实现机制和适用场景上存在一些显著的异同。

相同点:

同步化异步代码: 两者都允许开发者将异步操作写成看起来像同步的、自上而下的代码流程,极大地提升了可读性。避免回调地狱: 它们都是解决多层嵌套回调问题的有效方案。基于Promise:

async/await

是基于Promise的语法糖,而Generator实现的协程通常也依赖于Promise来管理异步操作的结果和错误。

不同点:

底层机制:

Generator协程: 更加底层和原始。它利用

function*

yield

来手动控制函数的暂停和恢复。它需要一个外部的“执行器”来驱动迭代器,处理

yield

出的Promise。你可以把它想象成一种“手动挡”的异步控制。

async/await

是ES2017引入的语言特性,是Promise的语法糖。

async

函数会自动返回一个Promise,

await

关键字则会暂停

async

函数的执行,直到它等待的Promise解决。它自带执行器,是“自动挡”的异步控制。

错误处理:

Generator协程: 错误处理需要执行器将Promise的拒绝通过

generator.throw(error)

方法“注入”回Generator,然后在Generator内部使用

try...catch

捕获。

async/await

await

表达式可以直接在

try...catch

块中捕获Promise的拒绝,与同步代码的错误处理方式完全一致,更为直观和方便。

灵活性与控制力:

Generator协程: 提供了更高的灵活性。

yield

可以暂停任何值,不仅仅是Promise。这意味着你可以用Generator实现更复杂的控制流,比如在Redux Saga中,你可以

yield

一个普通对象来描述一个副作用,而不是直接执行它。

async/await

专注于处理Promise。

await

只能等待Promise或其他thenable对象。它的设计目标就是简化异步操作,因此在处理非Promise的暂停/恢复场景时不如Generator灵活。

适用场景:

async/await

绝大多数现代异步编程场景: 无论是前端的API请求、用户交互,还是后端的数据库操作、文件I/O,

async/await

都是首选。它简洁、易用、错误处理直观,是JavaScript异步编程的“标准答案”。团队协作和维护: 由于其是语言原生特性,且约定俗成,团队成员更容易理解和维护使用

async/await

编写的代码。

Generator 实现的协程:

async/await

出现之前的过渡方案:

async/await

尚未普及的ES6时代,Generator协程(如

co

库)是解决异步流程控制的强大工具需要精细控制流的框架或库: 某些库或框架,例如Redux Saga,利用Generator的强大灵活性来构建复杂的副作用管理机制。它们不只是等待Promise,还可能

yield

出各种指令对象,由外部的中间件来解释执行。自定义异步抽象层: 当你需要构建一个非常规的异步流程控制机制,或者需要处理非Promise的暂停/恢复逻辑时,Generator提供了底层的能力去实现这些。

总的来说,对于日常开发,

async/await

是毫无疑问的首选,它就是为现代异步JavaScript而生的。而Generator协程则更像是一种底层工具,它揭示了

async/await

的魔法,并在一些需要极致灵活性和自定义控制流的特定场景下,依然能发挥其独特的价值。

以上就是JS 协程与并发模型 – 使用 Generator 实现类似 async 的执行流程的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1521342.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月20日 14:05:27
下一篇 2025年12月20日 14:05:47

相关推荐

  • JavaScript模板引擎原理与自定义实现

    模板引擎核心是将数据与模板结合生成HTML,通过解析变量如{name}并替换为数据值实现动态渲染。基本流程包括接收模板和数据、解析占位符、执行替换并返回结果。常见语法使用{{}}或{}标记变量,利用正则匹配并用replace进行替换。简易实现可直接替换变量,如compile函数处理{name}为da…

    2025年12月21日
    000
  • 使用HTML、CSS和JavaScript构建响应式图片轮播图教程

    本教程详细指导如何使用html、css和javascript构建一个功能完善的图片轮播图。文章将从html结构、css样式布局到javascript动态控制图片显示进行全面讲解,并提供示例代码和常见问题排查,确保读者能够理解并实现一个流畅的图片切换效果,避免图片垂直堆叠的常见问题。 在现代网页设计中…

    2025年12月21日 好文分享
    000
  • JS函数怎样定义函数性能监测_JS函数性能监测定义与执行时间计算方法

    答案是利用performance.now()或Date.now()记录函数开始和结束时间,通过时间差监测JavaScript函数执行性能。 在JavaScript中,监测函数的执行性能主要是通过记录函数开始和结束的时间差来实现。核心方法是利用performance.now()或Date.now())…

    2025年12月21日
    000
  • 使用Generator函数处理异步流程_js异步编程

    Generator函数是ES6引入的可通过yield暂停执行的特殊函数,返回迭代器对象;通过结合Promise与自动执行器,可实现类似async/await的同步化异步处理模式,是理解JavaScript异步演进的重要基础。 在 JavaScript 异步编程中,Generator 函数提供了一种更…

    2025年12月21日
    000
  • JS插件开发中如何管理事件_JavaScript插件事件处理机制详解

    答案:JavaScript插件应通过自定义事件实现解耦,封装on/off接口管理事件生命周期,使用命名空间防止冲突,支持链式调用并绑定正确上下文,确保灵活性与可维护性。 在JavaScript插件开发中,事件管理是核心功能之一。良好的事件处理机制不仅能提升插件的灵活性和可维护性,还能让使用者更方便地…

    2025年12月21日
    000
  • JS注解怎么实现文档化_ JS注解生成开发文档的流程与工具

    JSDoc是一种JavaScript结构化注释规范,通过@param、@returns等标签描述代码元素,并借助工具生成HTML文档,结合IDE支持和CI/CD可提升团队协作效率。 JavaScript本身不支持原生注解(Annotation)像Java那样的语法,但通过约定的注释格式和配套工具,可…

    2025年12月21日
    000
  • JS循环语句怎么编写_JS循环语句forwhile及doWhile使用方法

    for循环适用于已知循环次数的场景,语法包含初始化、条件判断和更新表达式;示例为打印1到5。 JavaScript中的循环语句用于重复执行一段代码,直到满足特定条件为止。常用的循环有for、while和do…while三种。它们各有特点,适用于不同场景。 for循环:已知循环次数时使用 for循…

    2025年12月21日
    000
  • JS怎样在Spring中实现分页查询_JS在Spring中实现分页查询的详细教程

    后端通过Spring Data JPA的Pageable实现分页接口,自动解析page、size、sort参数;2. 前端使用JS(如Axios)发送带分页参数的请求;3. 获取数据后,JS动态渲染列表内容并生成分页按钮;4. 优化用户体验,如添加加载状态、限制页码显示、支持条数切换和错误处理。 在…

    2025年12月21日
    000
  • JavaScript实现3D轮播图效果_javascript动画

    答案是利用CSS 3D变换和JavaScript实现立体旋转效果。通过HTML构建包含多个图片项的容器,使用CSS让图片沿Y轴均匀分布在圆环上,结合JavaScript控制旋转角度与自动或手动切换动画,形成3D轮播图。 要实现一个3D轮播图效果,核心是利用CSS 3D变换配合JavaScript控制…

    2025年12月21日
    000
  • 使用JavaScript和GitHub API程序化管理仓库文件

    本文详细介绍了如何使用javascript和github rest api程序化地在github仓库中添加或更新文件。核心内容包括:利用个人访问令牌(pat)进行认证,将文件内容进行base64编码,以及在更新现有文件时必须提供文件的sha值。通过分步指南和示例代码,读者将学会如何先通过get请求获…

    2025年12月21日
    000
  • 解决Electron应用中node-hid库在渲染进程中无法工作的问题

    本文旨在解决electron应用中原生node.js模块(如`node-hid`)在渲染进程中无法正常运行的问题。核心解决方案是利用electron的主进程拥有完整的node.js环境优势,在此进程中执行原生模块操作,并通过进程间通信(ipc)机制将结果安全地传递给渲染进程,从而确保应用功能正常并避…

    2025年12月21日
    000
  • JS注解怎么标注日期类型_ JS日期类型数据的注解使用与说明

    答案:JavaScript中无原生注解,但可通过JSDoc或TypeScript标注日期类型。JSDoc用@type {Date}、@param {Date}、@returns {Date}为变量、参数、返回值声明Date类型;TypeScript则直接使用Date进行静态类型标注,提升可读性与类型…

    2025年12月21日
    000
  • Java基础之有哪些注释方法?怎么用

    单行注释(//)用于行尾注释,2. 多行注释(/…/)可跨行注释代码块,3. 文档注释(/*…/)配合javadoc生成API文档,含@param、@return等标签,提升代码可读性。 Java 中有三种注释方式,分别是单行注释、多行注释和文档注释。它们用来给代码添加说明,…

    2025年12月21日
    000
  • 在Google Apps Script中实现HTML表格多列动态过滤

    本教程详细介绍了如何在google apps script项目中,通过javascript实现html表格数据的多列动态过滤功能。文章将指导您如何修改现有代码,使其能够遍历表格的每一行和行内的所有单元格,判断输入文本是否存在于任一单元格中,从而精确地显示或隐藏匹配的行,有效解决了仅在单列搜索的局限性…

    2025年12月21日
    000
  • js脚本如何获取当前时间_js获取当前时间并显示的完整代码教程

    使用Date对象可轻松获取当前时间。首先创建new Date()实例,再通过getFullYear()、getMonth()+1、getDate()等方法提取年月日时分秒,注意月份从0开始需加1。结合setInterval每秒调用updateClock函数,利用toLocaleDateString和…

    2025年12月21日
    000
  • JavaScript依赖注入与控制反转

    控制反转(IoC)将依赖创建交给外部容器,依赖注入(DI)是实现IoC的具体方式,通过构造函数、方法或属性注入依赖,实现组件解耦、易于测试与配置灵活,JavaScript可通过函数式编程或自定义容器实现DI/IoC。 依赖注入(Dependency Injection, DI)和控制反转(Inver…

    2025年12月21日
    000
  • 使用JavaScript实现一个简单的颜色选择器_javascript UI组件

    答案:通过HTML、CSS和JavaScript实现一个轻量级颜色选择器,用户点击预设色块即可选中颜色并实时显示。结构上使用div容器与data-color属性存储颜色值,JavaScript通过事件委托监听点击,动态更新选中状态及显示区域文本,CSS则美化界面,提供选中反馈效果,整体简洁可复用,适…

    2025年12月21日
    000
  • js生成器中next的使用

    生成器函数通过function*定义,使用yield暂停执行,调用后返回生成器对象,其next()方法控制执行并返回{value, done}对象;1. next()启动或恢复执行,每次遇到yield时暂停并返回值;2. 第二次及之后的next(arg)可向yield传参,作为上一个yield表达式…

    2025年12月21日
    000
  • JS Cookie怎么读写_JS Cookie读写操作与生命周期管理方法

    答案:通过原生JS可操作Cookie实现客户端存储。使用getCookie读取指定名称的Cookie值,setCookie设置带过期时间的Cookie,deleteCookie通过设置过去时间删除Cookie,需注意路径、编码及Secure、SameSite等安全属性,适用于身份认证等需与服务器共享…

    2025年12月21日
    000
  • 使用JavaScript实现一个简单的虚拟DOM_js框架原理

    虚拟DOM核心是用JS对象描述DOM结构,通过diff算法对比新旧节点,仅更新变化部分以提升性能。先用h函数创建vnode,再通过render函数将其渲染为真实DOM;当数据变化时,patch函数比较新旧vnode,复用相同节点,替换或修改差异部分,实现高效更新。该机制避免频繁操作真实DOM,显著提…

    2025年12月21日
    000

发表回复

登录后才能评论
关注微信