16

I was wondering about the best pattern/approach here. This is a function in my router, so the user hits 'quotes/:id', but for that view to render, I need a list of their projects, customers and currencies. What would be the best way to make sure all 3 fetches() have occurred before trying to instantiate the quotesEdit view? Is it considered bad practice to grab all the information when the user clicks something?

    quotesEdit: function(id) {
        kf.Collections.quotes = kf.Collections.quotes || new kf.Collections.Quotes();
        kf.Collections.projects = kf.Collections.projects || new kf.Collections.Projects();
        kf.Collections.currencies = kf.Collections.currencies || new kf.Collections.Currencies();
        //do a fetch() for the 3 above
        kf.Collections.customers = kf.Collections.customers || new kf.Collections.Customers();
        var quote = kf.Collections.quotes.where({Id: parseInt(id, 10)});
        kf.Utils.ViewManager.swap('sectionPrimary', new kf.Views.section({
          section: 'quotesEdit',
          model: quote[0]
        }));
    }
Chris W.
  • 37,583
  • 36
  • 99
  • 136
benhowdle89
  • 36,900
  • 69
  • 202
  • 331

3 Answers3

40

I find a combination of jQuery deferreds and underscore's invoke method solves this elegantly:

//call fetch on the three collections, and keep their promises
var complete = _.invoke([quotes, projects, currencies], 'fetch');

//when all of them are complete...
$.when.apply($, complete).done(function() {
   //all ready and good to go...
});
jevakallio
  • 35,324
  • 3
  • 105
  • 112
  • 3
    By default fetch is made using `GET` http method. And it's amazing I can write `_.invoke([quotes, projects, currencies], 'fetch', {type: 'POST'})` – lexeme May 24 '13 at 05:51
  • I've searched for hours for this particular implementation of this solution. THANK YOU. – Ray Dec 17 '14 at 23:59
  • 3
    I think this answer is a bit too clever for it's own good. You have to understand the behavior of `_.invoke`, `fetch`, `when`, and `apply`. Whereas @mrappleton's answer is perfectly clear even for someone with even only has a vague understanding of `$.when`. – Chris W. Apr 30 '15 at 23:47
  • 1
    Note to self: unlike `_.map`, which applies a _single function_ to each element in the array, `_.invoke` allows us to call a _method_ function on each object in the array so we get `[quotes.fetch(), projects.fetch(), currencies.fetch()]`. Have a look at the Underscore source code for `invoke` and note the line `var func = isFunc ? method : value[method];` which is giving us `[quotes['fetch'], projects['fetch'], currencies['fetch']]`. – Robert Mar 02 '16 at 09:53
  • 1
    Unfortunately this beauty is skin deep, and this solution is needlessly complex given a simple `$.when().then()` solves the problem. – Madbreaks Apr 12 '17 at 17:27
20

Promises! Specifically jQuery.when

You can do something like this:

$.when(
  kf.Collections.quotes.fetch(),
  kf.Collections.projects.fetch(),
  kf.Collections.currencies.fetch()
).then(function(){
  // render your view.
});

jQuery.ajax (and by extension backbone fetch) returns a promise and you can use $.when to set a callback function once multiple promises are resolved.

mrappleton
  • 372
  • 3
  • 10
4

Backbone's fetch returns a jQuery Deferred object (a promise). So you can use jQuery's when function to wait for all of the promises to resolve:


quotesEdit: function(id) {
  kf.Collections.quotes = kf.Collections.quotes || new kf.Collections.Quotes();
  kf.Collections.projects = kf.Collections.projects || new kf.Collections.Projects();
  kf.Collections.currencies = kf.Collections.currencies || new kf.Collections.Currencies();

  //do a fetch() for the 3 above
  var quotePromise = kf.Collections.quotes.fetch();
  var projectsPromise = kf.Collections.projects.fetch();
  var currenciesPromise = kf.collections.currencies.fetch();

  // wait for them to all return
  $.when(quotePromise, projectsPromise, currenciesPromise).then(function(){

    // do stuff here, now that all three have resolved / returned

    kf.Collections.customers = kf.Collections.customers || new kf.Collections.Customers();
    var quote = kf.Collections.quotes.where({Id: parseInt(id, 10)});
    kf.Utils.ViewManager.swap('sectionPrimary', new kf.Views.section({
      section: 'quotesEdit',
      model: quote[0]
    }));

  };

}

I've written a bit about promises and jQuery's when, here:

http://lostechies.com/derickbailey/2012/03/27/providing-synchronous-asynchronous-flexibility-with-jquery-when/

http://lostechies.com/derickbailey/2012/07/19/want-to-build-win8winjs-apps-you-need-to-understand-promises/

that second link is still valid, in spite of the primary subject being Win8 JS

Derick Bailey
  • 72,004
  • 22
  • 206
  • 219
  • Thanks! Everyone pretty much said about 'promises' but it was the crafty use of @fencliff's _.invoke() that swung me! – benhowdle89 Feb 19 '13 at 17:08
  • unwanted extra layer of indirection, if you ask me :) ... but i guess the reduction in calling "fetch" three times is a little prettier – Derick Bailey Feb 19 '13 at 17:12
  • How does one use the input of the first fetch in the second and so on? @DerickBailey – vini Mar 17 '16 at 11:48