问题根源与解决方案
原因分析
-
单实例链式结构:
-
openAny
是全局单例,所有链式操作(包括invoke
内部)都通过_chain
顺序执行。 - 当在
invoke
内调用await openAny.click()
时,主链(_chain
)会等待自身完成,形成 循环依赖,导致后续操作无法执行。
-
-
死锁机制:
openAny.invoke(async () => { await openAny.click('button'); // ❌ 内部等待主链完成 });
- 主链执行到
invoke
时,会等待回调函数完成。 - 回调函数内部又等待主链的
click
操作完成,导致 相互等待,链式流程卡死。
- 主链执行到
解决方案
1. 使用 new OpenAny()
创建新实例
- 独立链式环境:新实例拥有自己的
_chain
,避免与主链冲突。
openAny.invoke(async () => {
// ✅ 正确:使用新实例操作,不阻塞主链
await new OpenAny()
.click('[data-key="dialog-searchtype"] .b3-dialog__action .b3-button--text')
.focus("#searchInput");
});
2. 通过 resetChain()
恢复主链
- 适用场景:主链已死锁,需要强制重置。
openAny.invoke(() => {
// ❌ 错误操作导致主链卡死
openAny.click('button'); // 假设此处因异步问题卡死
});
// 后续操作前重置链
openAny.resetChain().click('restart-button'); // ✅ 恢复主链
代码示例与对比
错误示例(死锁)
openAny
.click('#step1')
.invoke(async () => {
// ❌ 错误:内部 await 主链操作,导致死锁
await openAny.click('#step2');
})
.click('#step3'); // 永远不会执行
正确示例(新实例隔离)
openAny
.click('#step1')
.invoke(async () => {
// ✅ 正确:使用新实例操作
await new OpenAny()
.click('#step2')
.press('Enter');
})
.click('#step3'); // 正常执行
设计总结
场景 | 正确做法 | 错误做法 | 结果 |
---|---|---|---|
invoke 内独立操作 |
new OpenAny().click() |
await openAny.click() |
✅ 避免死锁 |
主链恢复 | openAny.resetChain() |
无处理 | ✅ 恢复可用性 |
并行操作 | Promise.all([new OpenAny(), ...]) |
直接操作主链 | ✅ 隔离风险 |
最佳实践
-
invoke
内操作原则:- 需要等待异步操作时,优先使用参数工具函数(如
sleep
)。 - 如需调用
openAny
方法,必须使用新实例new OpenAny()
。
- 需要等待异步操作时,优先使用参数工具函数(如
-
错误恢复:
try { await openAny.invoke(() => { /* 可能出错的操作 */ }); } catch (e) { openAny.resetChain(); // 强制重置链 openAny.click('#recover-button'); // 恢复流程 }
-
复杂流程封装:
const searchFlow = () => new OpenAny() .click('#search') .input('keyword', '#input') .press('Enter'); // 主链调用 openAny .invoke(() => searchFlow()) // ✅ 安全调用 .click('#next-step');
通过 隔离实例 或 主动重置链,可彻底避免因链式依赖导致的死锁问题。
