Async proxy - modern method calls

Super simple modernization of method calls, with a tiny implementation. I basically wanted a tiny function I could call which would return a Promise for Meteor method calls, so I can do async/await. This is that tiny function:

// This may require use of meteor/promise to work on server side with fibers - not sure
export const callAsync = (methodName, ...args) => new Promise(
  (resolve, reject) => Meteor.call(methodName, ...args, (error, result) => {
    if (error) reject(error)
    resolve(result)
  })
)

But then I thought, it’s still weird to call a method with a string, like await callAsync("methodName", "arg1", "arg2") so I remembered that Proxy objects are a thing (I used to use those in ES4/AS3 for RPC fun!).

So I made a quick Proxy wrapper:

export const Methods = new Proxy({}, {
  get: (obj, key) => (...args) => callAsync(key, ...args)
})

So now I can import {Methods} from '/imports/utils/methods' and then call methods as though they are methods!

await Methods.methodName("arg1", "arg2")

This may even be a nicer way to expose new async method calls than reworking Meteor.call - which I always found weird anyway due to its clash with Function.call.

Once we add in polyfills and babel transform overhead, this gets larger, but with Meteor’s upcoming duel build system, this will remain tiny in the bundle. And that’s awesome!

4 Likes

I made it even smaller, and added an additional Proxy for other invocations like:

await MeteorAsync.loginWithPassword(user, pass)
import { Meteor } from 'meteor/meteor'

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

export const Methods = new Proxy({}, {
  get: (obj, key) => (...args) => callAsync(key, ...args)
})

export const MeteorAsync = new Proxy({}, {
  get: (obj, key) => (...args) => new Promise(
    (resolve, reject) => Meteor[key](...args, (error, result) => {
      if (error) reject(error)
      resolve(result)
    })
  )
})
4 Likes

That’s pretty neat! But note that Proxies are not supported in IE, so you will have to add a polyfill and this is the best option:

Oh! I didn’t realize that’s not in babel-polyfill. That polyfill would be enough to make it work. I wonder if there is some way to get that included in only the legacy bundle in Meteor 1.7.

1 Like

It was easy enough to create a local package, to make the proxy polyfill work:

package.js:

Package.describe({
  name: 'google-chrome:proxy-polyfill',
  summary: 'Polyfill for the Proxy object',
  version: '0.2.0',
  git: 'https://github.com/GoogleChrome/proxy-polyfill.git'
})

Npm.depends({'proxy-polyfill': '0.2.0'})

Package.onUse(function (api) {
  api.versionsFrom('METEOR@1.7-rc.11')
  api.addFiles('export.js', 'legacy')
})

exports.js:

if (!window.Proxy) {
  Proxy = require('proxy-polyfill/src/proxy')
}