2

How do I catch an error that gets thrown asynchronously by Reflect.get(target, name, receiver) within the Proxy handler?

I'm using Proxy and Reflect to wrap an API class, so that I can catch any Network exceptions and gracefully display error messages to the user.

//Wrapper.js

let handler = {
  get: (target, name, receiver) => {
    try {
      Reflect.get(target, name, receiver); //throws Error asynchronously
    } catch (e) {
      console.log('caught error', e);
    }
};

export default new Proxy(new API(), handler);
//App.js

import Wrapper from './Wrapper';

Wrapper.asyncFunction(); //throws uncaught Error
//API.js

class API {
  get user() {
    return new User()
  }
}
//User.js

class User {

  /**
   * List all users
   */
  all() {
    return new Promise((resolve, reject) => {
      reject(new Error('Network error'));
    });
  }
}

When an error is thrown, inside Reflect.get() "caught error" never gets printed and remains uncaught.

Mastergalen
  • 4,289
  • 3
  • 31
  • 35
  • "Thrown asynchronously" meaning within a Promise? You could attach a `catch` handler to the return value of `Reflect.get(...)`. – sdgluck Aug 26 '16 at 11:11
  • You cannot "throw asynchronously". All you can do is "return a promise that will get rejected". – Bergi Aug 26 '16 at 13:45

1 Answers1

5

You could wrap the call to the return value of Reflect.get(...) (the actual API method) in a new Promise, catch any errors, log them, and then pass them on again. Similarly if there are no errors simply resolve the outer Promise.

let handler = {
  get: (target, name, receiver) => {
    return (...args) => new Promise((resolve, reject) => {
      const apiMethod = Reflect.get(target, name, receiver);
      const boundApiMethod = apiMethod.bind(target);

      boundApiMethod(...args).then(resolve, (e) => {
        console.log('caught error', e);
        reject(e);
      });
    });
  }
};

Your current code is not catching the error because asynchronous functions are not executed in-place, so the error is thrown outside of the try block.

Edit: an alternative approach that doesn't use a Promise constructor:

let handler = {
  get: (target, name, receiver) => {
    return (...args) => {
      return Reflect
        .get(target, name, receiver)
        .apply(target, args)
        .catch((e) => { console.log('caught error', e) });
    };
  }
};
sdgluck
  • 24,894
  • 8
  • 75
  • 90
  • Thanks for your reply, I should have mentioned that Reflect.get() also can return a function that is not a Promise. An error is then thrown where .catch() is undefined. – Mastergalen Aug 26 '16 at 11:25
  • Under what circumstances would it not return a Promise? Please add the code for `Wrapper.asyncFunction` to your Q. I suggest that you amend the API to always return a Promise, otherwise you cannot reliably catch the error. – sdgluck Aug 26 '16 at 11:27
  • Actually, even if **Wrapper** has only functions that return promises, adding a .catch() to Reflect still throws the error that .catch is undefined. See https://jsfiddle.net/geh2k153/3/ – Mastergalen Aug 26 '16 at 12:33
  • That's because you aren't invoking the function returned from `Reflect.get`. :-) – sdgluck Aug 26 '16 at 12:51
  • [How about this?](http://jsbin.com/dipuwesaye/1/edit?html,js,console) Catches the error internally, logs it, then passes the result on via a new Promise. – sdgluck Aug 26 '16 at 12:55
  • Ah, that makes sense - Your example works on jsbin, however, when I put it into my own code, the function that gets called by Reflect.get() inside API.js now does not have `this` defined? – Mastergalen Aug 26 '16 at 14:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/121947/discussion-between-sdgluck-and-mastergalen). – sdgluck Aug 26 '16 at 15:00
  • Unfortunately, the Reflect does not seem to work when trying to call a function of a class instance in the property of the API class. E.g. calling Wrapper.user.all() throws the error that it's not a function, even though if I console.log the wrapper right before calling it, it's showing that the function is defined in the `[[Target]]` property of the Proxy instance – Mastergalen Aug 28 '16 at 21:39
  • Avoid the [`Promise` constructor antipattern](http://stackoverflow.com/q/23803743/1048572)! – Bergi Aug 28 '16 at 22:00
  • Thanks @Bergi, I've added an alternative. :-) – sdgluck Aug 29 '16 at 01:24
  • 1
    really good reply, i was able to use your second code block to chain an `expect(code)` method to the `axios` package to mimic `supertest`. thanks!! – heisian Jul 18 '17 at 20:05