AJAX with $.ajax()
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:
$.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:
<!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):
<!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:
// 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.
<!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
| Option | Meaning |
|---|---|
url | where to send the request |
method | "GET" (default), "POST", "PUT", "PATCH", "DELETE" |
data | object or string to send (query string for GET, body otherwise) |
dataType | expected response: "json", "text", "html", "xml", "script" |
contentType | type of the data you send, e.g. "application/json" |
headers | extra request headers, e.g. { Authorization: "Bearer ..." } |
timeout | milliseconds before giving up |
cache: false | adds 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(), orawaitthe request. - Sending an object when the server expects JSON. Without
contentTypeandJSON.stringify, jQuery sends form-encoded data. - Not checking
response.okwith 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()orfinally.
Key points
$.ajax({ url, method, data, dataType })returns a jqXHR you can chain with.done(),.fail()and.always(), orawait.$.ajax()fails on HTTP errors;fetch()resolves on them, so checkresponse.ok.- jQuery sends form-encoded data by default; for JSON set
contentTypeand useJSON.stringify(). - For GET,
databecomes the query string; with fetch useURLSearchParams. - Avoid
async: falseand JSONP; usetimeout(orAbortSignal.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
<!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()?
jQuery treats HTTP error statuses as failures; fetch only rejects for network-level problems.
2. How do you send a JSON body with $.ajax()?
dataType describes the response; contentType and JSON.stringify describe what you send.
3. Why can you write const post = await $.ajax({...})?
jqXHR objects follow the Promises/A+ rules, so await and try...catch work.
4. Which jQuery option should you never use in new or maintained code?
Synchronous requests freeze the page and are deprecated by browsers.
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
