JavaScript is single-threaded
JavaScript runs one thing at a time. So, how does it download a file or wait for a server response without "freezing"? Thanks to the event loop.
Operations that take time (network requests, timers...) are delegated to the environment (the browser or Node) and JavaScript keeps running the rest of the code. When the task finishes, its response function is placed in a queue, and the event loop runs it as soon as the main thread is free.
console.log("1");
setTimeout(() => console.log("2"), 0); // deferred
console.log("3");
// Prints: 1, 3, 2
Even if the delay is 0, the setTimeout callback waits until the current
code finishes. That is the key to asynchrony.
Callbacks
Historically, asynchronous tasks were handled with callbacks: functions you pass so they get called "when it finishes".
download("data.json", function (result) {
console.log("Done:", result);
});
The problem: nesting many callbacks produces the dreaded callback hell, code shaped like a pyramid that is hard to read. Promises were born to solve it.
Examples
The order isn't what it seems
console.log("Start");
setTimeout(() => console.log("Deferred task"), 0);
console.log("End");