2

I have a question about handling exceptions/errors. Consider this implementation using a try/catch. This is certainly one option, but I've also heard try/catch can be a little heavy-handed.

Option 1:

async function updateCustomerRegion(params, auth) {
    const { id, region } = params;

    const Customer = await CustomerModel();
    const filter = { _id: id };
    const update = { region: region };

    try {
      const customer = await Customer.findOneAndUpdate(filter, update, { new: true })
        .lean()
        .exec();
    } catch (error) {
      console.log(error);
      throw "Error: Unable to update customer";
    }

    return {
       count: customer ? 1 : 0,
       data: customer
    };
}

Another option is this implementation where I just throw an error if "customer" is false:

Option 2:

async function updateCustomerRegion(params, auth) {
  const { id, region } = params;

  const Customer = await CustomerModel();
  const filter = { _id: id };
  const update = { region: region };

  const customer = await Customer.findOneAndUpdate(filter, update, { new: true })
      .lean()
      .exec();

  if (!customer) throw "Error: Unable to update customer";

  return {
    count: customer ? 1 : 0,
    data: customer
  };
};

My question is, how functionally equivalent are these two implementations? Will Option #1 handle more than Option #2, or is using try/catch a little heavy-handed in this instance?

2
  • Well, they're not functionally equivalent at all, which one works depends on whether findOneAndUpdate does throw an exception or not. What does its documentation say? Commented Jul 26, 2019 at 14:37
  • The second approach does not provide any details about the error as the first one does, and it doesn't work for functions that normally return falsy values. Commented Jul 26, 2019 at 14:39

2 Answers 2

2

Option 1 As far as I know, it's best practice to wrap async/await with try/catch, anywhere if anything goes wrong then exceptions/errors will be handled in catch block i.e; you can throw custom exceptions in catch block along with any generic exceptions.

In your Option 2, I don't think code would reach if (!customer) throw "Error: Unable to update customer"; if there is an exception/error it would return from above line and error out from there with a non-user friendly exception in response - wouldn't go to caller and return a response(either expected or user friendly error message). if(!customer) is just like your function executed well & you're checking on the return object is not valid - if empty do return no customer found or something it's not equal to catching errors. So to be precise it's more like functionality check rather than exception handling. Ex :-

Combining both for better implementation :

    async function updateCustomerRegion(params, auth) {
    const { id, region } = params;

    /* here you are basically validating region is string or not and returning from here, with out any further exception (just a basic example)*/
    if (!region && region !== typeof ('')) throw "Error: invalid region";

    const Customer = await CustomerModel();
    const filter = { _id: id };
    const update = { region: region };

    try {
        const customer = await Customer.findOneAndUpdate(filter, update, { new: true })
            .lean()
            .exec();
        /* Here as well a validation check not to execute further assuming your DB call will respond with empty object*/
        if (Object.entries(customer).length !== 0 && customer.constructor === Object) throw "Error: Unable to update customer - no customer found";
        /* or */
        if (Object.entries(customer).length !== 0 && customer.constructor === Object) return { count: 0, data: [] };

    } catch (error) {
        console.log(error);
        /* Basically this is just an example to show you can check something here & do custom exceptions */
        if (error.stack === 'operation exceeded time limit') {
            throw "Error: Unable to update due to timeout or someother";
        } else {
            throw "Error: Unable to update";
        }
    }
    /* more logic to add more fields and return */
    return {
        count: 1,
        updatedBy: 'name',
        reason: 'just updated like that',
        data: customer
    };
}
Sign up to request clarification or add additional context in comments.

2 Comments

This LINE ( it's best practice to wrap async/await with try/catch ) is enough. Thanks for posting your answer. I was confused, should I only catch error in parent function or also define try catch in child function where I am doing async await, queries from database etc ? Thanks @whoami
@UsmanI: that's your choice when programming, Check this: stackoverflow.com/questions/50705308/… , If you wanted to change/log/control errors at child function you can use try/catch in every child func to make your code look clean or to easy debug code with 100s of functions during an error!
1

They are not functionally equivalent.

Implementation #1 will throw an error if findOneAndUpdate is actually throwing an error itself, regardless of its return value (it's not returning a value at all in this case). Here you are handling exceptions that have been thrown in findOneAndUpdate or that have bubbled from a dependency of it.

Implementation #2 will throw an error if findOneAndUpdate doesn't throw an error but returns a falsy value. Here you are deciding that the return value is unexpected, and you cannot proceed with the method execution, hence throwing an exception. In this case, if findOneAndUpdate throws an exception for whatever reason, you are not handling it and it will bubble up to updateCustomerRegion callee, possibly crashing your application.

As a rule of thumb, I would suggest to try/catch all your async calls whenever it makes sense (e.g. performing API calls, queries, etc...). You can also combine the two, handling the exception and throwing on falsy returns might make complete sense in your use case.

Also, I would advise against throwing raw strings but to always rely on an actual instance of an Error class (either typescript stock errors or a custom one).

5 Comments

Good points. What would an example of a "throw" with actual error info in it look like?
I personally like to rely on specific exception types to propagate information about which type of error I got, for example: throw new MetadataNotFoundException(); Already let the catcher know anything he needs about the error. You would want to, of course, get the error instance type to handle different cases, for example: if (error instanceof MetadataNotFoundException) {
If I just did catch(error) throw error; what would that give me?
What you did is called rethrowing, which is just throwing again the same error you received. It might be useful in cases where you want to perform something on exceptions (usually logging of some sort) but you don't actually know how to properly handle or correct the issue, so you let it bubble outwards.
If the only instruction in your catch block is the rethrow (like you did), then there is no purpose in catching the exception at all.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.