Meteor Guide: Methods

hey mate,

Just following the conversation along, trying to understand the different points of view. When you say, ‘However, there’s nothing stopping you from defining a Promise wrapper for the call.’, is the below an example of what you mean?

// server side
export const authorizationMSGraph = new ValidatedMethod({
  name: 'msGraph.authorization',
  validate: msGraph.omit(['msGraphInformation']).validator(),
  async run ({ msGraphAction }) {
    if (Meteor.isServer) {
      switch (msGraphAction) {
        case ('getToken'): {
          const method = 'POST';

          const url = 'https://login.microsoftonline.com/'
            + Meteor.settings.private.msGraph.tenantId
            + '/oauth2/token';

          const headers = {
            'Content-Type': 'application/x-www-form-urlencoded',
          };

          const params = {
            'grant_type': Meteor.settings.private.msGraph.grant_type,
            'client_id': Meteor.settings.private.msGraph.client_id,
            'client_secret': Meteor.settings.private.msGraph.client_secret,
            'resource': 'https://graph.microsoft.com',
          };

          return await HTTP.call(method, url, { headers, params });
        }
        default: {
          throw new Meteor.Error(
            'msGraph.authorization.failure',
            `Method ${msGraphAction} was not recognized...`,
          );
        }
      }
    }
  },
});
// client side
export function getAccessTokenPromise() {
  const msGraphInformation = {};

  return new Promise((resolve, reject) => {
    authorizationMSGraph.call({
      msGraphAction: 'getToken',
      msGraphInformation: msGraphInformation,
    }, (err, result) => {
      if(err) {
        reject(err);
      } else {
        resolve(result);
      }
    });
  });
}

Hence the getAccessTokenPromise() will work on the client browser where ever? Just trying to get clarification.

Thanks so much.

Tat

Yes, that’s the principle :slight_smile: .

Just a small point about your HTTP.call: that does not return a Promise, so your await is not needed there. Instead that (non-callback) form uses Fibers to wait for completion. However, you could choose to wrap the callback form in a Promise and use await.

@robfallows Anything you notice about my example that is causing the ‘res’ value to be undefined?

I’m trying to understand what will happen given that you sometimes have an args parameter and sometimes not. I don’t even know if that could cause what you’re seeing, but it may be worth rationalising your code for consistency and see if that makes a difference.

@robfallows Well, just in the example I posted, that method takes no arguments. It just returns a literal number, but the response, from the calling code, is undefined. So, although I know that kind of looks like it might be pseudo-code or example code, it’s actually code I wrote and executed to test the simplest possible situation. When I run that actual code, response is always undefined, though I can’t see any reason why that would happen.

Or maybe I’m not understanding your response…? Are you saying the code I’m showing should actually be working the way I expect, so there might be something else not accounted for?

The “advanced biolerplate” example does not return the result of the method invocation. It only counts on exceptions propagating through the stack (note their calling example, it only checks err, and else assumes everything went OK, never assumes a result value, as it is an update operation). If you want to return a value from the method, you should do so explicitly:

return TestMethod.run.call(this, args);

and

return Meteor.apply(this.name, [], options, callback);

for the client side; otherwise, the method returns nothing and res is, obviously, undefined.

1 Like

@alon

Thanks for your response. I had tried it with returning the value of Meteor.apply and still gotten undefined returned. At that point I thought perhaps apply doesn’t actually return the value from the callback, but maybe it somehow happened implicitly. Just because I had no other theory and was grasping at straws.

regarding this example line you show:

return TestMethod.run.call(this, args);

Where would that go? The client code is not inside a callback, so I don’t know what it would be returning to.

What the server-side method definition returns, the client-side callback gets as res (if no error is raised).

The method definition is


Meteor.methods({
  [TestMethod.name]: function(args) {
    TestMethod.run.call(this, args)
  }
})

which calls the function but does not return its value.

You can return the value from the method and have it available to the client by using:


Meteor.methods({
  [TestMethod.name]: function(args) {
    return TestMethod.run.call(this, args)
  }
})

The apply part is meant for the client-side call. It allows you to have more control over the way the call is made, but you are not returning its value (it will return a value or raise an error from the client method stub due to the options you passed to apply).

import {Meteor} from 'meteor/meteor'

export const TestMethod = {

  name: 'testMethod',

  run() {
    return 100
  },

  call(callback) {

    const options = {
      returnStubValue: true,
      throwStubExceptions: true
    }

    return Meteor.apply(this.name, [], options, callback)
  }
}

Meteor.methods({
  [TestMethod.name]: function(args) {
    return TestMethod.run.call(this, args)
  }
})

with the above code, you should get:

import {TestMethod} from '/imports/data/api/test'

let stubValue = TestMethod.call((err, res) => {
  console.log("Response", res);
});
console.log(stubValue);

should both produce 100.

3 Likes

@alon

Oh man. Of course, the actual Meteor.methods() registration! Somehow I completely missed the obvious SEVERAL times while trying to get this to work in the past. That’s the one place I forgot about. Thanks for your patience and help!

This, btw, is what I get for having created a Meteor Method factory that encapsulates the actual call to Meteor.methods() to register them. It made me forget about that part.

@sashko I would like to clear up sth about Method call life cycle. It’s written in the guide that if an error occurs during simulation on the client side, Meteor continues with server side operations (sending DDP package to the server, etc.). Why does Meteor continue with the server side operations in a failed case? Isn’t it waste of resources?

If an exception is thrown from the Method simulation, then by default Meteor ignores it and continues to step (2)…

It’s because server method runs in different context so there might be correct response even if client method fails.