使用async await时如何在不确定时间的回调函数中退出?

const a = (): Promise<void> => {
  return new Promise((resolve) => {
    const callback = (mutations: MutationRecord[]) => {
      // 监听按钮的某个属性变化,此处退出函数
      resolve();
    };
    const observer = new MutationObserver(callback);
    observer.observe(buttonEl, {
      attributes: true,
    });

    // 调用一个Promise函数,成功后触发按钮的点击事件,然后监听按钮的属性变化
    p().then(() => {
      buttonEl.click();
    });
  });
};

const b = async (): Promise<void> => {
  const callback = (mutations: MutationRecord[]) => {
    // 监听按钮的某个属性变化,怎么在此处退出函数?
  };
  const observer = new MutationObserver(callback);
  observer.observe(buttonEl, {
    attributes: true,
  });

  // 调用一个Promise函数,成功后触发按钮的点击事件,然后监听按钮的属性变化
  await p();
  buttonEl.click();
};

如上述示例代码,a是常规的Promise写法,现在想要改成b那样的async写法,请问该怎么办?

想要这么改的原因是函数内部的逻辑逐渐复杂,会调用多个Promise函数,常规写法会陷入层层嵌套的窘境,不利于维护。

阅读 1.2k
avatarAI BotBETA

b函数中,你无法直接在MutationObserver的回调函数callback中直接“退出”async函数,因为async函数执行的是基于Promise的异步流程,而MutationObserver的回调是一个事件监听器,它并不直接参与Promise的解析或拒绝。

然而,你可以通过其他方式来实现你的目标,比如使用一个外部标志来控制是否继续观察或者解除观察。以下是一个基于你给出的代码示例的修改版本:

const b = async (): Promise<void> => {
  let isObserved = true; // 外部标志

  const callback = (mutations: MutationRecord[]) => {
    // 监听按钮的某个属性变化,当满足条件时停止观察
    if (/* 判断条件 */) {
      if (isObserved) {
        isObserved = false;
        observer.disconnect(); // 停止观察
      }
      // 注意:这里不能直接return退出async函数,但可以通过其他方式(如Promise的resolve)通知外部
    }
  };

  const observer = new MutationObserver(callback);
  observer.observe(buttonEl, {
    attributes: true,
  });

  // 调用一个Promise函数,成功后触发按钮的点击事件,然后监听按钮的属性变化
  await p();
  buttonEl.click();

  // 如果需要等待属性变化后再继续执行后续代码,则需要额外处理
  // 可以通过另一个Promise来等待,比如使用Promise的race来监听某个信号

  // 注意:下面的代码仅用于演示,实际中你可能需要更复杂的逻辑来管理这个Promise
  // let waitForMutation = new Promise<void>((resolve) => {
  //   // 这里添加逻辑来在某些条件下resolve这个Promise
  // });

  // await Promise.race([waitForMutation, someOtherTimeoutOrSignal]);

  // 由于上面的waitForMutation并没有实际实现,所以这里直接返回
  return;
};

请注意,在上面的代码中,我添加了一个isObserved标志来控制是否继续观察。当满足某个条件时,我们设置isObservedfalse并调用observer.disconnect()来停止观察。然而,这并不意味着async函数b会“退出”——它只是停止了对DOM元素的观察。

如果你想要在属性变化后继续执行async函数中的后续代码,你可能需要创建一个新的Promise(如上面的waitForMutation)并在某个条件下解析它。然后,你可以使用await来等待这个Promise,从而暂停async函数的执行直到属性变化发生。但是,由于MutationObserver的回调是异步触发的,你不能直接在回调中解析这个Promise,你需要通过其他方式(如信号量、事件或其他同步原语)来协调。

3 个回答

你的需求可能需要最新的Promise.withResolvers()接口,目前还是Stage-4阶段。

const b = async (): Promise<void> => {
  const { promise, resolve } = Promise.withResolvers<void>()

  const callback = (mutations: MutationRecord[]) => {
    resolve();
  };
  const observer = new MutationObserver(callback);
  observer.observe(buttonEl, {
    attributes: true,
  });

  // 调用一个Promise函数,成功后触发按钮的点击事件,然后监听按钮的属性变化
  await p();
  buttonEl.click();

  // 下面这句也可以用 return promise,取决于你的后续逻辑
  await promise
};


这个功能才出没多久,考虑兼容旧的浏览器,你可能需要更新core-js
另外,你用了TS,如果需要通过类型检查,可能还要把TS更新到5.4或以上,然后在tsconfig.json里确保配置libESNext这一项。

只把observer相关逻辑用Promise包起来?

const b = async (): Promise<void> => {
  const promise = new Promise(resolve => {
    const callback = (mutations: MutationRecord[]) => {
      // 监听按钮的某个属性变化,怎么在此处退出函数?
      resolve();
    };
    const observer = new MutationObserver(callback);
    observer.observe(buttonEl, {
      attributes: true,
    });
  })

  // 调用一个Promise函数,成功后触发按钮的点击事件,然后监听按钮的属性变化
  await p();
  buttonEl.click();
  return promise;
};

退出 Promise 的做法万变不离其宗,那就是:改变 Promise 实例的状态。成功就 resolve,失败就 reject。

最简单的做法就是保留函数的引用。然后必要的时候调用它。

let resolve;
let reject
const p = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});
const callback = () => {
  resolve();
}
// 做你需要的其它事情
// .... 省略代码若干
// 等待 promise
await p;

你也可以在 await 那里适当加个 Promise.race 作为超时保护。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