1

when a function returns a promise, I can call some other function after the first one did it's work:

do_stuff().then(function(){
  alert('yoooo');
});

and do_stuff() looks like this:

function do_stuff(){

  if(!got_the_data){

    var d = $.Deferred();

    $.ajax({
      success: function(html){
        $('#box').append(html);
        $('#box').addClass('visible');
        $('#box').on('transitionend webkitTransitionEnd', function(){
          got_the_data = true;
          d.resolve();
        });
      }
    });

    return d.promise();

  }else{
    // got the data, what now?
  }
}

but what do I return if I already did the ajax request (result is cached) and I don't have to wait for anything? I can't return d.resolve() because the function that was attached to then() won't fire :/

and I can't return d.promise because I have to resolve the "d" somewhere

thelolcat
  • 10,995
  • 21
  • 60
  • 102
  • FYI, your `do_stuff()` is an anti-pattern. There's already a promise returned from `$.ajax()`. You can use that - you don't need to create a new promise. – jfriend00 Dec 08 '14 at 00:30
  • See http://stackoverflow.com/a/27346898/1331430 – Fabrício Matté Dec 08 '14 at 00:32
  • jfriend but in my real code the resolve() is called inside a transitionend event. Basically I have to get the ajax, put some html in the page, trigger a transition, wait for it to complete, then resolve – thelolcat Dec 08 '14 at 00:37
  • @thelolcat: In that case, you should create an extra promise for the transition, and chain it together with the ajax promise. See [this question](http://stackoverflow.com/q/23803743/1048572) for more information on the antipattern – Bergi Dec 08 '14 at 01:29
  • @thelolcat If possible , can post `transitionend event` pieces ? Thanks – guest271314 Dec 08 '14 at 01:36
  • @Bergi to be fair, until they fixed `.then` to match Promises/A a bit better this was the only way, and it kinda stuck... – Alnitak Dec 08 '14 at 14:57

2 Answers2

2

You can choose between two approaches; caching data or caching promises.

Here's two examples, both of which key on url, though any other key may be used, as appropriate - as long as it uniquely identifies each individual case.

Cache data

var dataCache = {};

function do_stuff_1(url) {
    if(dataCache[url] === undefined) {
        return $.ajax({
            url: url
        }).then(function(data) {
            dataCache[url] = data;
            return data;
        });
    } else {
        return $.when(dataCache[url]);
    }
}

Cache promises

var promiseCache = {};

function do_stuff_2(url) {
    if(!promiseCache[url]) {
        promiseCache[url] = $.ajax({
            url: url
        });
    }
    return promiseCache[url];
}

In both approaches, the function will (barring an uncaught error) return a promise, either by executing $.ajax() or by retrieving data/promise from the cache.

In most applications, there's virtually nothing to distinguish one approach from the other.

In an application where the cache is likely to grow to be large, then cache the data and avoid the overhead of caching promise wrappers.

If necessary, the cache can be pre-loaded, thus avoiding the need to fetch known data :

var dataCache = {
    '/path/to/data/a': 'A',
    '/path/to/data/b': 'B'
}

or

var promiseCache = {
    '/path/to/data/a': $.when('A'),
    '/path/to/data/b': $.when('B')
}
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
1

The simplest solution is to just return an empty already-resolved promise in the else clause:

return $.Deferred().resolve();

To avoid the Promise anti-pattern your code might be better structured thus:

function show_stuff(html) {
    return $.Deferred(function(def) {
        $('#box').append(html);
        $('#box').addClass('visible');
        $('#box').on('transitionend webkitTransitionEnd', def.resolve);
    });
}

function do_stuff() {
    if (got_the_data) {
        return $.Deferred().resolve();
    } else {
        return $.ajax(...).then(show_stuff);
    }
}

Note that there's no line (yet) setting got_the_data = true - you should consider whether it's really appropriate to wait until the data has been displayed to set this flag, otherwise there's nothing to prevent multiple invocations of do_stuff all resulting in new stuff getting added to #box. IMHO you would be better with a getting_the_data flag.

Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • Thanks, that worked apparently. But why do ppl keep saying my code is antipattern? – thelolcat Dec 08 '14 at 15:06
  • because it is - the above is fine for the "fake a promise" case, but in your other code you should be using `.then` to handle the transition completion and not interleaving the promises - I'll try to describe it better in my answer. – Alnitak Dec 08 '14 at 15:12