ovg
ovg

Reputation: 1546

Optional catch in javascript promises

The following is valid:

new Promise<void>((resolve, reject) => {
      reject()
    })
    .then(() => {})
    .catch(() => {})

But I might not always care about the error. Is there a way to make the catch optional?

I tried this but it didn't work:

new Promise<void>((resolve, reject?) => {
      if (reject) reject()
    })
    .then(() => {})

Error: Uncaught (in promise): undefined

Upvotes: 6

Views: 5918

Answers (4)

Emi Sanchez
Emi Sanchez

Reputation: 21

I was trying to solve the same issue, and finally come up with the following promise wrapper:

/**
 * wraps a given promise in a new promise with a default onRejected function,
 * that handles the promise rejection if not other onRejected handler is provided.
 *
 * @param   customPromise      Promise to wrap
 * @param   defaultOnRejected  Default onRejected function
 * @returns wrapped promise
 */
export function promiseWithDefaultOnRejected(customPromise: Promise<any>, defaultOnRejected: (_: any) => any): Promise<any> {

  let hasCatch = false;

  function chain(promise: Promise<any>) {
    const newPromise: Promise<any> = new Promise((res, rej) => {
      return promise.then(
        res,
        function(value) {
          if (hasCatch) {
            rej(value);
          } else {
            defaultOnRejected(value);
          }
        },
      );
    });

    const originalThen = newPromise.then;
    
    // Using `defineProperty` to not overwrite `Promise.prototype.then`
    Object.defineProperty(newPromise, 'then', {
      value: function (onfulfilled: any, onrejected: any) {
        const result: Promise<any> = originalThen.call(newPromise, onfulfilled, onrejected);
        if (typeof onrejected === 'function') {
          hasCatch = true;
          return result;
        } else {
          return chain(result);
        }
      }
    });

    return newPromise;
  }

  return chain(customPromise);
}

This function lets you wrap your promises with a defaultOnRejected function that will handle the rejected promise if no other handler is provided. For example:

const dontCare = promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {});

The result promise will never throw an "Unhandled Promise Rejection", and you can use it as follows:

dontCare.then(x=>console.log("never happens")).catch(x=>console.log("happens"));

or

dontCare.then(x=>console.log("never happens"), x=>console.log("happens"));

or simply without onRejected handler:

dontCare.then(x=>console.log("never happens")).then(x=>console.log("also never happens"));

An issue with this util is that it is not working as expected with async/await syntax: you need to propagate and handle the "catch" path as follows:

async () => {
  try {
    await promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {})
      .catch((e) => { throw e; });
  } catch (e) {
    console.log("happens");
  }
}

Upvotes: 2

HMR
HMR

Reputation: 39250

Let me try to describe your situation:

You have a service that gets user information and a function called getUser that uses that service. When the service fails for any reason then getUser does not have a user available. The result of getUser is used quite a lot of times in your code with the following situation(s):

  1. User is available run a function (block of code).
  2. User is not available run a function (block of code).
  3. Run a function with the error/reject of the service.

When using getUser result you may want to run all 3 functions, a combination of 2 of them or only one.

Current getUser returns a Promise and this type does not seem to be suitable for your situation. Mainly because rejecting a promise and not catching it will cause unhandled promise rejection. And because if you want to run code if user is available or not it complicates the functions (they both have to check the result instead of assuming user is or is not available).

Maybe you can try the following code, please be careful making assumptions in the not available block, this could be due to any error. For example: it does not mean the user does not exist because it could be a network error.

In the example getUser is used but can be any function that returns a promise where you assume not available on reject.

const isAvailable = promise => {
  //do not expose NOT_AVAILABLE outside this function
  const NOT_AVAILABLE = {type:"not available"};
  //no uncaught promise rejection errors by VM
  const savePromise = promise.catch(x=>x);
  return {
    available:fn=>
      promise
      .catch(e=>Promise.reject(NOT_AVAILABLE))
      .then(fn)
      .catch(
        e=>
          (e===NOT_AVAILABLE)
            ? undefined//ignore
            : Promise.reject(e)//re throw, error is not from service
      ),
    //call not available with the promise if promise rejects
    notAvailable:fn=>promise.catch(()=>fn(promise)),
    catchError:promise.catch.bind(promise)
  };
}

const service = arg =>
  (arg===1)
    ? Promise.resolve(arg)
    : Promise.reject(arg)

const getUser = arg => isAvailable(service(arg));

var user = getUser(2);
//if service failed available will be skipped
user.available(
  user=>console.log("skipped:",user)
);
//both catch and notAvailable will be called
user.notAvailable(
  arg=>console.log("not available:",arg)
);
user.notAvailable(
  arg=>console.log("still not available:",arg)
);
//not handling catchError does not cause uncaught promise exception
//  but you can inspect the error
// user.catchError(
//   err=>console.log("error is::",err)
// );


var user = getUser(1);
//you can call available on user multiple times
user.available(
  user=>console.log("got user:",user)
);
user.available(
  user=>Promise.resolve(user)
  .then(user=>console.log("still got user:",user))
  .then(x=>Promise.reject("have to catch this one though"))
  .catch(e=>console.log("ok, got error trying to run available block:",e))
);

//showing you can inspect the error
var user = getUser(5);
user.catchError(err=>console.log("error from service:",err));

Upvotes: -1

tiagodws
tiagodws

Reputation: 1395

You could resolve when the error is something you don't care about. If your catch returns anything other than a rejected promise, the error isn't propagated down the chain.

const ignorableError = new Error("I don't care about this error");

const myPromise = new Promise((resolve, reject) => {
      reject(ignorableError);
    })
    .then(() => {})
    .catch(error => {
      if(error == ignorableError) {
        console.log("Error ignored");
        return;
      }

      // Do something else...

    });
    
myPromise.then(() => console.log("Success"))

Upvotes: 1

Bergi
Bergi

Reputation: 664297

Is there a way to make the catch optional?

No. If you are using a promise that might error, you need to handle that (or propagate it to your caller).

Of course if you create the promise yourself, rejecting it is optional, and you can choose to never reject your promises so that you won't need to handle any errors. But if there are errors from promises that you are using, and you want to ignore them, you must do so explicitly. Just write

somePromise.catch(e => void e);
// or             () => { /* ignore */ }
// or             function ignore() {}

Upvotes: 7

Related Questions