Meteor Guide: Methods

@shilman “Meteor.Error” is a misnomer in the first place because it only exists for DDP as far as I can tell, so it should be called “DDP.Error” or “DDP.ClientError”. That said, I’d prefer to generalize more than that because I think the idea of a “standards spec” for throwing client-safe errors would be useful for many different frameworks beyond Meteor. I think the simplest and most generic way to “spec” this idea is a boolean prop clientSafe: true on any Error instance that is thrown.

My proposal for a standard spec would be something like this:

“When an Error is thrown while executing code as part of an HTTP or Websocket request, catch and examine the Error instance. If error.clientSafe is exactly true, then error.name, error.message, error.error, error.reason, and error.details may all be assumed to be safe to include in the response sent to the requesting client. Alternatively, if error.sanitizedError is an Error instance, then error.sanitizedError.name, error.sanitizedError.message, error.sanitizedError.error, error.sanitizedError.reason, and error.sanitizedError.details may all be assumed to be safe to include in the response sent to the requesting client. The error stack should never be sent.”

To follow this spec within Meteor/DDP, I think we only need to add an || error.clientSafe === true to the condition, and change the sanitizedError check to work when sanitizedError is any type of Error and not just Meteor.Error.

Thoughts, @sashko?

2 Likes

Also, @rlivingston we can add a custom error function, too, since that will likely be necessary in the short term until everyone can get onto a Meteor version where things work out of the box.

2 Likes

That makes sense. I’ve documented your proposal as a github issue, and am happy to make the code changes, add unit tests, update documentation, and whatever else is required to make this happen:

@aldeed @sashko please advise and feel free to comment on the issue, assign to me, etc. or I can create a PR and we continue the conversation there.

There’s still no mention of promises in guide other than in context of npm packages, is the expectation then to introduce dependencies in face of a solid choice (official) or just use wrapasync which apparently is a package for callback hell.

Most of the promise based packages are deprecated, citing introduced support for promises officially.

You can now use async/await directly in Meteor.

but wheres the documentation. as in how make method be promise.

async function asdf () {
  var a = await Meteor.call("refreshToken", Meteor.user())
  console.log(a)
  return a
}

This for example doesn’t appear to do anything on client side.

1 Like

So async function evaluates as a promise (just “return 1” still makes it a promise), so, so long as method is appended with async then it becomes a promise.

This should go into data loading section I think.

Just to add a little more clarity to this: the Promise is resolved on the server before returning to the client.

So, the client does not see a Promise returned from a Meteor.call - it sees the result exactly as it’s always done.

so you can not await it as a promise then if it returns only on server? I suppose not…

No. It would not be possible to return a Promise to the client anyway - how would a client resolve a Promise made by the server?

However, there’s nothing stopping you from defining a Promise wrapper for the call. There is currently a “gap” with client-side method calls, in that they are not Promisified when the non-callback form is used. Although, there was at one time a PR which almost made it into Meteor core.

well await or then expects resolved promise, afaik promise is an object, so it’s not beyond impossible to send promise object (that has resolved status).

How do you return a value to the client when using the advanced methods? If I return a value in the run function it the result in the call is undefined.

I’m not entirely sure what you’re referring to here, but have you read these articles which explain how to use Promises and async/await?

I’ve been working with/learning what this guide calls “advanced” Meteor methods. It really helps me out with better unit testing, but I’m not able to get a returned value from client calling code when my ‘run’ function returns something. Within the client callback, both err and res are always undefined. I notice that here in the docs, only stubs for mongo operations are discussed and those appear to happen implicitly when returnStubValue is set to true. But what about when I just want the server to find/calculate something and return it to the client? I haven’t turned up many answers via google searches either.

1 Like

That is exactly the problem I had. You explained a bit better haha! @robfallows

2 Likes

To clarify the problem that I (and possibly @reimerwilliam) am having, let’s say I have this method in my advanced meteor method object:

run() {
  return SomeCollection.find().count()
}

And in my client code I’ve got this:

MyMethod.call((err, res) => {
  console.log("Error", err)
  console.log("Response", res)
})

The rest of my advanced method code is IDENTICAL to what’s here in the documentation; most significantly, the definition of the call() method.

What I get back is that both err and res are undefined, and yes, there are documents in the db. As a matter of fact I even altered the run() code to return a literal (say, “return 12”), however, in the client code, res still comes back as undefined.

So it would seem we cannot return arbitrary values from advanced meteor methods like we could with conventional methods, or else I’m just not understanding some extra steps we would have to take in order to do that…? Whatever that is, it’s far from obvious, to me at least.

Can we see that please?

@robfallows Sure, in fact I just wrote a barebones little test method to get it down to the basics.

The method:

import {Meteor} from 'meteor/meteor'

export const TestMethod = {

  name: 'testMethod',

  run() {
    return 100
  },

  call(callback) {

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

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

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

The client consumer code:

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

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

The output:

Error undefined
Response undefined
1 Like