0

I have a complicate recursive javascript function that worked very well until I needed to start using asynchronous functions (jQuery.ajax in this case). I have included some psuedo-code below that gives the gist of the situation. I would like to implement this code using promises (instead of the current callbacks), but can't work out how to do deal with the recursion and the multiple conditional ajax calls. If I understand promises correctly they allow you to act on the return of an asynchronous call, but how do I deal with the situation below?

function wrapperFunction(success,error) {
    var level = 0;
    result = [];

    var innerFunction = function(info,result,success,error) {
        level++;

        jQuery.ajax({
            url: 'baseurl1' + info,
            success: parseData,
            error: error
        });

        function parseData(data) {
            data.forEach( function(item) {
                result.push(item.something);

                if ( item.info ) {
                    innerFunction(item.info, result, success, error);
                }
            });

            if ( data.condition ) {
                jQuery.ajax({
                    url: 'baseurl2' + info,
                    success: parseData2,
                    error: error
                });
            }

            function parseData2(data2) {
                data2.forEach(function(item2) {
                    if ( item2.condition ) {
                        jQuery.ajax({
                            url: 'baseurl3' + info,
                            success: parseData3,
                            error: error
                        });
                    }
                });
                function parseData3(data3) {
                    if ( data3.condition ) {
                        jQuery.ajax({
                            url: 'baseurl4' + info,
                            success: parseData4,
                            error: error
                        });
                    }
                    function parseData4(data) {
                        result.push(data2.something + data4.something);
                    }
                }
            }

            level--;
            if (level == 0 && success) {
                success(result);
            }
        }

    }

    innerFunction('rootinfo', result, success, error);   
}   

wrapperFunction(
    function(data) {
        // process success
    },
    function(err) {
        // handle errors
    }
}

As I said already, this is only pseudo code. I initially extracted it from my real code to see if I could get my head around the actual structure. I know it works if the ajax calls where synchronous, but now they are asynchronous it only sometimes works (depending on how quickly the calls return).

How do I implement this situation using promises?

EDIT

I have just worked out how to use promises to serialise everything, and though that is one way to wait for everything to finish I rather run multiple calls in parallel and then find a way to wait for everything to finish. I know this is possible with promises, but I don't know how.

Kees
  • 95
  • 10
  • Why do you want to use promises? They offer no real benefit over what you currently have. Also note that there is no recursion here. You could probably tidy the code to make it more elegant, but that would depend on the scope required of each function and the data it retrieves. – Rory McCrossan Feb 24 '16 at 09:18
  • InnerFunction calls an ajax query, which calls parseData on success, which calls innerFunction under certain conditions. That looks like recursion to me. – Chris Lear Feb 24 '16 at 09:24
  • You can do something like this: https://jsfiddle.net/sandenay/0pa0L9dx/ – Sandeep Nayak Feb 25 '16 at 06:56
  • This way your AJAX calls would fire in parallel and then we may wait for all promises to resolve – Sandeep Nayak Feb 25 '16 at 06:57
  • @RoryMcCrossan As Chris says the innerFunction is called recusively. As for promises, I somehow need a way to wait for all ajax queries to finish before moving on. If you know of a way to do that without promises, feel free to share. – Kees Feb 25 '16 at 07:51
  • @SandeepNayak Your jsfiddle example uses the .when functionality, which would work if I knew which ajax queries would be fired, however due to loops, recursive function calling and conditional situations I have no idea beforehand how what to put in the .when – Kees Feb 25 '16 at 07:53
  • You could use a global array and push all the deferred objects within that array as and when you are creating deferred objects. Something like shown here..http://stackoverflow.com/questions/20688803/jquery-deferred-in-each-loop – Sandeep Nayak Feb 25 '16 at 09:06
  • Done a lot of thinking about this and I think the best thing is to wrap the whole of the innerFunction functionality in a promise, which gets resolved when any of the 'if' statements is negative (as there won't be any more ajax queries after that) or if 'parseData4' completes successfully. I can then store that promise in a global array as @SandeepNayak suggested, that should catch everything. The only question left then is: can I just ignore the promises when an error occurs, or should I reject them all? – Kees Feb 26 '16 at 09:53

1 Answers1

0

I have got it working, with the help of Sandeep Nayak (see comments above). I thought I post the working version of the pseudo code for anyone coming across this post with a similar problem.

I have essentially added two levels of promises (outer and inner), where the outer promise does get resolved if all inner promises are resolved. There is probably scope for tidying up the code, but it works as it stands.

function wrapperFunction(success,error) {
    var level = 0;
    var result = [];
    var outerPromises = [];

    var innerFunction = function(info,result,success,error) {
        var outerPromise = new jQuery.Deferred();
        outerPromises.push( outerPromise );

        level++;

        jQuery.ajax({
            url: 'baseurl1' + info,
            success: parseData,
            error: error
        });

        function parseData(data) {
            data.forEach( function(item) {
                result.push(item.something);

                if ( item.info ) {
                    innerFunction(item.info, result, success, error);
                }
            });

            if ( data.condition ) {
                jQuery.ajax({
                    url: 'baseurl2' + info,
                    success: parseData2,
                    error: error
                });
            } else {
                outerPromise.resolve();
            }

            function parseData2(data2) {
                var innerPromises = [];

                data2.forEach(function(item2) {
                    var innerPromise = new jQuery.Deferred();
                    innerPromises.push( innerPromise );

                    if ( item2.condition ) {
                        jQuery.ajax({
                            url: 'baseurl3' + info,
                            success: parseData3,
                            error: error
                        });
                    } else {
                        innerPromise.resolve();
                    }

                    function parseData3(data3) {
                        if ( data3.condition ) {
                            jQuery.ajax({
                                url: 'baseurl4' + info,
                                success: parseData4,
                                error: error
                            });
                        } else {
                            innerPromise.resolve();
                        }
                        function parseData4(data) {
                            result.push(data2.something + data4.something);

                            innerPromise.resolve();
                        }
                    }
                });

                jQuery.when.apply(undefined, innerPromises).then( function() { outerPromise.resolve(); } );
            }

            level--;
            if (level == 0 && success) {
                jQuery.when.apply(undefined, outerPromises).then( function() { success(result); } );
            }
        }

    }

    innerFunction('rootinfo', result, success, error);   
}   

wrapperFunction(
    function(data) {
        // process success
    },
    function(err) {
        // handle errors
    }
}
Kees
  • 95
  • 10