Skip to main content

localStorage, Cookies, Fetch, AbortController, IndexedDB

localStorage, sessionStorage

Web storage objects localStorage and sessionStorage allow to save key/value pairs in the browser.
Data survives a page refresh (for sessionStorage) and even a full browser restart (for localStorage).

  • Unlike cookies, web storage objects are not sent to server with each request. Because of that, we can store much more. Most modern browsers allow at least 5 megabytes of data (or more) and have settings to configure that.
  • Also unlike cookies, the server can’t manipulate storage objects via HTTP headers. Everything’s done in JavaScript.
  • The storage is bound to the origin (domain/protocol/port triplet). That is, different protocols or subdomains infer different storage objects, they can’t access data from each other.

Both storage objects provide the same methods and properties:

  • setItem(key, value) – store key/value pair.
  • getItem(key) – get the value by key.
  • removeItem(key) – remove the key with its value.
  • clear() – delete everything.
  • key(index) – get the key on a given position.
  • length – the number of stored items.
// Storing a string
localStorage.setItem('theme', 'dark');

// Storing an object (requires JSON.stringify)
const user = { name: 'Alice', loggedIn: true };
localStorage.setItem('user', JSON.stringify(user));

// Retrieving and parsing the data
const savedTheme = localStorage.getItem('theme'); // 'dark'
const savedUser = JSON.parse(localStorage.getItem('user')); // { name: 'Alice', loggedIn: true }

console.log(savedTheme);
console.log(savedUser.name); // 'Alice'

// Removing an item
localStorage.removeItem('theme');

// Clear all data
localStorage.clear();

Cookies

Cookies are small strings of data that are stored directly in the browser.
Cookies are usually set by a web server using the response Set-Cookie HTTP header. Then, the browser automatically adds them to (almost) every request to the same domain using the Cookie HTTP header.

alert( document.cookie ); // cookie1=value1; cookie2=value2;...

Split by ; to get separate cookies.

Writing to document.cookie

We can write to document.cookie. But it’s not a data property, it’s an accessor (getter/setter). An assignment to it is treated specially.

A write operation to document.cookie updates only the cookie mentioned in it and doesn’t touch other cookies.

document.cookie = "user=John"; // update only cookie named 'user'
alert(document.cookie); // show all cookies

Cookies have many optional but really important additional text parts.

document.cookie = "username=John Doe; expires=Thu, 18 Dec 2099 12:00:00 UTC; path=/";
  • ;domain=domain
  • ;expires=date-in-UTCString-format
  • ;max-age=max-age-in-seconds
  • ;partitioned
  • ;path=path
  • ;samesite=lax/strict/none
  • ;secure
  • name prefixes
    • __Secure-
    • __Host-
    • __Http-
    • __Host-Http-

https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie

Fetch API

The Fetch API provides a JavaScript interface for making HTTP requests and processing the responses.
Fetch is promise-based and is integrated with features of the modern web such as service workers and Cross-Origin Resource Sharing (CORS).

With the Fetch API, you make a request by calling fetch(), which is available as a global function in both window and worker contexts. You pass it a Request object or a string containing the URL to fetch, along with an optional argument to configure the request.

The fetch() function returns a Promise which is fulfilled with a Response object representing the server's response. You can then check the request status and extract the body of the response in various formats, including text and JSON, by calling the appropriate method on the response.

async function getData() {
const url = "https://example.org/products.json";
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}

const result = await response.json();
console.log(result);
} catch (error) {
console.error(error.message);
}
}

Request

The first parameter of the fetch() function is “required” and it should be either a URL string or a Request object.

The second parameter of the fetch() function is the “optional” options parameter that can be customized.

Options include

  • method: GET, POST, DELETE, PUT, etc.
  • headers: Allows you to set custom headers for the request, such as Content-Type or Authorization.
  • body: Represents the request body for methods like POST or PUT. It can be a FormData object, a plain text string, or a JSON object.
  • mode: Controls the request’s CORS (Cross-Origin Resource Sharing) behavior.
  • cache: Specifies how the browser handles caching of the response.
  • signal: An AbortSignal object instance; allows you to communicate with a fetch request and abort it if desired via an AbortController.

Sending Cookies and Authentication

By default, the Fetch API does not send cookies or include authentication information. To include cookies or authentication headers, you need to set the credentials option to include. This allows you to make authenticated requests or maintain session-based authentication.

Cross-Origin Requests and CORS

CORS is a security mechanism that restricts which resources a web page can request from a different domain. You need to ensure that the server supports CORS and includes the appropriate headers (e.g., Access-Control-Allow-Origin) to allow your origin to access the resource.

Response

Once you receive the response, you can process it in various ways. Common methods available on the response object include:

  • response.json() - parses the response body as JSON and returns a promise that resolves to a JavaScript object.
  • response.text() - returns a promise that resolves to the response body as plain text.
  • response.blob(), response.arrayBuffer()

Example

// JSON object is created to define the headers.
const headers = {
"Content-Type": "application/json",
};

// JSON object and converting it to a string of body.
const body = JSON.stringify({
title: "foo",
body: "bar",
userId: 1,
});

const requestOptions = {
method: "POST",
headers,
body,
};

fetch("https://jsonplaceholder.typicode.com/posts", requestOptions)
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));

Using FormData

// body object is created using the new FormData() constructor.
const body = new FormData();
body.append("title", "foo");
body.append("body", "bar");
body.append("userId", 1);

const requestOptions = {
method: "POST",
body,
};

fetch("https://jsonplaceholder.typicode.com/posts", requestOptions)
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));

AbortController and AbortSignal

An instance of the AbortController class exposes the abort method and the signal property. Invoking the abort method emits the abort event to notify the abortable API watching the controller about the cancellation.

JavaScript is a single-threaded programming language. Depending on the runtime environment, the JavaScript engine offloads asynchronous processes, such as making network requests, file system access, and other time-consuming jobs, to some APIs to achieve asynchrony.

Ordinarily, we expect the result of an asynchronous operation to succeed or fail. However, the process can also take more time than anticipated, or you may no longer need the results when you receive them.

Setting Timeouts (using AbortController() constructor):

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);

fetch("https://jsonplaceholder.typicode.com/posts", {
signal: controller.signal,
})
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error))
.finally(() => clearTimeout(timeout));

Using AbortSignal

const signal = AbortSignal.timeout(200);
const url = "https://jsonplaceholder.typicode.com/todos/1";

const fetchTodo = async () => {
try {
const response = await fetch(url, { signal });
const todo = await response.json();
console.log(todo);
} catch (error) {
if (error.name === "AbortError") {
console.log("Operation timed out");
} else {
console.error(err);
}
}
};

fetchTodo();

Uusing multiple aborts in tandem - AbortSignal.any()

// Create two separate controllers for different concerns
const userController = new AbortController();
const timeoutController = new AbortController();

// Set up a timeout that will abort after 5 seconds
setTimeout(() => timeoutController.abort(), 5000);

// Register an event listener that can be aborted (removed) by either signal
document.addEventListener('click', handleUserClick, {
signal: AbortSignal.any([userController.signal, timeoutController.signal])
});

IndexedDB