为了账号安全,请及时绑定邮箱和手机立即绑定

开玩笑地忽略被拒绝的“一劳永逸”承诺

开玩笑地忽略被拒绝的“一劳永逸”承诺

尚方宝剑之说 2023-10-14 11:12:16
我的商店的processAction()函数以“即发即忘”的方式调用私有异步函数,然后进行提取。 processAction()它本身不处理任何错误处理,并且在浏览器中,如果获取失败,外部库将处理所有未捕获的承诺拒绝。因此,如果我模拟我的获取以拒绝,则私有函数(我正在测试的效果)将拒绝。由于我没有对异步函数调用创建的承诺的引用,因此我无法在测试中捕获拒绝,但测试失败,因为存在未处理的拒绝。我怎样才能告诉 jest 接受这种调用私有函数本身的短时间,而不是仅仅触发调用它的操作?动作.tsconst actions = {  doTheThing() {    dispatch({ type: 'DO_THE_THING' });  },};export default actions;商店.tsimport fetch from './fetch';class Store {  isFetching = false;  // ...  processAction({ type, payload }: { type: string, payload: any }) {    switch (type) {      case 'DO_THE_THING':        this.fetchTheThing();        break;    }  }  private async fetchTheThing() {    try {      this.isFetching = true;      const result = await fetch(myUrl);      // ...    } finally {      this.isFetching = false;    }  }}export default new Store();__mocks__/fetch.tslet val: any;interface fetch {  __setVal(value: any): void;}export default async function fetch() {  return val;}fetch.__setVal = function(value: any) {  val = value;};商店.test.tsimport actions from './actions';import store from './store';const fetch = (require('./fetch') as import('./__mocks__/fetch')).default;jest.mock('./fetch');test('it sets/unsets isFetching on failure', async () => {  let rej: () => void;  fetch.__setVal(new Promise((_, reject) => rej = reject));  expect(store.isFetching).toBe(false);  Actions.doTheThing();  await Promise.sleep(); // helper function  expect(store.isFetching).toBe(true);  rej(); // <---- test fails here  await Promise.sleep();  expect(store.isFetching).toBe(false);});
查看完整描述

2 回答

?
ABOUTYOU

TA贡献1812条经验 获得超5个赞

我的函数以“即发即忘”的方式调用私有异步函数,并且不添加任何错误处理。

不要那样做。

外部库处理所有未捕获的承诺拒绝。在生产中,我希望 shell 来处理它,所以我不想在函数本身中处理它。

不要依赖这个外部库。

您应该在函数中使用自己的全局错误处理函数。

在生产中,让该错误处理函数简单地重新抛出异常,以便环境能够捕获该异常,或者更好的是,如果可能的话,直接调用 shell 错误处理函数。

在测试中,您可以模拟自己的全局处理程序,并断言它是使用预期参数调用的。


查看完整回答
反对 回复 2023-10-14
?
qq_花开花谢_0

TA贡献1835条经验 获得超7个赞

processAction是同步的并且不知道 Promise,这会导致悬空的 Promise。悬空承诺永远不应该拒绝,因为这会导致未经处理的拒绝,这是一种例外。根据环境的不同,这可能会导致应用程序崩溃。即使异常是全局处理的,这也不应该成为不处理预期错误的理由。


正确的方法是在fetchTheThing拒绝发生的地方明确抑制拒绝:


  private async fetchTheThing() {

    try {

      ... 

    } catch {} finally {

      this.isFetching = false;

    }

  }

或者在这种情况下,它更像processAction是导致悬而未决的承诺:


this.fetchTheThing().catch(() => {});

否则将调度未处理的拒绝事件。


如果没有的话,可以通过监听事件来测试:


  ...

  let onRej = jest.fn();

  process.once('unhandledRejection', onRej);

  rej();

  await Promise.sleep();

  expect(onRej).toBeCalled();

  expect(store.isFetching).toBe(false);

如果已经有另一个监听器,这将无法按预期工作unhandledRejection,这在良好的 Jest 设置中是可以预期的。如果是这种情况,唯一不会影响其他测试的解决方法是在测试之前重置它们并在测试之后重新添加:


let listeners;


beforeEach(() => {

  listeners = process.rawListeners('unhandledRejection');

  process.removeAllListeners('unhandledRejection');

});


afterEach(() => {

  (typeof listeners === 'function' ? [listeners] : listeners).forEach(listener => {

    process.on('unhandledRejection', listener);

  });

})

不建议这样做,使用时应自行承担风险,因为这表明错误处理存在更深层次的问题,而在正确设计的 JavaScript 应用程序中通常不可接受。


查看完整回答
反对 回复 2023-10-14
  • 2 回答
  • 0 关注
  • 104 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信