Chaining jQuery AJAX Requests Sequentially

Managing asynchronous operations in JavaScript often requires executing tasks in a strict order, where each subsequent task depends on the result of the previous one. This article demonstrates how to chain multiple jQuery AJAX requests sequentially using jQuery's Promise implementation. You will learn how to use the .then() method for static sequences, how to handle dynamic lists of requests using Array.prototype.reduce(), and how modern async/await syntax can simplify the process.

Understanding jQuery AJAX Promises

When you call $.ajax(), jQuery returns a jqXHR object. In jQuery 1.8 and later (especially modernized in jQuery 3.0+), this object implements the Promise interface. This allows you to attach callbacks using .done(), .fail(), and standard Promise methods like .then().

To run requests sequentially, a .then() callback must return a new $.ajax() call. This returns a new Promise, pausing execution of the chain until the new request resolves.

Chaining Fixed Requests with .then()

The most straightforward way to execute requests sequentially is by returning the next $.ajax() call inside the .then() handler of the preceding one.

function runSequentialRequests() {
    $.ajax({
        url: '/api/step-one',
        method: 'GET'
    })
    .then(function(responseOne) {
        console.log('Step 1 complete:', responseOne);
        // Return the second AJAX request
        return $.ajax({
            url: '/api/step-two',
            method: 'POST',
            data: { stepOneId: responseOne.id }
        });
    })
    .then(function(responseTwo) {
        console.log('Step 2 complete:', responseTwo);
        // Return the third AJAX request
        return $.ajax({
            url: '/api/step-three',
            method: 'POST',
            data: { status: responseTwo.status }
        });
    })
    .then(function(responseThree) {
        console.log('All steps completed successfully:', responseThree);
    })
    .fail(function(error) {
        // Catches an error from any request in the chain
        console.error('An error occurred during the sequence:', error);
    });
}

If any request in this chain fails, execution skips remaining .then() handlers and triggers the .fail() block immediately.

Chaining Dynamic Requests with Array.prototype.reduce

When you have an arbitrary number of requests defined in an array, you can chain them dynamically using Array.prototype.reduce(). Start with an immediately resolved Promise using $.when().

const userIds = [101, 102, 103, 104];

function fetchUsersSequentially(ids) {
    const results = [];

    return ids.reduce(function(previousPromise, currentId) {
        return previousPromise.then(function() {
            return $.ajax({
                url: `/api/users/${currentId}`,
                method: 'GET'
            }).then(function(userData) {
                results.push(userData);
            });
        });
    }, $.when()).then(function() {
        return results;
    });
}

// Usage
fetchUsersSequentially(userIds)
    .done(function(allUsers) {
        console.log('Fetched all users in order:', allUsers);
    })
    .fail(function(err) {
        console.error('Sequence stopped due to an error:', err);
    });

In this pattern, each item in the array waits for the previous Promise to resolve before initiating its own AJAX call.

Modern Alternative: async and await

In modern JavaScript environments, jQuery's jqXHR objects are "Thenables," making them directly compatible with native async/await. This provides the cleanest syntax for sequential execution.

async function runSequenceModern() {
    try {
        const stepOne = await $.ajax({ url: '/api/step-one', method: 'GET' });
        console.log('Step 1:', stepOne);

        const stepTwo = await $.ajax({ 
            url: '/api/step-two', 
            method: 'POST', 
            data: { id: stepOne.id } 
        });
        console.log('Step 2:', stepTwo);

        const stepThree = await $.ajax({ 
            url: '/api/step-three', 
            method: 'POST', 
            data: { status: stepTwo.status } 
        });
        console.log('Final Result:', stepThree);
    } catch (error) {
        console.error('Sequence failed:', error);
    }
}

Using await naturally pauses execution until each jQuery AJAX request settles, avoiding deeply nested callbacks and manual chain management.