Most code runs top to bottom, one line after another. Asynchronous (async) code does not: it starts something slow, like a timer or loading data from a server, and runs the rest later, when that finishes. That makes its bugs feel invisible, because nothing seems to happen, or it happens in a strange order. This last lesson explains just enough about async code to debug it.
You met this in JS Timers. A setTimeout callback waits, even with a delay of 0, until the rest of the script has finished:
console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
That is the first rule of async debugging: the order the code is written is not the order it runs.
A Promise is an object that stands for a value that will arrive later. You attach what to do with the value using .then(callback). This little helper returns a promise that finishes after some milliseconds:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
console.log("start");
wait(100).then(() => console.log("100 ms later"));
console.log("end of script");
The course does not cover promises in depth, but you will meet them constantly: loading data, timers, and many browser features all return promises.
await, used inside a function marked async, pauses that function until a promise finishes, so async code reads top to bottom again. The rest of the program keeps running in the meantime:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
async function run() {
console.log("waiting...");
await wait(100);
console.log("done");
}
run();
console.log("run() returned right away");
await line and step through async code just like normal code. The debugger waits for the promise before moving to the next line.
fetch(url) asks a server for a file and returns a promise of a response. Reading the response body is a second async step, response.json(), which turns JSON text into a JavaScript value. The lesson pages have a small data.json file next to them, so you can try this in the console right here:
fetch("data.json")
.then(response => response.json())
.then(data => console.log(data));
If nothing appears, log the response itself before using it. Its status and ok properties tell you whether the server actually sent the file:
fetch("data.json")
.then(response => {
console.log(response.status, response.ok);
return response.json();
})
.then(data => console.log(data.name));
Many async bugs are really network problems. The developer tools' Network tab lists every request the page makes. Reload the page with it open and check:
Inside an async function, wrap the await lines in try...catch (JS Error Statements). Here W3Schools' example asks for a file that does not exist:
async function loadData() {
try {
const response = await fetch("wrong.json");
console.log(response.status, response.ok);
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error.name + ": " + error.message);
}
}
loadData();
fetch only rejects when the request cannot be made at all (no network, blocked address). A "404 Not Found" is still a response, so the error above does not come from the missing file: it comes from response.json() trying to read the server's HTML error page as JSON. That is why the message mentions <!DOCTYPE. Check response.ok and throw a clear error yourself.
async function loadData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Could not load ${url}: HTTP ${response.status}`);
}
const data = await response.json();
console.log(data.name);
} catch (error) {
console.log(error.message);
}
}
async function main() {
await loadData("data.json");
await loadData("wrong.json");
}
main();
main awaits each call so they run one after the other. Without the awaits, both requests would run at the same time and log in whichever order they happened to finish.
With a .then() chain instead of await, add a .catch() at the end to handle an error from any step:
fetch("wrong.json")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log("caught:", error.name));
An error thrown in async code that nothing catches does not stop your other code, but it is not invisible either. The console shows it with the words "in promise":
async function fail() {
throw new Error("boom");
}
fail();
console.log("the rest of the script still runs");
Uncaught (in promise). What is true is that the page shows nothing, so a user never finds out. Keep the console open, and handle errors where the user needs to know.return its fetch) is not a promise that never resolves. The fetch still finishes; the caller just receives undefined instead of the promise, so it has nothing to wait for. See below.A function that starts async work must return the promise, or its caller cannot wait for the result:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
function getNumber() {
wait(10).then(() => 42); // forgot return
}
function getNumberFixed() {
return wait(10).then(() => 42);
}
console.log(getNumber());
getNumberFixed().then(n => console.log(n));
If await getNumber() gives you undefined, look for a missing return in the function first.
status, ok) before using its datatry...catch around await, or .catch() at the end of a chainawait lines and step throughWhat does this log, in order?
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
1, 4, 3, 2. The normal lines run first. Then JavaScript runs promise callbacks before timer callbacks, even a timer with a delay of 0.fetch("scores.json") gets a 404. What happens?
fetch throws a TypeError immediatelyok set to false and status 404fetch only rejects when the request cannot be made at all, so you must check response.ok yourself.const data = await loadScores(); gives undefined, but the Network tab shows the file loaded fine. What should you check first?
loadScores returns its promise (for example, return fetch(...).then(...)). Without return, the function gives back undefined right away, and await has nothing to wait for.data.json and log data.scoresresponse.ok. Then open your browser's developer tools (F12) and watch the request in the Network tabawait line