Promises play no role in this instance. The code would be as simple as follows:
Meteor.methods({
sendVerificationLink: () => {
if (!Meteor.userId()) {
throw new Meteor.Error("not-logged-in", "You are not logged in.");
}
const body = Meteor.user().services.email.verificationTokens[0].token;
Email.send({
SecureToken: "xxx",
To: "xxx@yopmail.com",
From: "xxx@gmail.com",
Subject: "This is the subject",
Body: body,
});
}
});
Note:
it would make sense to check whether the object hierarchy Meteor.user().services.email.verificationTokens[0].token exists, because if it doesn’t, you’ll get a TypeError. I would use lodash get or Meteor._get.
if an error is thrown, just let it fly. Returning something like { isError: true, err } is rather unusual. The convention is that the client callback should be invoked with an error, if there was one, rather than a result, which contains an error.
what however would make sense in every method is a selective conversion of any error other than Meteor.Error to Meteor.Error. For this purpose all of my methods are wrapped in a function with a try catch calling this function:
const rethrowAsMeteorError = error => {
if (error instanceof Meteor.Error) {
throw new Meteor.Error(error.error, error.reason, error.stack);
}
if (error instanceof Error) {
if (error.name === "ClientError") {
throw new Meteor.Error(error.error, {message: error.message, details: error.details}, error.stack);
}
throw new Meteor.Error(error.message, { actualErrorType: error.name }, error.stack);
}
if (error instanceof Object) {
throw new Meteor.Error(error.name, error.message, error.stack);
}
// the assumption is that "error" is of a primitive type, such as string or number, etc.
throw new Meteor.Error(error);
}
The problem at hand in Meteor is that if an error other than Meteor.Error is thrown in method, all the client receives is a Meteor.Error along the lines of “500 Server Error”, which is very inadequate.
Exception while simulating the effect of invoking ‘sendVerificationLink’ TypeError: Cannot read property ‘email’ of undefined
at MethodInvocation.sendVerificationLink…
and nothing is passed to body variable, so mail is not sent.
when I console.log(Meteor.user().services.email.verificationTokens[0].token) it shows value on server? but reports same error on client.
...
import _ from 'lodash';
...
const body = _.get(Meteor.user(), "services.email.verificationTokens[0].token");
if (!body) {
throw new Meteor.Error("no token");
}
...
Thanks, I missed that bit! I must say, however useful the optional chaining is, the resulting expression can be very ugly.
Too bad that no simple shortcut syntax was introduced to the effect of “ignore whatever in the following expression can go wrong, since everything is optional here”.
Then we could write instead of
const body = Meteor.user()?.services?.email?.verificationTokens?.[0]?.token;
…something like this:
const body = ?!Meteor.user().services.email.verificationTokens[0].token;
Strange thing. I’ve tried both ‘lodash’ and chaining syntax and got “undefined” for body variable.
Then, I’ve made a publish with ‘services.email.verificationTokens’ field and subscribed on client. With that everything is working fine, but I don’t understand why I have to subscribe on client to make server method work correctly?
You have the method defined in shared code space. That is to say on both the client and the server. When Metoer methods are available in both, Meteor runs the one on the client as a simulation so that it can perform latency compensation. If you remove the code from the client, it will no longer run the simulation and this error should clear up.