Can't pass down errors from server

Hello!

I’m trying to pass display a server service error to a client alert.

What I CAN DO:

have an error thrown from the method displayed to the client

What I CANT:

pass the error from the service to the method, the callback is not getting called

here the code

services = {
  myService() {
    ... 
    try {
      ...
      return result
    } catch {
      ...
      return error
    }
  }
}
Meteor.methods({
  "myMethod": () => {
    Meteor.wrapAsync(services.myService((res, err) => {
      console.log(err)
      throw new Meteor.Error(err)
    }))
  }
})

Thanks for the help!

maybe this will help: Throw error (Async/Await) in Meteor Method!

Thanks for the answer, but doesn’t seem to work.

Actually, if in the method I remove wrapAsync and the service callback, I actually catch the error, but not the message.

Can you provide more complete code?

Part of the issue here is how you’re using wrapAsync. You need to pass the function reference in as an argument. What you’re doing is calling the function and passing the returned value to wrapAsync

Also, from your code, myService doesn’t use a callback, so the code in the callback will never be run, so it will never throw and error; and without a callback, wrapAsync doesn’t do anything

What you likely want is this:

Meteor.methods({
  "myMethod": () => {
    try {
      return services.myService();
    } catch (error) {
      console.log(err)
      throw new Meteor.Error(err)
    }
  }
})

Or if your service does use a callback, this is how you use wrapAsync:

Meteor.methods({
  "myMethod": () => {
    const wrappedService = Meteor.wrapAsync(services.myService, services);
    // the second argument is only needed if myService uses `this`
    try {
      return wrappedService();
    } catch (error) {
      console.log(err)
      throw new Meteor.Error(err)
    }
  }
})

thank you so much for the answers but I don’t manage to make it work :frowning:
@copleykj here the complete code:

SERVICE

sendGridServices = {
  async sendEmail(data, draftId, userId) {
    sgMail.setApiKey(API_KEY);
    messageId = randomMessageId();
    data = {
      ...data,
      from: "",
      headers: { "Message-Id": messageId, ...data.headers },
      date: Date(),
    };
    try {
      await sgMail.send(data);
      console.log("email sent");
      storeEmail({ data }, userId), deleteDraft(draftId);
    } catch (error) {
      if (error.response) {
        console.error(error.response.body);
      }
      return error;
    }
  },
};

export default sendGridServices;

METHOD

Meteor.methods({
  "email.sendgrid.send": (data, draftId) => {
    console.log("this is my data: ", data);
    const userId = Meteor.userId();
    try {
      data.prevEmailData
        ? sendGridServices.replyEmail(data, draftId, userId)
        : sendGridServices.sendEmail(data, draftId, userId);
    } catch (error) {
      console.log("this is error in method: ", error);
      throw new Meteor.Error("ERROR", "message NOT sent. Verify all required fields, else contact the admin");
    }
  },

CLIENT

    Meteor.call("email.sendgrid.send", payload, draftId, (err, res) => {
      if (!error) {
        successAlert({
          message: "email sent",
        });
        handleClose();
        clearInput(initialState);
        deleteDraft({ variables: { _id: draftId } });
      } else dangerAlert({ err });
    });

As far as I can see, your sendGridServices.sendEmail function catches your error and returns it, but never re-throws it, while your Meteor method has a try catch for code that doesn’t throw.

1 Like

As @copleykj says, you are preventing the error from bubbling into the method’s catch handler.
In addition, it’s an async function and you aren’t awaiting it in the method.

I think the first thing to do is revisit how try/catch handlers work, and how promises and async functions work.
Then have a read of how they interact with Meteor’s method system here:

Then you can fix your code by:

  1. not swallowing the error in sendGridServices's methods
  2. making sure you await the result of promises and async functions
  3. correctly handling errors in the place that you can do something about those errors.
1 Like

thanks a lot @copleykj and @coagmano for pointing me in the right direction!
I read the article and your comments and made it work (and more importantly got a better understanding of what’s going on :slight_smile: )

  1. correctly handling errors in the place that you can do something about those errors.

What do you mean with that? My goal was to reflect the error to the client so the user can contact me in case of an unforseen error with the details.

By that I mean that, generally speaking, errors should be allowed to bubble up the stack until something can be done about it, or you reach a boundary. Which means you should avoid catching and re-throwing.

You’ve got the right idea that in the case the system can’t recover from an error, you should reflect that back to the user, which means the error should be caught in the UI, so that it can be presented to the user and handled by them.

Of course, since web apps are split between client and server, we have a boundary at the API surface - the Meteor method - where the error needs to be caught, transformed and transmitted to the client.

Meteor will do this automatically if an error is thrown in a method, but often isn’t super descriptive (so it doesn’t leak details about system internals by default). So it’s still better to transform it yourself with new Meteor.Error

In your specific case, I mean that you can skip the catches in sendGridServices and catch in the method.

One last thing to keep in mind is that errors only bubble through synchronous code, so just be careful with errors around async calls. Make sure you await if you can, otherwise using .catch( handlers on Promises, or check for error in a callback.

3 Likes