1

I have a function like so:

const x = y(callback);
x();

It may be called with a synchronous or asynchronous callback:

const a = y(() => 42);
const b = y(async () => Promise.resolve(42));

The function y should take a callback, that can be synchronous or asynchronous. Is it possible to catch a thrown error from either a synchronous or asynchronous callback?

I can't simply wrap the callback in a try/catch:

const y = (callback) => function () {
  try {
    return callback(...arguments);
  }
  catch (callbackError) {
    throw callbackError;
  }
};

because that doesn't catch the errors thrown from promises. I can't just chain a .catch onto the callback as it might not be a promise.

I've read that checking if a function is a promise before calling it may not trivial/may not be possible.

I can't call it to check if it returns a promise before chaining the catch onto it because if it throws, the catch would not have yet been chained onto the call

Is there a way to catch an error from a callback that may or may not be a promise?


Per the comments, I should have specified I don't want the function to return a promise. That is:

const a = y(() => { throw new Error(); })();
console.log(a instanceof Error);

const b = y(async () => { throw new Error(); })();
console.log(b instanceof Error);

const c = y(() => 42)();
console.log(c === 42);

const d = y(async () => Promise.resolve(42))();
console.log(d instanceof Promise);

should all be true. Simply put, I want to return an error or the result

Nick Bull
  • 9,518
  • 6
  • 36
  • 58
  • First suggestion. Maybe treat all of them as promise. Wrap this 'normal' callback in Promise.resolve(blackBoxFunction).catch() on the top, and then catch errors from them – Robert Mar 28 '21 at 14:13
  • @Robert, I should've specified in the question, I don't want the function to return a promise if it throws an error – Nick Bull Mar 28 '21 at 14:24
  • ok. but i think you should consider to unific behavior of your code that treat async and not async callbacks in the same way. I'm not sure that checking and making some conditional when is async is good idea. (maybe in your case is but in general i'm pretty sure that isn't). The delay from microtask queue is the issue usually. – Robert Mar 28 '21 at 14:28
  • @Robert This function will modify the error before returning it as part of a promise library, so unfortunately in this case it may be necessary. I'll keep thinking if that is the case or not - thanks! – Nick Bull Mar 28 '21 at 14:39

4 Answers4

0

An async function also emits a promise, you might consider to also wrap the async inside a promise, which on resolution emits the value

sb39
  • 101
  • 2
  • 9
  • 1
    Unfortunately I've edited the question to clear up the misconception that this should always return a promise – Nick Bull Mar 28 '21 at 14:31
0

Realised that it's not necessary to chain the catch immediately! So the following abomination allows me to determine if it is a promise and handle the error accordingly:

const y = (error, callback) => function () {
  try {
    const returnValue = callback(...arguments);

    if (returnValue instanceof Promise) {
      return returnValue.catch((callbackError) => {
        return callbackError;
      });
    }
    else {
      return returnValue;
    }
  }
  catch (callbackError) {
    return callbackError;
  }
};

If you're wondering why this function is useful, here's an example of what I'm using it for. It modifies the error (chaining it with another error, see VError) before returning it:

const y = (error, callback) => function () {
  try {
    const returnValue = callback(...arguments);

    if (returnValue instanceof Promise) {
      return returnValue.catch((callbackError) => {
        throw VError(error, callbackError);
      });
    }
    else {
      return returnValue;
    }
  }
  catch (callbackError) {
    throw VError(error, callbackError);
  }
};

Note the return of the following as per the question:

const a = y(() => 42)(); // returns '42'
const b = y(async () => Promise.resolve(42))(); // returns 'Promise {42}'
const c = y(() => { throw new Error('Base'); })(); // throws VError
const d = y(async () => { throw new Error('Base'); })(); // throws VError
Nick Bull
  • 9,518
  • 6
  • 36
  • 58
0

You can use async/await notation here. Await will work with both synchronous and asnychronous functions.

Try to do:

async function init() {
  const x = y( async () => await callback() );
  try {
    await x();
  }
  catch(error) {
    /.../
  }
}

and your function can be simplified now to:


const y = (callback) => function (...argument) {
    return callback(...arguments); 
};
grzim
  • 534
  • 3
  • 10
  • Note that this will always return a promise - the function is `async`. Specifically do not want every return value to be a promise – Nick Bull Mar 28 '21 at 15:45
-1

Well you can do something like this by making all your code async... If I get your problem right.

UPD: understand the problem, here is my approach

(async () => {

  // Run fn
  const fn = callback => {
    return new Promise(async (resolve, reject) => {
        const res = await callback().catch(err => reject(err));
        // Resolve
        resolve(res);
    });
  };

  const variousFNs = [
    // Function 1
    {
      fn: () => 2+2
    },
    // Function 2 with error
    {
      fn: () => {
        const x = 1;
        x = 2;
        return x;
      }
    },
    // Promise Function 3
    {
      fn: () => Promise.resolve(8)
    },
    // Promise function 4 with error
    {
      fn: () => new Promise(async (resolve, reject) => reject('some promise error'))
    }
  ];
  
  //
  //
  // Run all your fns
  for (let i = 0; i < variousFNs.length; i++) {
    try {
      const res = await variousFNs[i].fn();
      console.log(res);
    } catch(err) {
      console.log(`Error catched: ${err}`);
    }
  }

})();
tarkh
  • 2,424
  • 1
  • 9
  • 12
  • `fn` always returns a promise, not errors in the case of `c` and `d` as specified in the question – Nick Bull Mar 28 '21 at 15:04
  • ok I understand your problem, check my UPD – tarkh Mar 28 '21 at 15:41
  • Your function, `fn`, still always returns a Promise. Is `fn` the answer you're providing here? – Nick Bull Mar 28 '21 at 15:49
  • Well yes, fn returns promise, but you can pass normal functions or async functions to it and always get results or errors. If you don't wait for promise, you'll get back Promise function instead of result. – tarkh Mar 28 '21 at 16:02