How jQuery .always() Works with Promises

This article explains how the jQuery .always() method functions within jQuery's Deferred and Promise architecture. You will learn the purpose of .always(), how it handles resolved and rejected states, what arguments it receives, and how it compares to native JavaScript promise methods for managing cleanup tasks.

What is the jQuery .always() Method?

The .always() method is a callback handler designed for jQuery Deferred and Promise objects, including the jqXHR objects returned by $.ajax(). It allows you to specify a function that executes whenever a promise settles—regardless of whether it was fulfilled (resolved) or failed (rejected).

This behavior makes .always() the ideal location for cleanup logic, such as removing loading spinners, resetting UI states, or closing connections.

Syntax and Basic Usage

The method accepts one or more functions or arrays of functions as arguments:

promise.always(function(arg1, arg2, arg3) {
    // Code that executes regardless of success or failure
});

Example with $.ajax()

In an asynchronous request, .always() runs after either .done() or .fail() handlers finish executing:

$("#loading-spinner").show();

$.ajax({
    url: "/api/data",
    method: "GET"
})
.done(function(data) {
    console.log("Request succeeded:", data);
})
.fail(function(jqXHR, textStatus, errorThrown) {
    console.error("Request failed:", textStatus);
})
.always(function() {
    // This runs no matter the outcome
    $("#loading-spinner").hide();
});

Arguments Passed to .always()

Unlike native JavaScript's Promise.prototype.finally(), which receives no arguments, jQuery's .always() passes the outcome arguments directly to your callback.

The exact arguments received depend on how the promise settled:

  1. When Resolved: The callback receives the exact arguments passed to .done(). For an AJAX call, this is:

    • data: The data returned from the server.
    • textStatus: A string categorizing the status (e.g., "success").
    • jqXHR: The jQuery XMLHttpRequest object.
  2. When Rejected: The callback receives the exact arguments passed to .fail(). For an AJAX call, this is:

    • jqXHR: The jQuery XMLHttpRequest object.
    • textStatus: A string categorizing the error (e.g., "timeout", "error").
    • errorThrown: An optional exception object or textual HTTP status.

Because the argument types differ depending on the outcome, checking the argument types is necessary if you intend to inspect them inside .always():

$.get("/api/user").always(function(firstArg, textStatus, thirdArg) {
    if (textStatus === "success") {
        // firstArg is response data
        console.log("Success payload:", firstArg);
    } else {
        // firstArg is the jqXHR object
        console.log("Status code:", firstArg.status);
    }
});

Differences Between .always() and Native .finally()

While .always() predates native ES6 promises, it serves a similar role to native Promise.prototype.finally(), with two notable differences:

  • Arguments: jQuery .always() receives resolution or rejection parameters. Native .finally() receives no arguments because it is strictly meant for guaranteed execution regardless of state.
  • Return Values: In standard native promises, .finally() returns a new promise that resolves to the original value (unless the finally handler throws an error or returns a rejected promise). jQuery's .always() returns the original Deferred or Promise object to maintain jQuery-style method chaining.