0

I'm trying to use a function to initialize datas with 3 ajax calls, and when this function is done I want to do something with the datas. I have to wait for the 3 ajax calls to be done before I can return a promise. The thing is, if I wait for the 3 ajax calls to be done (with $.when (ajax1, ajax2, ajax3).done(...)), I get the error "Cannot read property 'then' of undefined"

Here is my code

function myInit() {
    initData().then(function() {
        console.log("a");
    });
}

function initData() {
    var results = {
        categories: [],
        commandes: [],
        users: []
    };
    var ajaxCategories = $.ajax({
        url: url + '/categorie/getAll',
        success: function(data) {
            results.categories = data;
        }
    });

    var ajaxCommandes = $.ajax({
        url: url + '/statistique/getSimpleCommandes',
        success: function(data) {
            results.commandes = data;
        }
    });
    var ajaxUsers = $.ajax({
        url: url + '/statistique/getSimpleUsers',
        success: function(data) {
            results.users = data;
        }
    });
    $.when(ajaxUsers, ajaxCommandes, ajaxCategories)
        .done(function() {
            results.commandes.forEach(function(commande) {
                results.users.forEach(function(user) {
                    if (user.id == commande.user)
                        commande.user = user.nom;
                });
            });
            return $.Deferred().resolve();
        });
}

I return a promise in the $.when. If I move this return outside of the when, it works fine, but then I risk getting out of the function and continuing my code with the datas not loaded. Where is my mistake?

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Percee
  • 449
  • 5
  • 25
  • 1
    I don't see any `return` in `initData` - `initData()` does *not* return a promise. – Bergi May 19 '15 at 13:15
  • You'll need to use `.then` not `.done` if you want to `return` something from the callback. – Bergi May 19 '15 at 13:16
  • When asking for help, please take the time to ensure your code is readably and reasonably formatted. The above is not. *Edit*: *Was* not, I've fixed it for you. – T.J. Crowder May 19 '15 at 13:18
  • Thanks, I actually wondered why it was already fixed when I tried to edit it – Percee May 19 '15 at 13:20

2 Answers2

0

Returning a promise from the callback for done doesn't make the initData function return anything.

Create the promise outside the $.when call so that you can resolve it in the callback and return it from initData:

var promise = $.Deferred();
$.when(ajaxUsers, ajaxCommandes, ajaxCategories)
    .done(function(){
        results.commandes.forEach(function(commande){
            results.users.forEach(function(user){
                if (user.id == commande.user)
                    commande.user = user.nom;
            });
        });
        promise.resolve();
    });
return promise.promise();

Alternatively, return the promise that done returns:

return $.when(ajaxUsers, ajaxCommandes, ajaxCategories)
    .done(function(){
        results.commandes.forEach(function(commande){
            results.users.forEach(function(user){
                if (user.id == commande.user)
                    commande.user = user.nom;
            });
        });
    });

The handlers added using the done method are called in the order that they are added, so any handlers that the caller adds will run after your handler.

If you want to pass on data from your done handler in the promise that you return, you send it in the resolve call:

var promise = $.Deferred();
$.when(ajaxUsers, ajaxCommandes, ajaxCategories)
    .done(function(){
        results.commandes.forEach(function(commande){
            results.users.forEach(function(user){
                if (user.id == commande.user)
                    commande.user = user.nom;
            });
        });
        promise.resolve(results);
    });
return promise.promise();
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • `return promise;` => `return promise.promise();` We don't want to expose the full `Deferred` API to the caller. Of course, `$.when` returns a promise already. – T.J. Crowder May 19 '15 at 13:19
  • 1
    https://stackoverflow.com/questions/23803743/what-is-the-deferred-antipattern-and-how-do-i-avoid-it – Bergi May 19 '15 at 13:20
  • Thanks, it works. I read in the link provided by Bergi that Deferred should only be used when I have no other choice. Is there a better way to do what I'm trying to do in this piece of code, without using $.Deferred directly? – Percee May 19 '15 at 13:26
  • @Percee: You can also use the promise that the `$.when` method creates, and the `done` method returns. – Guffa May 19 '15 at 13:32
