NexusCS

AJAX

JavaScript
Asynchronous JavaScript And XML for making HTTP requests in the browser
featured

Getting started

XMLHttpRequest (XHR)

const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data");
xhr.onload = function () {
  console.log(xhr.responseText);
};
xhr.onerror = function () {
  console.error("Request failed");
};
xhr.send();

Fetch API

// GET request
fetch("/api/data")
  .then((response) => response.json())
  .then((data) => console.log(data));

// POST request
fetch("/api/data", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ key: "value" }),
});

Async/await pattern

async function fetchData() {
  try {
    const response = await fetch("/api/data");
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error.message);
  }
}

Fetch API

Request options

fetch(url, {
  method: "POST", // HTTP method
  headers: {
    // Request headers
    "Content-Type": "application/json",
    Authorization: "Bearer token",
  },
  body: JSON.stringify(data), // Request body
  mode: "cors", // cors, no-cors, same-origin
  credentials: "include", // omit, same-origin, include
  cache: "no-cache", // default, no-cache, reload, force-cache
  redirect: "follow", // follow, error, manual
});

Response methods

response.json(); // Parse JSON
response.text(); // Get as text
response.blob(); // Get as Blob
response.formData(); // Get as FormData
response.arrayBuffer(); // Get as ArrayBuffer

Response properties

response.ok; // true if 200-299
response.status; // HTTP status code
response.statusText; // Status message
response.headers; // Headers object
response.url; // Response URL
response.redirected; // true if redirected

Abort requests

const controller = new AbortController();

fetch(url, {
  signal: controller.signal,
});

// Cancel request
controller.abort();

XMLHttpRequest

Basic XHR

const xhr = new XMLHttpRequest();

// Configure request
xhr.open("GET", "/api/data");

// Set headers
xhr.setRequestHeader("Content-Type", "application/json");

// Event handlers
xhr.onload = () => console.log(xhr.responseText);
xhr.onerror = () => console.error("Failed");

// Send request
xhr.send();

POST with JSON

const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/data");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = function () {
  if (xhr.status === 200) {
    console.log(JSON.parse(xhr.responseText));
  }
};
xhr.send(JSON.stringify({ key: "value" }));

ReadyState values

Value Constant Description
0 UNSENT open() not called
1 OPENED open() called
2 HEADERS_RECEIVED Headers available
3 LOADING Downloading
4 DONE Complete

Event handlers

xhr.onreadystatechange = () => {}; // State changes
xhr.onload = () => {}; // Success
xhr.onerror = () => {}; // Network error
xhr.onprogress = (e) => {}; // Download progress
xhr.ontimeout = () => {}; // Timeout
xhr.onabort = () => {}; // Aborted

Timeout

xhr.timeout = 5000; // 5 seconds
xhr.ontimeout = () => {
  console.error("Request timed out");
};

Progress tracking

// Upload progress
xhr.upload.onprogress = (e) => {
  const percent = (e.loaded / e.total) * 100;
  console.log(`Upload: ${percent}%`);
};

// Download progress
xhr.onprogress = (e) => {
  const percent = (e.loaded / e.total) * 100;
  console.log(`Download: ${percent}%`);
};

jQuery AJAX

$.ajax()

$.ajax({
  url: "/api/data",
  type: "POST", // GET, POST, PUT, DELETE
  data: { key: "value" }, // Data to send
  dataType: "json", // Expected response type
  success: function (data) {
    console.log(data);
  },
  error: function (jqXHR, textStatus, errorThrown) {
    console.error(errorThrown);
  },
  complete: function () {
    console.log("Request complete");
  },
});

Promise style

$.ajax({ url: "/api/data" })
  .done((data) => console.log(data))
  .fail((error) => console.error(error))
  .always(() => console.log("Complete"));

Shorthand methods

$.get(url, data, success, dataType);
$.post(url, data, success, dataType);
$.getJSON(url, data, success);

Global settings

$.ajaxSetup({
  headers: { Authorization: "Bearer token" },
  timeout: 5000,
});

Axios

Basic requests

// GET
axios.get(url).then((res) => console.log(res.data));

// POST
axios.post(url, data).then((res) => console.log(res.data));

// PUT
axios.put(url, data);

// DELETE
axios.delete(url);

Config object

axios({
  method: "post",
  url: "/api/data",
  data: { key: "value" },
  headers: { Authorization: "Bearer token" },
  timeout: 5000,
  responseType: "json",
});

Interceptors

// Request interceptor
axios.interceptors.request.use(
  (config) => {
    config.headers.Authorization = "Bearer token";
    return config;
  },
  (error) => Promise.reject(error),
);

// Response interceptor
axios.interceptors.response.use(
  (response) => response,
  (error) => {
    console.error(error.response.status);
    return Promise.reject(error);
  },
);

Create instance

const api = axios.create({
  baseURL: "https://api.example.com",
  timeout: 5000,
  headers: { "X-Custom-Header": "value" },
});

api.get("/users"); // https://api.example.com/users

Error handling

axios.get(url).catch((error) => {
  if (error.response) {
    // Server responded with error
    console.log(error.response.status);
    console.log(error.response.data);
  } else if (error.request) {
    // No response received
    console.log(error.request);
  } else {
    // Setup error
    console.log(error.message);
  }
});

// Check if Axios error
if (axios.isAxiosError(error)) {
  // Handle Axios-specific error
}

Common patterns

Sending JSON

// Fetch
fetch(url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(data),
});

// XHR
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(data));

// Axios (automatic)
axios.post(url, data);

