Meteor methods and undefined via callback

Am I missing something? Meteor methods are async and should be used with the callback, sure. No problem. But even so I’m getting undefined when I call it.

Client .jsx:

const validateAll = () => {
    Meteor.call('validateNewUser', { username, email, password1, password2 }, (err, result) => {
      console.log(err)
      console.log(result)
      (etc)

main.js (server):

Meteor.methods({
  validateNewUser ({username, email, password1, password2}) {
    // Username validation
   (etc)

I’ve outputted various stuff in the meteor method, and it should be returning the correct value. Which also means it is getting called appropriately. Everything on the client-side is stubbornly undefined, however.

Thoughts? Thanks in advance!

Do you mean that the output from

console.log(err);
console.log(result);

Are undefined?

Yeppers. Obviously I’m trying to do other stuff past that with the result, but I figured this would be enough to illustrate what I’m doing and what the problem is.

Anybody have any ideas on this? I don’t really know how to proceed after exhausting my bag of tricks.

What are you returning in the method body? Can you post the full code?

Yeah for sure. It just didn’t seem relevant given what gets output by the meteor method’s console.logs, but here is the whole thing:

Meteor.methods({
  validateNewUser ({username, email, password1, password2}) {
    // Username validation
    const longEnough = username.length >= MIN_USERNAME_LENGTH;
    if (!longEnough) {
      return VALIDATION_RETURN_CODES.USERNAME_MIN_LENGTH_ERROR;
    }

    Meteor.call('checkIfUsernameExists', username, (err, result) => {
      console.log(result);
      if (result) {
        return VALIDATION_RETURN_CODES.USERNAME_EXISTS;
      } else {
        // Email validation
        const valid = String(email)
        .match(
          /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
        );
        if (!valid) {
          return VALIDATION_RETURN_CODES.INVALID_EMAIL;
        }
        
        Meteor.call('checkIfEmailExists', email, (err, result) => {
          console.log(result)
          if (result) {
            return VALIDATION_RETURN_CODES.EMAIL_EXISTS;
          } else {
            console.log(password1 === password2 && password2.length >= MIN_PASSWORD_LENGTH)
            // Password validation
            if (password1 === password2 && password2.length >= MIN_PASSWORD_LENGTH) {
              console.log('Should be fine')
              console.log(VALIDATION_RETURN_CODES.OK)
              return VALIDATION_RETURN_CODES.OK;
            } else if (password1 !== password2) {
              return VALIDATION_RETURN_CODES.PASSWORD_MISMATCH;
            } else if (password2.length < MIN_PASSWORD_LENGTH) {
              return VALIDATION_RETURN_CODES.PASSWORD_MIN_LENGTH_ERROR;
            } else {
              return VALIDATION_RETURN_CODES.UNKNOWN;
            }
          }
        });
      }
    })
  },

  checkIfUsernameExists (username) {
    return (Meteor.users.findOne( {username })) ? true : false;
  },
  checkIfEmailExists (email) {
    console.log(Meteor.users.find( {"emails.address" : email }).count() > 0)
    return Meteor.users.find( {"emails.address" : email }).count() > 0 ? true : false;
  }
}

The VALIDATION_RETURN_CODES is just effectively an enum.

validateNewUser method has no return value except if the username is not long enough

I’m not sure that’s right - it short-circuits and returns that error code if that’s the case, otherwise should proceed to the Meteor.call(‘checkIfUsernameExists’,…) that’s right after, right?

The return of the callbacks of the succeeding method calls are not the return of the validateNewUser function.

In the server, if you do not include a callback when calling the methods, you can get the return values like:

const result = Method.call('methodName', inputs);

If you want to stick with a callback, then you need to transform the callback into a promise that you need to await to get the results

1 Like

Not that in each Meteor.call you are defining a new arrow function for the callback, and return only returns from the innermost function.
See this excellent SO answer for more: javascript - How to return the response from an asynchronous call - Stack Overflow

In Meteor, rjdavid’s answer is the quickest way to get this working

but to add to that, I strongly recommend that you don’t call Methods from other Methods on the server side. Meteor Methods are like HTTP endpoints in a traditional REST app and you wouldn’t make HTTP calls from the server to itself to call other functions.

I recommend extract the logic into their own functions, and using the Methods purely for authorization, validation and argument parsing, and then call into the function with the main logic.
Then when you need to call the logic from another method, you can run the backing function directly.

That said, the abstraction is a trade-off and you should adapt random advice from the internet to your specific situation. If your app is simple, then calling methods directly is also perfectly fine

1 Like

And so using the sync form of Meteor.call on the server, your method would look like this:
(note: I have lightly refactored to suit my OCD)

const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
Meteor.methods({
  validateNewUser ({username, email, password1, password2}) {
    // Username validation
    const longEnough = username.length >= MIN_USERNAME_LENGTH;
    if (!longEnough) {
      return VALIDATION_RETURN_CODES.USERNAME_MIN_LENGTH_ERROR;
    }

    const userNameExists = Meteor.call('checkIfUsernameExists', username);
    console.log(`userNameExists ${userNameExists}`);
    if (userNameExists) {
      return VALIDATION_RETURN_CODES.USERNAME_EXISTS;
    }
    // Email validation
    const emailIsValid = String(email).match(EMAIL_REGEX);
    if (!emailIsValid) {
      return VALIDATION_RETURN_CODES.INVALID_EMAIL;
    }
    
    const emailExists = Meteor.call('checkIfEmailExists', email);
    console.log(`emailExists ${emailExists}`)
    if (emailExists) {
      return VALIDATION_RETURN_CODES.EMAIL_EXISTS;
    }
    console.log(`password check ${password1 === password2 && password2.length >= MIN_PASSWORD_LENGTH}`)
    // Password validation
    if (password1 === password2 && password2.length >= MIN_PASSWORD_LENGTH) {
      console.log('Should be fine')
      console.log(`VALIDATION_RETURN_CODES.OK ${VALIDATION_RETURN_CODES.OK}`)
      return VALIDATION_RETURN_CODES.OK;
    } else if (password1 !== password2) {
      return VALIDATION_RETURN_CODES.PASSWORD_MISMATCH;
    } else if (password2.length < MIN_PASSWORD_LENGTH) {
      return VALIDATION_RETURN_CODES.PASSWORD_MIN_LENGTH_ERROR;
    } else {
      return VALIDATION_RETURN_CODES.UNKNOWN;
    }
  },

  checkIfUsernameExists (username) {
    return (Meteor.users.findOne( {username })) ? true : false;
  },
  checkIfEmailExists (email) {
    console.log(Meteor.users.find( {"emails.address" : email }).count() > 0)
    return Meteor.users.find( {"emails.address" : email }).count() > 0 ? true : false;
  }
}