3

Example

class Foo {
    private pro = new Promise(() => {
                      throw new Error();
                  });

    public usePro() {
        return this.pro.then(() => {});
    }
}

let foo = new Foo();
setTimeout(() => {
    foo.usePro().then(() => {
        console.log("end.");
    }).catch(() => {
        console.log("error.");
    })
}, 1000);

I understand that javascript can't know at runtime that someone will catch the error later, so how am I suppose to do in such a situation ?

Console

(node:39166) UnhandledPromiseRejectionWarning: error
(node:39166) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:39166) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
error.
(node:39166) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
wlh
  • 3,426
  • 1
  • 16
  • 32
Neok
  • 680
  • 1
  • 9
  • 22
  • How can you ensure that someone will catch the error later? Also, [you shouldn't do async stuff in your constructor](https://stackoverflow.com/a/24686979/1048572) – Bergi Aug 26 '18 at 20:44

3 Answers3

1

Errors should be caught wherever the Promise is used, even if that Promise is returned (and caught) by something else later. One option would be to assign to this.proValue a resolved object or a rejected object, depending on whether the original Promise resolves or rejects. Then, when usePro is called, check this.proValue and return either Promise.resolve(resolved) or Promise.reject(rejected). Using standard Javascript so this can be shown in a runnable snippet:

class Foo {
  constructor() {
    this.pro = new Promise(() => {
      throw new Error('Problem!');
    })
    .then((resolved) => {
      this.proValue = { resolved };
    })
    .catch((rejected) => {
      this.proValue = { rejected };
    });
  }

  usePro() {
    const { resolved, rejected } = this.proValue;
    if (resolved) return Promise.resolve(resolved);
    else if (rejected) return Promise.reject(rejected);
  }
}

const foo = new Foo();
setTimeout(() => {
  foo.usePro().then(() => {
    console.log("end.");
  }).catch((e) => {
    console.log("error caught. " + e);
  })
}, 1000);

If you want to be able to call usePro before Foo's internal Promise has resolved (or rejected), then when usePro is called, construct and return a Promise that resolves once this.pro's Promise resolves (or rejects). unfortunately the code required is moderately more complicated:

class Foo {
  constructor() {
    this.callProms = [];
    setTimeout(() => {
      this.pro = new Promise(() => {
        throw new Error('Problem!');
      })
      .then((resolved) => {
        this.proValue = { resolved };
      })
      .catch((rejected) => {
        this.proValue = { rejected };
      })
      .finally(() => {
        console.log('internal promise finishing');
        this.resolveCalls();
      });
    }, 1000);
  }
  resolveCalls() {
    this.callProms.forEach((resolve) => {
      resolve(this.getProValue());
    });
  }
  getProValue() {
    const { proValue } = this;
    if (!proValue) return;
    const { resolved, rejected } = proValue;
    if (resolved) return Promise.resolve(resolved);
    else if (rejected) return Promise.reject(rejected);
  }
  usePro() {
    return this.getProValue()
    || new Promise((resolve) => {
      this.callProms.push(resolve);
    });
  }
}

console.log('Starting');
const foo = new Foo();

// Immediate call of `usePro`:
foo.usePro().then(() => {
  console.log("end.");
}).catch((e) => {
  console.log("immediate error caught. " + e);
})

// Delayed call:
setTimeout(() => {
  foo.usePro().then(() => {
    console.log("end.");
  }).catch((e) => {
    console.log("delayed error caught. " + e);
  })
}, 2000);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • this won't work if `usePro()` gets called before the promise resolved. – Jonas Wilms Aug 26 '18 at 19:54
  • You're right, see edit. Kind of ugly, I can't think of a way around manually constructing a new Promise on each early call of `usePro` – CertainPerformance Aug 26 '18 at 20:39
  • 1
    This is horribly complicated. Why not just store the `proValue` in a promise itself - a promise that never is rejected? You basically are re-inventing [`reflect`](https://stackoverflow.com/a/31424853/1048572). – Bergi Aug 26 '18 at 20:47
1

Great answer by CertainPerformance.

Let me add that in Node.js, you can also add an unhandledRejection listener on process:

process.on('unhandledRejection', reason => {
    console.error({Error:reason})
    process.exit(1);
});
wlh
  • 3,426
  • 1
  • 16
  • 32
  • Don't you think it's dangerous to ignore all warnings, which could come from somewhere else ? – Neok Aug 26 '18 at 20:11
  • I'm not saying to ignore all warnings, but to listen for `unhandledRejections`, which may come from unknown sources. Once you catch them, of course you should start refactoring your code. I'm just adding the above answer as good practice for error handling, especially in the development process. – wlh Aug 26 '18 at 20:13
0

You could use Promise.all to delay the resolving:

  const delay = ms => new Promise(res => setTimeout(res, ms));

  Promise.all([
      foo.usePro(),
      delay(1000)
  ]).then(() => { 
     console.log("end.");
  }).catch(() => { 
     console.log("error."); 
  });

That way the .catch is directly attached, but the then callback is executed after the delay.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151