openAny invoke 内部 await 造成的死锁与分析

问题根源与解决方案

原因分析

  1. 单实例链式结构

    • openAny​ 是全局单例,所有链式操作(包括 invoke​ 内部)都通过 _chain​ 顺序执行。
    • 当在 invoke​ 内调用 await openAny.click()​ 时,主链(_chain​)会等待自身完成,形成 循环依赖,导致后续操作无法执行。
  2. 死锁机制

    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(), ...]) 直接操作主链 ✅ 隔离风险

最佳实践

  1. invoke内操作原则

    • 需要等待异步操作时,优先使用参数工具函数(如 sleep​)。
    • 如需调用 openAny​ 方法,必须使用新实例 new OpenAny()​。
  2. 错误恢复

    try {
      await openAny.invoke(() => { /* 可能出错的操作 */ });
    } catch (e) {
      openAny.resetChain(); // 强制重置链
      openAny.click('#recover-button'); // 恢复流程
    }
    
  3. 复杂流程封装

    const searchFlow = () => new OpenAny()
      .click('#search')
      .input('keyword', '#input')
      .press('Enter');
    
    // 主链调用
    openAny
      .invoke(() => searchFlow()) // ✅ 安全调用
      .click('#next-step');
    

通过 隔离实例主动重置链,可彻底避免因链式依赖导致的死锁问题。

image.png

留下你的脚步
推荐阅读