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);     });  // 模拟API function 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函数,并处理其内部异步操作的完整机制。

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

DeepL Write

DeepL推出的AI驱动的写作助手,在几秒钟内完善你的写作

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

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

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

    const generator = generatorFunc();

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

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

    next()

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

    yield

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

  3. 处理

    next()

    方法的返回值: 每次调用

    generator.next(previousValue)

    ,都会返回一个包含

    value

    done

    属性的对象。

    • 检查
      done

      属性: 如果

      done

      true

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

    • 处理
      value

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

      value

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

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

    value

    是一个Promise,执行器会使用

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

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

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

    generator.throw(error)

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

    try...catch

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

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

    next()

    时Generator还没有

    yield

    过任何值。

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

yield

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

co

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

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

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

async/await

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

相同点:

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

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

不同点:

  1. 底层机制:

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

      yield

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

      yield

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

    • async/await

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

      async

      函数会自动返回一个Promise,

      await

      关键字则会暂停

      async

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

  2. 错误处理:

    • Generator协程: 错误处理需要执行器将Promise的拒绝通过
      generator.throw(error)

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

      try...catch

      捕获。

    • async/await

      await

      表达式可以直接在

      try...catch

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

  3. 灵活性与控制力:

    • 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

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

javascript es6 java js 前端 json 工具 后端 ai 代码可读性 red JavaScript 中间件 es6 try throw catch Error const 递归 循环 并发 JS function 对象 promise 异步 数据库

上一篇
下一篇
text=ZqhQzanResources