EthicsComputerCourses
Join training +91 90532 10052

AJAX with $.ajax()

jQuery · Lesson 17 of 36 Intermediate 6 min read Updated

AJAX (Asynchronous JavaScript and XML) means talking to a server in the background, without reloading the page. Live search suggestions, "load more" buttons and forms that submit without leaving the page all use it. The name mentions XML, but today the data is almost always JSON.

jQuery made AJAX easy long before browsers had a friendly API, and $.ajax() is still everywhere in older code. In this lesson you will use $.ajax() with its most important options, handle success and failure, and then write each request with the modern fetch() function. The examples use JSONPlaceholder, a free practice API that returns fake users, posts and to-dos.

Your first request

$.ajax() takes one settings object. The most important setting is url. It returns a jqXHR object (jQuery's wrapper around the browser's request object) which behaves like a promise:

JavaScript
$.ajax({
  url: "https://jsonplaceholder.typicode.com/users/1",
  method: "GET",          // the default
  dataType: "json",       // what you expect back; jQuery parses it for you
})
  .done(function (user) { console.log(user.name); })
  .fail(function (jqXHR, textStatus) { console.log("Failed:", jqXHR.status, textStatus); })
  .always(function () { console.log("Request finished"); });
  • .done() runs on success and receives the parsed data.
  • .fail() runs on failure: a network error, a timeout, or an HTTP error status such as 404 or 500.
  • .always() runs in both cases, which is useful for hiding a loading message.

Try it with a user id from 1 to 10. Then try 99, which does not exist:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>$.ajax() basics</title>
  <style>.error { color: #b3261e; }</style>
</head>
<body>
  <label>User id <input id="uid" type="number" value="3" min="1"></label>
  <button id="load">Load user</button>
  <p id="out">Choose an id and click Load.</p>

  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    $("#load").on("click", function () {
      const id = $("#uid").val();
      const $btn = $(this).prop("disabled", true);
      $("#out").removeClass("error").text("Loading...");

      $.ajax({
        url: "https://jsonplaceholder.typicode.com/users/" + encodeURIComponent(id),
        dataType: "json",
        timeout: 8000,                       // give up after 8 seconds
      })
        .done(function (user) {
          $("#out").text(user.name + " lives in " + user.address.city + " and works at " + user.company.name + ".");
        })
        .fail(function (jqXHR, textStatus) {
          $("#out").addClass("error").text("Could not load user " + id + " (status " + jqXHR.status + ", " + textStatus + ")");
        })
        .always(function () {
          $btn.prop("disabled", false);
        });
    });
  </script>
</body>
</html>

For id 99 the server answers with status 404, so .fail() runs. Disabling the button during the request stops people sending the same request five times.

The same request with fetch()

fetch() is built into every modern browser and returns a real Promise. The usual pattern uses async and await (see async & await):

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>fetch() version</title>
  <style>.error { color: #b3261e; }</style>
</head>
<body>
  <label>User id <input id="uid" type="number" value="3" min="1"></label>
  <button id="load">Load user</button>
  <p id="out">Choose an id and click Load.</p>

  <script>
    const out = document.querySelector("#out");

    document.querySelector("#load").addEventListener("click", async (event) => {
      const button = event.currentTarget;
      const id = document.querySelector("#uid").value;
      button.disabled = true;
      out.classList.remove("error");
      out.textContent = "Loading...";

      try {
        const response = await fetch(`https://jsonplaceholder.typicode.com/users/${encodeURIComponent(id)}`, {
          signal: AbortSignal.timeout(8000),            // like jQuery's timeout option
        });
        if (!response.ok) {                             // fetch does NOT fail on 404 by itself
          throw new Error(`status ${response.status}`);
        }
        const user = await response.json();
        out.textContent = `${user.name} lives in ${user.address.city} and works at ${user.company.name}.`;
      } catch (error) {
        out.classList.add("error");
        out.textContent = `Could not load user ${id} (${error.message})`;
      } finally {
        button.disabled = false;
      }
    });
  </script>
</body>
</html>

Sending data

Use method: "POST" (or "PUT", "PATCH", "DELETE") and put your values in data. By default jQuery sends them as a form-encoded string, the same format an HTML form uses (title=Hi&userId=1). To send JSON, you must say so with contentType and convert the object yourself:

JavaScript
// jQuery: send JSON
$.ajax({
  url: "https://jsonplaceholder.typicode.com/posts",
  method: "POST",
  contentType: "application/json",
  data: JSON.stringify({ title: "Mango season in Lucknow", body: "Dussehri is here", userId: 1 }),
  dataType: "json",
}).done((post) => console.log("Created post", post.id));

// fetch: send JSON
const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ title: "Mango season in Lucknow", body: "Dussehri is here", userId: 1 }),
});
const post = await response.json();

