思路:状态标记 + Promise 阻塞 + 事件触发" 的逻辑闭环
1.开启
单步模式的开启设置 isStepMode = true
2.初始化时开启:创建实例时传入参数
构造函数会接收 isStepMode
参数,存储为实例属性,并通过 eventBus
通知外部组件(如节点 UI)开启单步模式:
kotlin
constructor(data, isStepMode = false) {
this.isStepMode = isStepMode; // 1. 存储单步模式状态到实例
this._waitResolve = null; // 2. 初始化 Promise resolve 句柄(用于控制暂停/继续)
eventBus.emit('update:runType', isStepMode); // 3. 通知外部组件(如BaseCustomNode)切换单步模式UI
this._initNodes(data);
this._initEvent(); // 4. 初始化事件监听(关键:监听"继续"事件)
}
3. 暂停逻辑核心
_createWaitPromise
方法(第 127 行)是单步暂停的核心,它通过返回一个 未 resolve 的 Promise 阻塞代码执行,直到外部调用 resolve
才继续:
javascript
// 创建暂停Promise(单步模式下阻断递归)
_createWaitPromise(){
return new Promise(resolve => {
// 关键:将 Promise 的 resolve 方法保存到实例的 _waitResolve 属性
// 此时 Promise 处于 pending 状态,代码执行会卡在这个Promise处
this._waitResolve = resolve
})
}
- 原理 :Promise 未调用
resolve
时,后续await
该 Promise 的代码会一直阻塞(类似 "断点暂停")。 - 触发时机:在递归执行节点时,判断单步模式开启则调用该方法,实现 "每个节点执行前暂停"。
4.循环暂停 / 继续:递归中的节点执行闭环
递归逻辑,每个节点执行前都会检查单步模式,满足条件则暂停,直到用户触发 "继续"。
节点执行前暂停:判断单步模式并调用 _createWaitPromise
执行每个节点前会先检查是否为 "非开始 / 结束节点" 且单步模式开启,满足则触发暂停:
kotlin
// 非开始/结束节点 + 单步模式开启:触发暂停
if (!isSkipTruncate) {
// 1. 通知外部组件
eventBus.emit('logic:step:status', { nodeId, status: 'ready', nodeLabel });
if (this.isStepMode) {
// 2. 调用 _createWaitPromise,返回未resolve的Promise,阻塞后续代码执行
await this._createWaitPromise();
}
}
csharp
// 暂停结束后,才执行节点(发送"执行中"状态 + 调用 node.Start())
eventBus.emit('logic:step:status', { nodeId, status: 'running', nodeLabel });
let nextNode = await node.Start(); // 执行当前节点
csharp
// 递归执行下一个节点,重复"暂停-执行"循环
await this._recursiveRun(nextNode, node.nodeIn)
执行完当前节点后,递归调用方法处理下一个节点,再次触发 "暂停检查",实现 "每个节点执行前暂停" 的循环。
5."继续" 触发:外部组件通过 eventBus
调用 resolve
暂停后的 "继续" 是通过外部组件的 "运行" 按钮发送事件, 监听事件后调用 _waitResolve
(即 _createWaitPromise
中保存的 resolve
方法),让阻塞的 Promise 完成。
监听 "继续" 事件:_initEvent
方法
在 _initEvent
方法中,监听 logic:step:continue
事件,收到事件后调用 this._waitResolve()
解除暂停:
javascript
_initEvent(){
// 监听单步继续事件运行按钮触发
eventBus.on('logic:step:continue', () => {
if (this._waitResolve) {
this._waitResolve(); // 1. 调用保存的 resolve 方法,解除 Promise 阻塞
this._waitResolve = null; // 2. 重置 _waitResolve,避免重复调用
}
})
}
外部组件触发 "继续":BaseCustomNode 的 "运行" 按钮
在 外部组件 中,当用户点击 "运行" 按钮时,发送 logic:step:continue
事件,触发继续逻辑
运行按钮
ini
const handleRunNode = () => {
if (stepState.value.status !== 'ready') return;
// 发送"继续"事件,通知 LogicManager 解除暂停
eventBus.emit('logic:step:continue');
// 更新节点状态为"执行中"
stepState.value.status = 'running';
}
- 第一个节点暂停 : 执行第一个非开始 / 结束节点前,调用
_createWaitPromise
阻塞,同时通知 UI 显示 "运行" 按钮。 - 用户触发继续 :用户点击 "运行" 按钮,发送
logic:step:continue
事件, 调用_waitResolve
解除阻塞,执行当前节点。 - 递归下一个节点 :当前节点执行完后,
_recursiveRun
递归处理下一个节点,重复 "暂停 - 继续" 循环,直到流程结束。
javascript
// 1. 执行节点A,触发暂停
async _recursiveRun(节点A, ...) {
if (this.isStepMode) {
// 调用 _createWaitPromise,_waitResolve 被赋值为 节点A的resolve
await this._createWaitPromise();
// 此时 _waitResolve = 节点A的resolve(非null,处于暂停)
}
}
// 2. 用户点击"继续",触发事件
eventBus.on('logic:step:continue', () => {
if (this._waitResolve) {
this._waitResolve(); // 调用节点A的resolve,释放暂停
this._waitResolve = null; // 重置为null,节点A的释放完成
}
});
注意:
只在 _initEvent
中注册一次,但能实现多次触发响应 ,核心原因是 事件监听的 "注册一次,永久生效" 特性 :一旦注册,事件总线会持续监听该事件,每次外部触发 logic:step:continue
时,都会执行回调函数,与注册次数无关。
eventBus
是一个基于 "订阅 - 发布" 模式的事件系统,eventBus.on('logic:step:continue', 回调)
表示 "订阅 logic:step:continue
事件"。
- 订阅操作(
on
)只需执行一次,即可长期生效:事件总线会将回调函数存入内部的事件列表中。 - 后续每次通过
eventBus.emit('logic:step:continue')
发布事件时,事件总线都会从列表中找到对应的回调函数并执行。
kotlin
() => {
if (this._waitResolve) {
this._waitResolve() // 释放当前暂停
this._waitResolve = null // 重置
}
}
每次触发时,它会检查当前实例的 _waitResolve
状态:
- 若处于暂停状态(
_waitResolve
有值),则执行释放逻辑; - 释放后立即重置
_waitResolve
为null
,不影响下一次暂停。这种 "基于实例当前状态动态执行" 的特性,让单次注册的回调能适配多次暂停 - 继续的循环。
只要实例存在,eventBus
中注册的监听就不会失效。在单步调试流程中:
- 第一个节点暂停时,触发
emit
,回调执行释放; - 第二个节点暂停时,再次触发
emit
,回调再次执行释放; - 直到流程结束,实例被销毁前,监听始终有效。