JavaScript › Asynchronous JavaScript
Promises, async/await, and fetch
A lot of what JavaScript does takes time — fetching data from a server, waiting for a response. JavaScript handles this without freezing the page, using promises and the async/await syntax built on them. For a tester, the key payoff is fetch: it’s how the client calls APIs, so reading fetch calls in a bundle reveals every endpoint the app talks to.
You'll learn to
- Understand what a promise represents
- Read and write async/await code
- Find API calls in code via fetch
Promises — a value that isn’t ready yet
A promise represents a result that will arrive later — like a receipt you exchange for a value once it’s ready.
fetch("https://api.site.com/users") // returns a promise immediately
.then(response => response.json()) // when it arrives, parse JSON
.then(data => console.log(data)) // then use the data
.catch(error => console.log(error)); // or handle a failure
.then() runs when the promise succeeds; .catch() runs if it fails. You chain .then() calls to handle each step. This is the older style, and you’ll still see it constantly in real code.
async/await — the modern, readable style
async/await is syntax that makes promise code read like ordinary top-to-bottom code:
async function getUsers() {
try {
const response = await fetch("https://api.site.com/users");
const data = await response.json();
console.log(data);
} catch (error) {
console.log("failed:", error);
}
}
await pauses the function until the promise resolves, then gives you the result. It does the same thing as the .then() chain, but reads like normal sequential code. An await can only be used inside an async function. This is the dominant modern style.
Reading a real API call
async function deleteUser(id) {
const res = await fetch(`/api/users/${id}`, {
method: "DELETE",
headers: { "Authorization": "Bearer " + token },
});
return res.ok;
}
Read it as a tester: the endpoint is /api/users/{id}, the method is DELETE, and it sends a bearer token. That single function tells you an endpoint exists, what method it uses, and how it authenticates — everything you’d want to know to test it for authorization flaws. The frontend is documenting the backend for you.
Checkpoint
You see this in a bundle: await fetch('/api/admin/users', { method: 'POST', ... }). What three useful facts does this single line tell a tester?
It reveals an endpoint (/api/admin/users), the method it accepts (POST), and that the client calls it — so it exists and is reachable. The 'admin' in the path also flags it as a high-value target worth testing for authorization, since a normal user reaching an admin endpoint would be a finding.
Try it yourself
In the console on any site, open the Network tab, reload, and watch the fetch/XHR requests appear. Click one to see its URL, method, and response. Then in the console, try a simple await fetch to a public API (inside an async function or using top-level await) and inspect the response object.
Summary
A promise represents a future result; .then()/.catch() handle success and failure, and async/await is the modern syntax that makes the same logic read like sequential code (await pauses until the promise resolves, inside an async function). fetch is how the client calls APIs, so reading fetch calls in a bundle maps the app’s endpoints, methods, and auth — and the Network tab shows what actually fired. Forgetting await leaves you holding a pending promise instead of data.
Key takeaways
- A promise is a value that arrives later;
.then/.catchorasync/awaithandle it. await(insideasync) pauses for the result and reads like normal code.fetch(calls in a bundle reveal endpoints, methods, and auth — a recon goldmine.- A missing
awaitleaves you with a pending promise, not the data.
Quick quiz
Next module, the security deep dive begins — starting with the full cross-site scripting family built on the DOM knowledge you already have.