Palmi
Palmi

Reputation: 2521

AJAX calls are not executed on server side in same order as called on client side

I have a ASP.NET MVC application with following javascript function in a view:

function OnAmountChanged(s, fieldName, keyValue, url) {
    console.log("Get Number: " + s.GetNumber());
    console.log(" ");
        $.ajax({
            type: "POST",
            url: url,
            data: { key: keyValue, field: fieldName, value: s.GetNumber() },
            success: function () {
                reloadShoppingCartSummary();
            }
        });
}

With a up / down button I can increase / decrease the number of my field which calls this function for every click. With the console.log I can see that the JavaScript function is called in the correct order (the order I changed the number value). But by debugging on server side I noticed that the order it executes the calls is different. What can cause this problem? And how can I solve or workaround this problem?

Upvotes: 0

Views: 211

Answers (1)

jfriend00
jfriend00

Reputation: 708206

This is not surprising at all and not something you can expect. If you want requests to be processed in a specific order, then you have to send one request, wait for it's response, then send the next. You can't just send all of them and expect them to be processed in a specific order.

In your specific case, if a request is "in-flight", you would have to either disable the button or you would queue up the next request and play it back when the previous request finishes.

There are also various strategies for how you send the data that can prevent, adapt to or detect out of sequence issues, but which to use and how to do it depends upon the specific data you're sending and how it works on both the client and server side of things.

There are many possible coding strategies for dealing with this issue. Here's one method that queues any requests that come in while another request is in process, forcing them to be processed in order by the server:

var changeQueue = [];
changeQueue.inProcess = false;
function OnAmountChanged(s, fieldName, keyValue, url) {
    // if already processing a request, then queue this one until the current request is done
    if (changeQueue.inProcess) {
        changeQueue.push({s:s, fieldName: fieldName, keyValue: keyValue, url: url});
    } else {
        changeQueue.inProcess = true;
        console.log("Get Number: " + s.GetNumber());
        console.log(" ");

            $.ajax({
                type: "POST",
                url: url,
                data: { key: keyValue, field: fieldName, value: s.GetNumber() },
                success: function () {
                    reloadShoppingCartSummary();
                }, complete: function() {
                    changeQueue.inProcess = false;
                    if (changeQueue.length) {
                        var next = changeQueue.shift();
                        OnAmountChanged(next.s, next.fieldName, next.keyValue, next.url);
                    }
                }
            });
        }
}

FYI, I think we can probably get promises to do the queuing work for us, but I'm working out some details for doing that. Here's one idea that's in process: http://jsfiddle.net/jfriend00/4hfyahs3/. If you press the button multiple times rapidly, you can see that it queues the presses and processes them in order.


Here's a specific idea for a generic promise serializer:

// utility function that works kind of like `.bind()`
// except that it works only on functions that return a promise
// and it forces serialization whenever the returned function is called
// no matter how many times it is called in a row
function bindSingle(fn) {
    var p = Promise.resolve();

    return function(/* args */) {
        var args = Array.prototype.slice.call(arguments, 0);

        function next() {
            return fn.apply(null, args);
        }

        p = p.then(next, next);
        return p;
    }
}

function OnAmountChanged(s, fieldName, keyValue, url) {
    console.log("Get Number: " + s.GetNumber());
    console.log(" ");
    return $.ajax({
        type: "POST",
        url: url,
        data: { key: keyValue, field: fieldName, value: s.GetNumber() }
    }).then(reloadShoppingCartSummary);
}

var OnAmountChangedSingle = bindSingle(OnAmountChanged);

So, to use this code, you would then pass OnAmountChangedSingle to your event handler instead of OnAmountChanged and this will force serialization.

Upvotes: 4

Related Questions