For a GET request, an object in data becomes the query string: data: { userId: 2 } requests ...?userId=2. With fetch, build it with URLSearchParams.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>POST with jQuery</title>
</head>
<body>
  <label>Post title <input id="title" value="Rohtak school science fair"></label>
  <button id="send">Publish</button>
  <p id="out"></p>
  <h3>Posts by user 2 (GET with data)</h3>
  <ol id="posts"></ol>

  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    $("#send").on("click", async function () {
      try {
        const post = await $.ajax({
          url: "https://jsonplaceholder.typicode.com/posts",
          method: "POST",
          contentType: "application/json",
          data: JSON.stringify({ title: $("#title").val(), body: "Projects from 40 schools", userId: 2 }),
          dataType: "json",
        });
        $("#out").text("Saved with id " + post.id + ": " + post.title);
      } catch (jqXHR) {
        $("#out").text("Error " + jqXHR.status);
      }
    });

    // GET: the data object becomes ?userId=2&_limit=3
    $.ajax({
      url: "https://jsonplaceholder.typicode.com/posts",
      data: { userId: 2, _limit: 3 },
      dataType: "json",
    }).done(function (posts) {
      $("#posts").append(posts.map((p) => $("<li>").text(p.title)));
    });
  </script>
</body>
</html>

JSONPlaceholder pretends to save the post and always returns id 101; nothing is really stored. Notice await $.ajax(...): since jQuery 3.0 the jqXHR follows the standard promise rules, so await, .then() and try...catch all work. When it fails, the value caught is the jqXHR itself, so read .status from it.

Useful options

OptionMeaning
urlwhere to send the request
method"GET" (default), "POST", "PUT", "PATCH", "DELETE"
dataobject or string to send (query string for GET, body otherwise)
dataTypeexpected response: "json", "text", "html", "xml", "script"
contentTypetype of the data you send, e.g. "application/json"
headersextra request headers, e.g. { Authorization: "Bearer ..." }
timeoutmilliseconds before giving up
cache: falseadds a timestamp to GET URLs so the browser cache is skipped

Common mistakes

  • Using the response outside the callback. const user = $.ajax(...) gives a jqXHR, not data. Use the data inside .done(), or await the request.
  • Sending an object when the server expects JSON. Without contentType and JSON.stringify, jQuery sends form-encoded data.
  • Not checking response.ok with fetch. A 404 page would be parsed as if it were your data.
  • Showing server text with .html(). API data is untrusted input; display it with .text().
  • Forgetting to re-enable buttons on failure. Put that code in .always() or finally.

Key points

  • $.ajax({ url, method, data, dataType }) returns a jqXHR you can chain with .done(), .fail() and .always(), or await.
  • $.ajax() fails on HTTP errors; fetch() resolves on them, so check response.ok.
  • jQuery sends form-encoded data by default; for JSON set contentType and use JSON.stringify().
  • For GET, data becomes the query string; with fetch use URLSearchParams.
  • Avoid async: false and JSONP; use timeout (or AbortSignal.timeout()) and handle failures every time.

Exercise

Using JSONPlaceholder's /todos endpoint, write a page with a "Load my to-dos" button. Use $.ajax() with data: { userId: 1, _limit: 5 } to load five to-dos, and show each title in a list, adding a tick (✓) before completed ones. Show "Loading..." while waiting and an error message if the request fails. Then rewrite the request part with fetch() and URLSearchParams.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My to-dos</title>
</head>
<body>
  <button id="load">Load my to-dos</button>
  <p id="msg"></p>
  <ul id="todos"></ul>
  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    $("#load").on("click", function () {
      $("#msg").text("Loading...");
      $("#todos").empty();
      $.ajax({
        url: "https://jsonplaceholder.typicode.com/todos",
        data: { userId: 1, _limit: 5 },
        dataType: "json",
      })
        .done(function (todos) {
          $("#todos").append(todos.map((t) => $("<li>").text((t.completed ? "✓ " : "") + t.title)));
          $("#msg").text(todos.length + " to-dos loaded");
        })
        .fail(function (jqXHR) {
          $("#msg").text("Could not load to-dos (status " + jqXHR.status + ")");
        });
    });

    // fetch version of the request:
    // const params = new URLSearchParams({ userId: 1, _limit: 5 });
    // const response = await fetch(`https://jsonplaceholder.typicode.com/todos?${params}`);
    // if (!response.ok) throw new Error(`status ${response.status}`);
    // const todos = await response.json();
  </script>
</body>
</html>

jQuery turns the data object into ?userId=1&_limit=5; in the fetch version URLSearchParams builds the same query string.

Quick Quiz

1. The server returns status 404. What happens with $.ajax() and with fetch()?

2. How do you send a JSON body with $.ajax()?

3. Why can you write const post = await $.ajax({...})?

4. Which jQuery option should you never use in new or maintained code?

Learn jQuery with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call