导读:本期聚焦于小伙伴创作的《如何使用 Generator 函数和 yield 关键字实现一个复杂的状态机?》,敬请观看详情。传统状态机常用 switch 或查表法维护流转逻辑,状态一多就容易嵌套混乱。Generator 函数配合 yield 能把每个状态写成顺序代码,运行时靠 next 暂停和恢复。本文说明如何用 function* 定义状态、用 yield 等待外部输入、用返回值表达转移,并给出订单流转示例。相比有限状态机库,这种写法可读性更高,也方便在异步流程里嵌入等待点,但需注意不能跨函数保存局部变量,以及迭代器只能单向推进的限制。

在 JavaScript 中,状态机常用于描述对象在不同阶段之间的流转,例如订单处理、游戏角色行为或协议解析。Generator 函数提供了用同步写法表达异步等待与状态暂停的能力,配合 yield 关键字,可以把复杂的状态切换逻辑拆解为线性代码,而不必维护庞大的条件分支表。

如何使用 Generator 函数和 yield 关键字实现一个复杂的状态机?

一、Generator 与状态机的基础原理

Generator 函数通过 function* 声明,调用后并不立即执行函数体,而是返回一个迭代器对象。每次调用迭代器的 next() 方法,函数会运行到下一个 yield 表达式处暂停,并把 yield 后面的值作为返回结果的 value 传出。这种“可暂停、可恢复”的特性,恰好对应状态机中“进入某状态、等待事件、转移到下一状态”的过程。

在传统的 switch 状态机中,我们通常用变量保存当前状态,再根据事件做分支判断。而使用 Generator,每一个 yield 点就自然成为一个状态停留点,函数内部的执行上下文(局部变量、执行位置)由引擎自动保存,无需手动维护状态变量。这使得状态逻辑可以用接近自然语言的顺序结构来表达。

1.1 最简状态机示例

下面用一个简单的红绿灯状态机展示基本写法。每次 next 调用推进一个状态,yield 返回当前状态名称。

function* trafficLight() {
  while (true) {
    yield '红灯';
    yield '绿灯';
    yield '黄灯';
  }
}

const light = trafficLight();
console.log(light.next().value); // 红灯
console.log(light.next().value); // 绿灯
console.log(light.next().value); // 黄灯
console.log(light.next().value); // 红灯

这段代码中,yield 充当了状态输出点,while 循环让状态无限轮转。虽然简单,但已经体现了 Generator 状态机的核心:状态隐藏在函数的暂停位置中,而不是显式存储在外部变量里。

二、用 yield 接收外部输入实现复杂转移

复杂状态机往往需要根据外部事件决定下一个状态,而不是固定顺序轮转。Generator 的 next(arg) 方法可以接收参数,该参数会成为上一个 yield 表达式的返回值,从而让状态函数读取外部事件并分支处理。

例如订单状态机,包含待支付、已支付、发货、完成、取消等状态,转移依赖用户操作或系统通知。我们可以让每个 yield 返回当前状态,并通过 next 传入触发事件,函数内用 if 或 switch 决定下一步流向。这样状态流转条件集中在函数体内,比分散的回调或事件监听更易于追踪。

2.1 订单状态机实现

以下示例实现了一个支持取消与支付的订单状态机。初始为待支付,收到 pay 事件进入已支付,收到 cancel 进入取消;已支付后收到 ship 进入发货,发货后收到 confirm 进入完成。

function* orderMachine() {
  let state = '待支付';
  while (true) {
    const event = yield state;
    if (state === '待支付') {
      if (event === 'pay') {
        state = '已支付';
      } else if (event === 'cancel') {
        state = '已取消';
        yield state;
        return '结束';
      }
    } else if (state === '已支付') {
      if (event === 'ship') {
        state = '已发货';
      }
    } else if (state === '已发货') {
      if (event === 'confirm') {
        state = '已完成';
        yield state;
        return '结束';
      }
    }
  }
}

const order = orderMachine();
console.log(order.next().value);        // 待支付
console.log(order.next('pay').value);   // 已支付
console.log(order.next('ship').value);  // 已发货
console.log(order.next('confirm').value); // 已完成

