Benjen
Benjen

Reputation: 2925

Are methods bound to events called asynchronouly or not in Javascript / Backbone.js?

I am wondering if methods bound to events are called asynchronously or not in javascript? In my case I am using Backbone.js to build an app. I use an event aggregator to communicate between views.

If I have a method which triggers an event, will the methods in other views bound to that event complete before the rest of the method calling the trigger event is run?

Event aggregator is as follows:

var eventAggrigator = _.extend({}, Backbone.Events);
eventAggrigator.on('submitContactEditForm', function() {
  console.log('Contact edit form submit event triggered');
});

Function call which triggers the event (this function is called from ViewA):

saveContact: function(event) {
  var self = this;
  // Prevent submit event trigger from firing.
  event.preventDefault();
  // Trigger form submit event.
  eventAggrigator.trigger('submitContactEditForm');
  // Update model with form values.
  this.updateContact();
  // Save contact to database.
  this.model.save({
    success: function(model, response) {
      console.log('Contact ' + self.model.get('surname') + ' saved');
    },
    error: function(model, response) {
      throw error = new Error('Error occured while saving contact.');
    }
  });
},

ViewB is bound to the event 'submitContactEditForm' (see relevant code from ViewB below):

initialize: function() {
  _.bindAll(this, 'addSortableFields', 'appendNewField', 'getFieldsHtml', 'removeField', 'render', 'setEmailValues');
  // Bind to event aggregator.
  eventAggrigator.bind('submitContactEditForm', this.setEmailValues);
  this.model = this.options.model;
},
setEmailValues: function() {
  // Extract email form values.
  var emails = _.clone(this.model.get('email'));
  var emailFields = this.$('.email-field');
  _.each(emails, function(email, index) {
    email.value = emailFields.eq(index).val();
  });
  this.model.set('email', emails);
},

So the question would be, will ViewB.setEmailValues() always be completed before this.model.save() in ViewA.saveContact() is executed?

Upvotes: 4

Views: 838

Answers (2)

ggozad
ggozad

Reputation: 13105

The relevant part where events are triggered in backbone is this:

trigger: function(events) {
  var event, node, calls, tail, args, all, rest;
  if (!(calls = this._callbacks)) return this;
  all = calls.all;
  events = events.split(eventSplitter);
  rest = slice.call(arguments, 1);

  // For each event, walk through the linked list of callbacks twice,
  // first to trigger the event, then to trigger any `"all"` callbacks.
  while (event = events.shift()) {
    if (node = calls[event]) {
      tail = node.tail;
      while ((node = node.next) !== tail) {
        node.callback.apply(node.context || this, rest);
      }
    }
    if (node = all) {
      tail = node.tail;
      args = [event].concat(rest);
      while ((node = node.next) !== tail) {
        node.callback.apply(node.context || this, args);
      }
    }
  }

  return this;
}

As you can see, event handlers are called synchronously one by one.

Warning: If your handlers make asynchronous calls, then of course these will not be guaranteed to complete before the execution stack continues after trigger. This, can be alleviated by the use of callbacks that would trigger completion or better by use of Deferred/Promises.

Upvotes: 4

fguillen
fguillen

Reputation: 38888

It is a very interesting question with a very interesting answer.

I was expecting this behavior to be asynchronous but it turned out to be synchronous.

I've based my conclusion in this code:

var MyModel = Backbone.Model.extend();

var oneModel = new MyModel();
oneModel.on( "event", eventHandler1 );
oneModel.on( "event", eventHandler2 );

function eventHandler1(){
  for( var i = 0; i < 10; i++ ){
    console.log( "eventHandler1", i );
  }
}

function eventHandler2(){
  for( var i = 0; i < 10; i++ ){
    console.log( "eventHandler2", i );
  }
}

function testEvent(){
  console.log( "testEvent::INI" );
  oneModel.trigger( "event" );
  console.log( "testEvent::END" );
}

testEvent();​

That results in this output:

testEvent::INI
eventHandler1 0
eventHandler1 1
eventHandler1 2
eventHandler1 3
eventHandler1 4
eventHandler1 5
eventHandler1 6
eventHandler1 7
eventHandler1 8
eventHandler1 9
eventHandler2 0
eventHandler2 1
eventHandler2 2
eventHandler2 3
eventHandler2 4
eventHandler2 5
eventHandler2 6
eventHandler2 7
eventHandler2 8
eventHandler2 9
testEvent::END 

Check the jsFiddle.

Upvotes: 0

Related Questions