How do you asynchronously initiate a callback in a meteor method?


#1

I have a TCP socket on my server that is connected to a controller. In plain node, I would do something similar to as follows:

let commands = net.connect(config.port, config.host);
let messages = net.connect(config.port, config.host, () => {
  messages.write('CF I\r');  // receive messages on this socket
});

function execute(command, callback=_.noop) {
  messages.on('data', (data) => {
    if (data.toString('ascii') === '?') {
      throw new Meteor.Error('ControllerError', 'Command not recognized');
    } else {
      let args = _.compact(data.toString('ascii').replace('\r\n', '').split(':'));
      if (args[0] === 'End') {
        callback();
      } else if (args[0] === 'Error') {
        let report = args.splice(1, args.length).toString('ascii');
        throw new Meteor.Error('ControllerError', report);
      }
    }
  });
}

My question is, how does this translate into a Meteor method asynchronously that actually blocks the client until the callback? Essentially what I want to do is:

Template['Controller'].events({
  'click button[data-action="start"]': function (e, template) {
    $(e.target).attr('disabled', true);
    Meteor.call('execute', 'Startup', function () {
      // completed the startup operation now
      $(e.target).attr('disabled', false);
    });
  }
});

Ideally, this would also have some sort of timeout. Say like 5 seconds.


#2

Can you call a Meteor method asynchronously AND block on the client?

I thought If you wanted to block you had to call the method this way, am I wrong?

Template['Controller'].events({
  'click button[data-action="start"]': function (e, template) {
    $(e.target).attr('disabled', true);
   
    // sync call so client is blocked
    var returnValueOfSomeKindOrNot = Meteor.call('execute', 'Startup');
    // completed the startup operation now
    $(e.target).attr('disabled', false);
  }
});

(also, how do you get the color attributes in your code on here?)


#3

I got it working, not sure if it’s helpful to anyone.

I made my object a package and an event emitter. Used meteorhacks:async to manage the asynchronous nature of it.

Cylon._messages.on('data', (data) => this.emit.apply(this, Cylon.parse(data)));

Cylon.execute = function (routine, seconds) {
  check(routine, String);

  let timeout = _.isNumber(seconds) ? seconds : this.config.DEFAULT_TIMEOUT;
  let timeoutError = new Meteor.Error('GalilError', 'CommandTimeout');
  var timerId = Meteor.setTimeout(function () {}, timeout);
  let refreshTimer = function (done, data) {
    clearTimeout(timerId);
    timerId = setTimeout(() => done(timeoutError), timeout);
  }

  let resp = Async.runSync((done) => {
    this.on('End', (routine) => done(null, routine));
    this.on('Error', (err) => done(err, null));
    this._commands.write(`XQ#${program}\r`);
    this._messages.on('data', refreshTimer.bind(this, done));
    refreshTimer.bind(this, done);
  });

  this._messages.removeListener('data', refreshTimer);
  if (resp.error) throw resp.error;
  return resp.response;
}