2

I need to run a series of calls over websockets via Socket.IO (client-side). Since I'm not using $.ajax, jQuery's deferred functions won't integrate as well and I'll have to manually handle promises. With every websocket call, I pass a callback and I'm quickly seeing how this project could spiral out of control. Here's a simplified example of how my websocket calls work (excluding all connection handling code):

function js2node(nodeFunction, data, callback){
    socket.emit('incoming', nodeFunction, data, callback);
}

function sampleServerCall(){
    js2node('sampleCall', 'something', 'sampleCallback');
}

function sampleCallback(json){
    // Handle data
}

sampleServerCall();

I will be talking to the server quite a bit, all calls will be asynchronous, but some will need to come back in a specific order. Enter jQuery deferred. Here is some working code:

var deferredArray = [];

$(function(){
    $.when(  // Any order
        getData1(),
        getData2()
    ).then(function(){  // Must have responses from dataCallback1 and dataCallback2 before doing this...
        $.when(  // Any order
            getData3(),
            getData4()
        ).then(function(){  // Must have responses from dataCallback3 and dataCallback4 before doing this...
            getData5();
        });
    });
});

function getData1(){
    js2node('data1', 'something', 'dataCallback1');
    deferredArray[0] = new $.Deferred();
    return deferredArray[0].promise();
}

function getData2(){
    js2node('data2', 'something', 'dataCallback2');
    deferredArray[1] = new $.Deferred();
    return deferredArray[1].promise();
}

function getData3(){
    js2node('data3', 'something', 'dataCallback3');
    deferredArray[2] = new $.Deferred();
    return deferredArray[2].promise();
}

function getData4(){
    js2node('data4', 'something', 'dataCallback4');
    deferredArray[3] = new $.Deferred();
    return deferredArray[3].promise();
}

function getData5(){
    js2node('data5', 'something', 'dataCallback5');
    deferredArray[4] = new $.Deferred();
    return deferredArray[4].promise();
}

function dataCallback1(json){
    // Handle data
    deferredArray[0].resolve();
}

function dataCallback2(json){
    // Handle data
    deferredArray[1].resolve();
}

function dataCallback3(json){
    // Handle data
    deferredArray[2].resolve();
}

function dataCallback4(json){
    // Handle data
    deferredArray[3].resolve();
}

function dataCallback5(json){
    // Handle data
    deferredArray[4].resolve();
}

As you can see, I'm still stuck with nested callbacks from the way I'm using when/then and nesting could potentially go deeper as I add functionality. Deferred is a new concept to me but I've read it's supposed to help in situations such as this. I feel like there has to be a better way than what I'm currently doing. Can anyone help me set this up more efficiently?

CauselessEffect
  • 415
  • 5
  • 11
  • 1
    Take a look at http://stackoverflow.com/a/20326123/461055, the answer gives an example of how to execute deferred objects in order. – Sumit Dec 04 '13 at 20:10
  • @Sumit, thanks, I'll have to play around with that some. I think I got what I need from the answers below but this should help too! – CauselessEffect Dec 05 '13 at 13:39

2 Answers2

6

You can do more with .then:

$(function(){
    $.when(
        doSock('data1', 'something'),
        doSock('data2', 'something')
    ).then(function(data1, data2){
        return $.when(
            doSock('data3', 'something'),
            doSock('data4', 'something')
        );
    }).then(function(data3, data4){
        return doSock('data5', 'something');
    });
});

That way your nesting never goes deeper than that.

(i used adeneo's helper method)

Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • +1, indeed, returning the promise from the $.when method is one way to go. – adeneo Dec 04 '13 at 20:12
  • Great, this resolves the nesting issue and should help the team better follow my intentions! – CauselessEffect Dec 05 '13 at 13:33
  • Unfortunately, this is not working as expected. The "doSock" call within the 2nd **.then** (data5) is executing before the resolve is actually given from the functions in the 2nd **$.when** (data3 and data4). As a matter of fact, the variables received in the 2nd **.then** function (data3, data4) are actually the same data from the first **.then** function (data1, data2)! Sorry, I'm sure this is confusing to read. – CauselessEffect Dec 06 '13 at 13:59
  • It seems to be working for me, when i substitute the socket request with a setTimeout: http://jsfiddle.net/PpZkd/ – Kevin B Dec 09 '13 at 16:34
  • Here it is with a longer delay so you can see that they are waiting properly: http://jsfiddle.net/PpZkd/1/ – Kevin B Dec 09 '13 at 16:34
  • I have been driving myself up the wall trying to figure this problem out! Finally realized it's the version of jQuery I'm using. If you go to your jsfiddle project and change jQuery to v1.7.2, you'll see what I'm talking about. Of course the project I'm working on hasn't updated jQuery since then! Now I have to convince the team to upgrade. Thanks for taking the time to make those examples for me. I appreciate your help getting this working! – CauselessEffect Dec 10 '13 at 20:02
  • 1
    Ah, simply replace `.then` with `.pipe`. don't forget to go back to `.then` when you upgrade to 1.8 http://jsfiddle.net/PpZkd/2/ – Kevin B Dec 10 '13 at 20:03
3

Using a better helper function sure would help, but you'd still have to structure the calls with $.when and $.then to execute them in the proper order

function doSock(nodeFunction, data) {
    var def = new $.Deferred();
    socket.emit('incoming', nodeFunction, data, function(received) {
        def.resolve(received)
    });
    return def.promise();
}

$(function(){
    $.when(
        doSock('data1', 'something'),
        doSock('data2', 'something')
    ).then(function(data1, data2){
        $.when(
            doSock('data3', 'something'),
            doSock('data4', 'something')
        ).then(function(data3, data4){
            doSock('data5', 'something');
        });
    });
});
adeneo
  • 312,895
  • 29
  • 395
  • 388
  • Thanks, that helper function will work nicely to clean up a lot of the redundancy mess! I will definitely be using a combo of your answer and Kevin's answer. Despite cleaning up my code better, I think I have to accept Kevin's for countering the nesting situation. Grr, wish I could accept both! I really appreciate your help. – CauselessEffect Dec 05 '13 at 13:31
  • After implementing this, I do have a question. In the line with the first instance of "then", is there any sort of guarantee variable "data1" will come from the doSock call "data1"? In other words, will whatever variables I define within the then function ALWAYS be in the same order I define list the functions (not necessarily when they finish)? This seems to be the case but I just want to make sure it's safe to rely on that assumption. – CauselessEffect Dec 05 '13 at 15:37
  • 1
    Yes, the order of the data coming back as arguments in $.then is always the same as the order of doSock functions etc. It's done this way so you know what result comes where, otherwise it would be hard to manage. – adeneo Dec 05 '13 at 16:34