-1

initData() does not return a promise, as there is no return statement in that function. You'll need to return the promise that $.when gives you.

Also to chain an action and return something from the callback, you'll need to use .then not .done:

function initData() {
    var results = {
        categories: [],
        commandes: [],
        users: []
    };
    var ajaxCategories = $.ajax({
        url: url + '/categorie/getAll',
        success: function(data) {
            results.categories = data;
        }
    });

    var ajaxCommandes = $.ajax({
        url: url + '/statistique/getSimpleCommandes',
        success: function(data) {
            results.commandes = data;
        }
    });
    var ajaxUsers = $.ajax({
        url: url + '/statistique/getSimpleUsers',
        success: function(data) {
            results.users = data;
        }
    });
    return $.when(ajaxUsers, ajaxCommandes, ajaxCategories)
//  ^^^^^^
    .then(function() {
//   ^^^^
        results.commandes.forEach(function(commande) {
            results.users.forEach(function(user) {
                if (user.id == commande.user)
                    commande.user = user.nom;
            });
        });
        return results; // I guess that is what you wanted
    });
}

You might even further advance this by not using a global results variable, but make it local to your callback:

function initData() {
    return $.when(
        $.get(url + '/categorie/getAll'), 
        $.get(url + '/statistique/getSimpleCommandes'),
        $.get(url + '/statistique/getSimpleUsers')
    ).then(function(categoriesRes, commandesRes, userRes) {
        var results = {
            categories: categoriesRes[0],
            commandes: commandesRes[0],
            users: userRes[0]
        };
        results.commandes.forEach(function(commande) {
            results.users.forEach(function(user) {
                if (user.id == commande.user)
                    commande.user = user.nom;
            });
        });
        return results;
    });
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • How can I use the value returned by then? I don't quite understand if it returns a promise or my results – Percee May 19 '15 at 13:40
  • No, you don't need to use `.then` instead of `.done`. In your example `.then` does exactly the same as `.done`. – Guffa May 19 '15 at 13:41
  • @Percee: It returns a promise for the results - they will be available as an argument to further callbacks. for example `initData.then(function(res) { console.log(res.commandes); })` – Bergi May 19 '15 at 13:42
  • @Guffa: Since I `return` from my callback it does indeed make a large difference. You can try it out yourself, or have a look at https://stackoverflow.com/questions/5436327/jquery-deferreds-and-promises-then-vs-done – Bergi May 19 '15 at 13:43
  • @Bergi: Neat, you're right. I won't have to stock it in window.data now :) – Percee May 19 '15 at 13:47
  • @Percee: Yes, that how promises are supposed to work - no global data variables of unknown state any more :-) – Bergi May 19 '15 at 13:49
  • @Bergi: What do you think is the difference between `.done(callback)` and `.then(callback)`? – Guffa May 19 '15 at 14:03
  • @Guffa: The result. `done` returns the original promise, while `.then(callback)` returns a new promise that resolves with the result of the `callback`. – Bergi May 19 '15 at 14:20
  • https://stackoverflow.com/questions/23803743/what-is-the-deferred-antipattern-and-how-do-i-avoid-it – Guffa May 19 '15 at 16:38
  • @Guffa: Yes, I know that antipattern ([obviously](http://stackoverflow.com/a/25569299/1048572)). Why did you point me to it? – Bergi May 19 '15 at 22:55
  • @Bergi: Because you are recommending it. – Guffa May 19 '15 at 23:02
  • @Guffa: Yes, I recommend to avoid it, but I didn't link it because I don't think the OP intended to use it. – Bergi May 19 '15 at 23:05
  • @Bergi: No, you are recommending to use it. – Guffa May 19 '15 at 23:13
  • @Guffa: Where would I do so? I almost never use deferreds. – Bergi May 19 '15 at 23:18