loading...

Async Meteor Method calls

jankapunkt profile image Jan Küster ・2 min read

Meteor is mostly backward compatible to even < 1.0 projects. Therefore lots of code is still callback based. So is the Meteor.call method. You can easily wrap it into a Promise-based approach:

export const callMethod = ({ name, args }) => 
  new Promise((resolve, reject) => {
    Meteor.call(name, args, (error, result) => {
      if (error) {
        return reject(error)
      }

      return resolve(result)
    })
})

Although Meteor.call supports multiple arguments I rather prefer to pass a single object with named arguments to keep the code more expressive.

You can then use callMethod natively in an async environment:

Template.myTemplate.events({
  'click .some-button': async function (event, templateInstance) {
    const age = await callMethod({
      name: 'getAge', 
      args: { name: 'John Doe' }
    })
    console.log(age) // whatever the method returned
  }
})

Additionally you can "hook" into these calls and thus mix callbacks with promises and create a decent UX experience when method calls are part of user interactions:

export const callMethod = ({ name, args, prepare, receive, success, failure }) => {
  // before call
  if (typeof prepare === 'function') {
    prepare()
  }

  // create the promise
  const promise = new Promise((resolve, reject) => {
    Meteor.call(name, args, (error, result) => {
      // on received
      if (typeof receive === 'function') {
        receive()
      }

      if (error) {
        return reject(error)
      }

      return resolve(result)
    })
  })

  // on success
  if (typeof success === 'function') {
    promise.then(success)
  }

  // on error
  if (typeof failure === 'function') {
    promise.catch(failure)
  }

  return promise
}

The code can then be used for example to display a "waiting" indicator:

Template.myTemplate.events({
  'click .update-button': async function (event, templateInstance) {
   const updatedDoc = await callMethod({
     name: 'updateUser', 
     args: { name: 'John Doe', age: 42 },
     prepare: () => templateInstance.state.set('updating', true),
     receive: () => templateInstance.state.set('updating', false),
     failure: er => alert(er),
     success: () => alert('updated')
   })
   // process updatedDoc if desired
})

Discussion

pic
Editor guide