在这个例子中,yield state 把当前状态暴露给外部,外部通过 next 传入事件驱动转移。当进入终态时,我们用 return 结束迭代器,后续调用 next 将返回 done 为 true。这种模式比在对象上挂载 onEvent 方法更紧凑,也避免了状态变量被外部误改。

2.2 状态转移表对比

为了更直观看到 Generator 写法的优势,我们把同等逻辑的传统查表法并列比较:

实现方式状态存储转移逻辑位置可读性
查表法外部变量或对象属性独立映射表分支多时较难跟踪
Generator 法函数执行上下文函数体内顺序代码接近自然流程描述

查表法在状态极多、转移规则频繁变动时便于集中配置,但调试时要同时看表和处理器。Generator 法把“状态是什么”和“收到什么事件后去哪”写在一起,新人阅读时更容易建立完整心智模型。

三、在异步场景中的状态机应用

Generator 天生适合配合 Thunk 或 Promise 实现异步流程控制。复杂状态机若某些状态需要等待接口返回,可在 yield 处产出 Promise,由外部驱动器 resolve 后再 next 传回结果,从而写出“看起来同步”的异步状态代码。

比如支付状态机中,已支付后需要调用物流接口获取运单号,再进入发货状态。我们可以 yield 一个返回 Promise 的函数,驱动器 await 完成后把运单号传入 next,状态机继续往下走。这样避免了回调嵌套,也把异步等待点明确标记在状态流中。

3.1 异步等待示例

下面模拟一个带异步调用的状态机,使用一个简单的 run 函数驱动 Promise 类型的 yield 值。

function getTracking() {
  return new Promise(resolve => {
    setTimeout(() => resolve('SF123456'), 500);
  });
}

function* asyncOrder() {
  yield '已支付';
  const tracking = yield getTracking();
  console.log('运单号:', tracking);
  yield '已发货';
}

function run(gen) {
  const it = gen();
  function step(data) {
    const result = it.next(data);
    if (result.done) return;
    if (result.value instanceof Promise) {
      result.value.then(res => step(res));
    } else {
      console.log('状态:', result.value);
      step();
    }
  }
  step();
}

run(asyncOrder);

上述代码中,yield getTracking() 产出 Promise,run 函数检测到后等待其完成,再把结果通过 next 送回赋值给 tracking。整个过程没有嵌套回调,状态顺序一目了然。实际项目中可替换为 co 库或自定义调度器来处理异常与并发。

四、局限与注意事项

Generator 状态机并非银弹。首先,迭代器只能单向推进,无法回到之前的状态,若业务需要回退则要重置迭代器或另写反向逻辑。其次,状态数据保存在闭包局部变量中,外部不能直接读取或修改,必须依靠 yield 和 next 通信,这既是封装优势也是调试门槛。

另外,在浏览器或 Node 的旧运行环境中,需注意 Generator 已被广泛支持,但调试堆栈不如普通函数直观。若状态机需要和可视化工具集成,查表法可能更容易序列化和展示。因此选择方案时应权衡团队熟悉度与维护成本,而非盲目追求写法新颖。

4.1 错误传递

Generator 内部抛出的异常可通过迭代器的 throw 方法在暂停点注入,也可以在 next 外捕获。建议在驱动器里统一 catch,避免状态机静默失败。

function* safeMachine() {
  try {
    yield '运行';
    throw new Error('状态异常');
  } catch (e) {
    yield '异常恢复';
  }
}

const m = safeMachine();
console.log(m.next().value);
console.log(m.next().value); // 触发异常并被捕获
console.log(m.next().value);

通过 try-catch 结合 yield,我们能让状态机在出错时切换到恢复状态,而不是整个迭代器终止。这种结构在长生命周期的设备控制逻辑中尤为实用。

五、总结

使用 Generator 与 yield 实现复杂状态机,本质是把控制流暂停能力转化为状态停留点。它用顺序代码替代分支表,用局部变量替代外部状态存储,在可读性上优势明显,并天然适配异步等待。只要清楚其单向性、闭包封装与调试特点,就能在订单流转、协议解析、游戏 AI 等场景中写出更简洁可靠的状态逻辑。

Generatoryield状态机修改时间:2026-07-31 15:45:42

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。