AJAX Events
Handling Asynchronous Operations: Promises and Async/Await
Section titled “Handling Asynchronous Operations: Promises and Async/Await”Modern JavaScript relies heavily on Promises and the async/await syntax to manage asynchronous operations like network requests (Fetch API), timers, and more. This provides a cleaner and more manageable alternative to older callback-based patterns.
Understanding Promises
Section titled “Understanding Promises”A Promise is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value.
A Promise can be in one of three states:
- Pending: Initial state, neither fulfilled nor rejected.
- Fulfilled: The operation completed successfully, and the Promise has a resulting value.
- Rejected: The operation failed, and the Promise has a reason for the failure (an error).
The Fetch API returns Promises. You interact with them using the .then(), .catch(), and .finally() methods.
Using .then() and .catch()
Section titled “Using .then() and .catch()”-
.then(onFulfilled, onRejected): Attaches callbacks for the fulfilled and rejected states. You typically use.then(onFulfilled)for success and chain a.catch(onRejected)for errors. -
.catch(onRejected): Attaches a callback only for the rejected state. Catches errors from the preceding Promise or any.then()callbacks in the chain. -
.finally(onFinally): Attaches a callback that executes regardless of whether the Promise was fulfilled or rejected (e.g., for cleanup).
Example using Promises with Fetch:console.log('Starting fetch...');
fetch('https://api.example.com/data') .then(response => { console.log('Received response object.'); if (!response.ok) { // If status code is not OK, create an error to be caught by .catch() throw new Error(`Network response was not ok: ${response.status}`); } return response.json(); // Returns another Promise }) .then(data => { console.log('Data parsed successfully:', data); // Update UI with data document.getElementById('content').innerText = JSON.stringify(data, null, 2); }) .catch(error => { console.error('Fetch operation failed:', error); // Display error message document.getElementById('content').innerText = `Error: ${error.message}`; }) .finally(() => { console.log('Fetch attempt finished (either success or failure).'); // Hide loading indicator, etc. });
console.log('Fetch initiated, but code continues executing...');Notice how .then() handlers are chained. Each .then() receives the result of the previous Promise or callback.
Simplifying with Async/Await
Section titled “Simplifying with Async/Await”async/await is syntactic sugar built on top of Promises, allowing you to write asynchronous code that looks and behaves more like synchronous code, making it easier to read and maintain.
-
async function: Declaring a function with theasynckeyword means it will automatically return a Promise. The resolved value of the Promise will be whatever the function returns. -
await: Theawaitoperator can only be used inside anasync function. It pauses the execution of theasync functionuntil the Promise it’s waiting for is settled (either fulfilled or rejected). If fulfilled, it returns the resolved value. If rejected, it throws the error.
The previous example rewritten using `async/await`:async function fetchData() { const contentElement = document.getElementById('content'); console.log('Starting fetch...'); try { const response = await fetch('https://api.example.com/data'); console.log('Received response object.');
if (!response.ok) { throw new Error(`Network response was not ok: ${response.status}`); }
const data = await response.json(); // Wait for JSON parsing console.log('Data parsed successfully:', data);
// Update UI with data contentElement.innerText = JSON.stringify(data, null, 2);
} catch (error) { // Errors from fetch() or thrown manually are caught here console.error('Fetch operation failed:', error); contentElement.innerText = `Error: ${error.message}`;
} finally { console.log('Fetch attempt finished (either success or failure).'); // Hide loading indicator, etc. }}
console.log('Calling fetchData...');fetchData(); // Call the async functionconsole.log('fetchData called, but code continues executing...');Key advantages of async/await:
- Readability: Looks much cleaner than nested
.then()chains. - Error Handling: Standard
try...catchblocks can be used for handling errors from awaited Promises. - Debugging: Stepping through
awaitcalls in debuggers is often more straightforward.
Legacy: XMLHttpRequest and onreadystatechange
Section titled “Legacy: XMLHttpRequest and onreadystatechange”Before Fetch and Promises, XMLHttpRequest (XHR) was the primary way to make asynchronous requests. It uses an event-based approach.
The onreadystatechange event handler is called multiple times during the request lifecycle, indicating changes in the readyState property.
| Property | Description |
|---|---|
onreadystatechange | Stores a function to be called whenever the readyState property changes. |
readyState | Holds the status of the XMLHttpRequest. Changes from 0 to 4:0: request not initialized 1: server connection established 2: request received 3: processing request 4: request finished and response is ready |
status | Holds the HTTP status code (e.g., 200 for “OK”, 404 for “Not Found”) after the request is complete (readyState is 4). |
A typical XHR pattern involved checking if readyState is 4 and status is 200 inside the onreadystatechange handler:
// --- LEGACY EXAMPLE --- Not recommended for new code ---const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { // Check if the request is complete and was successful if (xhr.readyState === 4) { if (xhr.status === 200) { // Success! console.log('XHR Success:', xhr.responseText); document.getElementById('myDiv').innerHTML = xhr.responseText; } else { // Error (e.g., 404, 500) console.error('XHR Error:', xhr.status, xhr.statusText); document.getElementById('myDiv').innerHTML = 'Error loading data.'; } }};
// Configure the request (GET request to 'data.txt')xhr.open('GET', 'data.txt', true); // true makes it asynchronous
// Optional: Handle network errorsxhr.onerror = function() { console.error('XHR Network Error occurred');};
// Send the requestxhr.send();While you might encounter XHR in older codebases, the Fetch API with Promises or async/await is the standard for new development due to its cleaner syntax and better integration with modern JavaScript features.