You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript实现状态模式反向流转时抛出构造函数不存在TypeError

状态模式下订单反向流转报错TypeError: EstadoRecibido is not a constructor排查与修复

问题背景

开发应用多设计模式的线上门店管理平台时,销售订单(Venta)需按如下规则流转状态:

  • 初始默认进入*已接收(EstadoRecibido / received state)*状态
  • 已接收状态可流转至*已受理(EstadoAceptado / accepted state)*状态
  • 已受理状态可流转至*已配送(EstadoEntregado / delivered state)*状态
  • 额外支持从已受理状态反向回退至已接收状态
    实际执行反向回退操作时抛出固定错误:TypeError: EstadoRecibido is not a constructor。其中Venta.js是状态模式的上下文(Context)类,内部持有当前订单状态的引用。

关联代码

1. 状态基类 Estado.js

class Estado { 
  recibirVenta () {
    return new Error('subclass responsibility')
  }

  aceptarVenta () {
    return new Error('subclass responsibility')
  }

  cancelarVenta () {
    return new Error('subclass responsibility')
  }

  entregarVenta () {
    return new Error('subclass responsibility')
  }
}

module.exports = Estado

2. 已接收状态类 EstadoRecibido.js

const Estado = require('./estado')
const EstadoAceptado = require('./estadoAceptado')

class EstadoRecibido extends Estado {
  constructor () {
    super()
    this.nombre = 'Recibido'
  }

  aceptarVenta (aVenta) {
    aVenta.setEstadoEnvio(new EstadoAceptado())
  }
}

module.exports = EstadoRecibido

3. 已受理状态类 EstadoAceptado.js

const Estado = require('./estado')
const EstadoEntregado = require('./estadoEntregado')
const EstadoRecibido = require('./estadoRecibido')

class EstadoAceptado extends Estado {
  constructor () {
    super()
    this.nombre = 'Aceptada'
  }
  
  entregarVenta (aVenta) {
    aVenta.setEstadoEnvio(new EstadoEntregado())
  }

  recibirVenta (aVenta) {
    aVenta.setEstadoEnvio(new EstadoRecibido())
  }
}

module.exports = EstadoAceptado

4. 订单上下文类 Venta.js

class Venta {
  constructor (cliente, items, tipoEnvio) {
    // 省略其余属性初始化逻辑
    this.estadoEnvio = new EstadoRecibido()
  }

  aceptarVenta () {
    this.estadoEnvio.aceptarVenta(this)
  }

  recibirVenta () {
    this.estadoEnvio.recibirVenta(this)
  }

  entregarVenta () {
    this.estadoEnvio.entregarVenta(this)
  }

  setEstadoEnvio (aEstado) {
    this.estadoEnvio = aEstado
  } 

  getEstadoEnvio () {
    return this.estadoEnvio
  }
}
module.exports = Venta

错误复现流程

执行以下代码可稳定触发报错:

const venta = new Venta()
// 实例化完成后,venta初始状态为已接收
venta.aceptarVenta()
// 执行后状态正常切换为已受理
venta.recibirVenta()
// 执行回退操作时报错:TypeError: EstadoRecibido is not a constructor

从已受理正向流转到已配送状态无任何异常,仅反向回退时触发错误。

根因分析

问题确实由CommonJS模块的循环依赖加载机制导致,加载链路如下:

  1. 首次加载EstadoRecibido.js时,Node会先为该模块创建空的module.exports缓存,再开始执行模块内代码
  2. 代码执行到顶部const EstadoAceptado = require('./estadoAceptado')时,暂停当前模块执行,转而去加载EstadoAceptado.js
  3. 加载EstadoAceptado.js时,执行到顶部const EstadoRecibido = require('./estadoRecibido'),此时Node发现EstadoRecibido模块已经在加载队列(缓存)中,但还没执行完成(仍停留在等待EstadoAceptado加载的阶段),因此直接返回当前缓存里的空导出对象给EstadoAceptado模块,而不是最终定义好的构造函数
  4. 等EstadoAceptado加载完成回到EstadoRecibido模块继续执行,后续才会把EstadoRecibido类赋值给module.exports,但此时EstadoAceptado里拿到的EstadoRecibido引用还是之前的空对象,调用new EstadoRecibido()时自然会抛出"不是构造函数"的错误
    正向流转到EstadoEntregado不报错,是因为EstadoEntregado和其余两个状态类没有形成循环依赖链路,加载时可以拿到完整的构造函数引用。

修复方案

方案1:延迟加载依赖(改动最小)

去掉模块顶部的互相依赖引入,改为在实际调用状态流转方法时再require对应状态类,打破模块加载阶段的循环引用:

  1. 修改EstadoRecibido.js:
const Estado = require('./estado')

class EstadoRecibido extends Estado {
  constructor () {
    super()
    this.nombre = 'Recibido'
  }

  aceptarVenta (aVenta) {
    const EstadoAceptado = require('./estadoAceptado')
    aVenta.setEstadoEnvio(new EstadoAceptado())
  }
}

module.exports = EstadoRecibido
  1. 修改EstadoAceptado.js:
const Estado = require('./estado')

class EstadoAceptado extends Estado {
  constructor () {
    super()
    this.nombre = 'Aceptada'
  }
  
  entregarVenta (aVenta) {
    const EstadoEntregado = require('./estadoEntregado')
    aVenta.setEstadoEnvio(new EstadoEntregado())
  }

  recibirVenta (aVenta) {
    const EstadoRecibido = require('./estadoRecibido')
    aVenta.setEstadoEnvio(new EstadoRecibido())
  }
}

module.exports = EstadoAceptado

这种方式下,所有模块都完成初始化加载后,才会在实际触发状态流转时执行require,此时拿到的都是已经完成导出的有效构造函数,不会出现空对象问题。

方案2:统一状态入口(适合状态较多的场景)

新建状态索引文件统一加载、导出所有状态类,避免状态模块之间两两直接引用,从根源消除循环依赖。

内容的提问来源于stack exchange,提问作者Mauro Alvarez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 21:09:18