// jQuery
$.ajax({
  url,
  type: "POST",
  contentType: "application/json",
  data: JSON.stringify(data),
});

FormData / File upload

const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("name", "filename");

// Fetch (no Content-Type!)
fetch(url, {
  method: "POST",
  body: formData,
});

// Axios
axios.post(url, formData);

// jQuery
$.ajax({
  url,
  type: "POST",
  data: formData,
  processData: false,
  contentType: false,
});

⚠️ Don't set Content-Type for FormData — browser sets boundary

Authorization headers

// Fetch
fetch(url, {
  headers: { Authorization: "Bearer token" },
});

// XHR
xhr.setRequestHeader("Authorization", "Bearer token");

// Axios (default)
axios.defaults.headers.common["Authorization"] = "Bearer token";

// jQuery (global)
$.ajaxSetup({
  headers: { Authorization: "Bearer token" },
});

Timeout handling

// Fetch (AbortController)
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
fetch(url, { signal: controller.signal });

// XHR
xhr.timeout = 5000;
xhr.ontimeout = () => console.error("Timeout");

// Axios
axios.get(url, { timeout: 5000 });

// jQuery
$.ajax({ url, timeout: 5000 });

CORS with credentials

// Fetch
fetch(url, { credentials: "include" });

// XHR
xhr.withCredentials = true;

// Axios
axios.get(url, { withCredentials: true });

// jQuery
$.ajax({
  url,
  xhrFields: { withCredentials: true },
});

Parallel requests

// Promise.all with fetch
Promise.all([fetch("/api/users"), fetch("/api/posts"), fetch("/api/comments")])
  .then((responses) => Promise.all(responses.map((r) => r.json())))
  .then(([users, posts, comments]) => {
    console.log({ users, posts, comments });
  });

// Axios with Promise.all
Promise.all([axios.get("/api/users"), axios.get("/api/posts")]).then(
  ([usersRes, postsRes]) => {
    console.log(usersRes.data, postsRes.data);
  },
);

Advanced techniques

Streaming responses

const response = await fetch(url);
const reader = response.body.getReader();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const chunk = new TextDecoder().decode(value);
  console.log(chunk);
}

Progress tracking

// Axios with progress
axios.post(url, data, {
  onUploadProgress: (progressEvent) => {
    const percent = Math.round(
      (progressEvent.loaded * 100) / progressEvent.total,
    );
    console.log(`Upload: ${percent}%`);
  },
  onDownloadProgress: (progressEvent) => {
    const percent = Math.round(
      (progressEvent.loaded * 100) / progressEvent.total,
    );
    console.log(`Download: ${percent}%`);
  },
});

Retry logic

async function fetchWithRetry(url, options = {}, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url, options);
      if (response.ok) return response;
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

Request caching

const cache = new Map();

async function cachedFetch(url) {
  if (cache.has(url)) {
    return cache.get(url);
  }

  const response = await fetch(url);
  const data = await response.json();
  cache.set(url, data);
  return data;
}

CORS

Client-side configuration

fetch(url, {
  mode: "cors", // cors, no-cors, same-origin
  credentials: "include", // include cookies
});

Required server headers

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true

Preflight requests

// These trigger preflight OPTIONS
fetch(url, {
  method: "PUT", // Non-simple method
  headers: {
    "X-Custom-Header": "value", // Custom header
  },
});

⚠️ Custom headers/non-simple methods trigger preflight OPTIONS

HTTP status codes

Success codes

Code Meaning
200 OK
201 Created
204 No Content

Redirect codes

Code Meaning
301 Moved Permanently
302 Found (temporary)
304 Not Modified

Client error codes

Code Meaning
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
429 Too Many Requests

Server error codes

Code Meaning
500 Internal Server Error
502 Bad Gateway
503 Service Unavailable

Error handling

Fetch errors

fetch(url)
  .then((response) => {
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return response.json();
  })
  .catch((error) => {
    if (error.name === "AbortError") {
      console.log("Request aborted");
    } else if (error.name === "TypeError") {
      console.log("Network error");
    } else {
      console.error(error.message);
    }
  });

⚠️ fetch() doesn't reject on 404/500 — must check response.ok

Axios errors

axios.get(url).catch((error) => {
  if (error.response) {
    // Server error (4xx, 5xx)
    console.error(error.response.status);
    console.error(error.response.data);
  } else if (error.request) {
    // No response received
    console.error("No response");
  } else {
    // Request setup error
    console.error(error.message);
  }
});

jQuery errors

$.ajax({ url }).fail((jqXHR, textStatus, errorThrown) => {
  console.error(`${textStatus}: ${errorThrown}`);
  console.error(jqXHR.responseText);
});

⚠️ jQuery rejects on HTTP errors, fetch doesn't

Gotchas

Common pitfalls

⚠️ fetch() doesn't reject on HTTP errors — Always check response.ok or response.status

⚠️ Don't set Content-Type for FormData — Browser automatically sets it with boundary parameter

⚠️ CORS preflight — Custom headers and non-simple methods trigger OPTIONS preflight

⚠️ Axios auto-transforms JSON — Sets Content-Type and parses responses automatically

⚠️ fetch() vs jQuery — jQuery rejects on HTTP errors, fetch only rejects on network failures

⚠️ Credentials not sent by default — Use credentials: 'include' or withCredentials: true

⚠️ Timeout in fetch — No built-in timeout, must use AbortController with setTimeout

⚠️ Progress events — Not available in fetch() for upload, use XHR or Axios

Also see