skip to Main Content

Let’s say there’s an async function that has an implementation bug, for example:

function resolveAfter2Seconds() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(undefined_variable);
        }, 2000);
    });
}

How can I call this function and handle the ReferenceError it will throw?

I’ve tried using a .catch block as follows:

resolveAfter2Seconds().then(result => console.log(result)).catch(error => console.log(error));

And also a wrapper function like so:

async function safeAsyncCall() {
    try {
        return await resolveAfter2Seconds()
    } catch {
        console.log("Something went wrong")
    }
}

safeAsyncCall().then(result => console.log(result))

Neither of which work (the code still crashes with a ReferenceError).

How can I safely call this function?

3

Answers


  1. The answer to the Original Poster question depends on the environment where the code will be running and if the script is a ESModule or CommonJS.

    In browsers, for example, you can just listen to unhandled errors like this:

    window.addEventListener
    (
       'error', error =>
       {
          // Handle unhandled errors here...
       }
    );
    

    Window: error event
    The error event is fired on a Window object when a resource failed to load or couldn’t be used — for example if a script has an execution error.

    From https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event

    Note that in your code, if running at a browser, will only throw an error in ESModule mode because if in CommonJS mode it tries to retrieve the variable as a property of window, which should return undefined (in most cases).

    In NodeJs, you can like this:

    process.on
    (
       'uncaughtException', error =>
       {
          // Handle unhandled errors here...
       }
    })
    

    Each environment should have it’s own way of doing this…

    EDIT:

    Example for OP specific environment:

    const eventEmmiter = new ( await import('events') ).EventEmitter;
    
    app.get('/some/route', ( request, response ) =>
    {
       // Pass the response to another error handler
       // accessible from all routes.
       const errorHandler = error => trueErrorHandler(error, request, response, errorHandler);
       
       eventEmmiter.on('error', errorHandler);
       
       // Do your route stuff here...
    });
    
    const trueErrorHandler = ( error, request, response, errorHandler ) =>
    {
       // Handle your errors from all routes here?
       
       // Before your return...
       eventEmmiter.removeListener('error', errorHandler);
    }
    

    You can just repeat the pattern at every route… Or you can just manage the exceptions specifically at each route instead of using the trueErrorHandler().

    Login or Signup to reply.
  2. I know that this isn’t what you want to hear, but I don’t think there’s any safe way to call that function. It’s just broken code.

    You can work around it like KLASANGUI suggested in his answer but I personally would rather fix it.

    If this library is installed via npm, i.e in your node_modules, I would edit it from within node_modules and use patch-package or yarn patch. This will generate a patch file from your changes which can then be applied each time npm install is run using something like postinstall.

    As someone who’s dealt with a lot of broken js packages, this is what I would do. I don’t like having to do weird hacks in my own code because of someone else’s broken code.

    Login or Signup to reply.
  3. The best option is to not use the library code – file a bug report about it and write your own 2 second delay function.

    There is horrendously horrible hack you might like to try not withstanding how awful you might think it: define the variable using the name reported in the syntax errors you know about. It may work because if it’s undefined, nobody else has defined it

    1. If you want to catch promise errors every time the delay function is used, define it as a rejected promise:

      const rejectPromise = Promise.reject("resolveAfter2Seconds is broken - do not call");
      rejectPromise.catch(()=>null);
      global[undefined_variable_name_string] = rejectPromise;
      

      Note you would need to provide a catch handler on calls to resolveAfter2Seconds – it won’t generate an uncaught rejection if you forget.

    2. If you want the resolveAfter2Seconds to actually work you could define the variable as undefined:

       global[undefined_variable_name_string] = undefined;
      

    Warning This the worst kind of hack I think I’ve ever contemplated. I hope it does not get used..

    Why might it be needed

    The syntax error occurs in the timer call back function while preparing the argument to pass to resolve (which never gets called). Since timer call backs are called with a clean stack, it is not being monitored by Promise code as a Promise job in the microtask queue, and the error will never turn up in a catch handler. To be exact the promise returned by resolveAfter2Seconds simply remains pending.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search