Most JavaScript tutorials throw closures, promises, and async/await at you within the first week. Then they move on before any of it sticks. This post slows down. If these concepts still feel fuzzy, you are not alone. A lot of working developers still cannot explain closures clearly. The explanations below will actually help.
JavaScript closures, promises, and async/await are three of the most used and most misunderstood features in the language. So let us go through each one clearly.
What Is a Closure?
A closure happens when a function remembers the variables from the place where it was created, even after that place has finished running.
Here is a simple example:
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
The inner function still has access to count even though makeCounter has already returned. That is a closure. The inner function carries its environment with it.
Why does this matter? Closures let you keep data private. You cannot touch count from outside. Only the returned function can change it. This pattern shows up constantly in real JavaScript code, especially in event handlers, timers, and module patterns.
What Is a Promise?
JavaScript runs one thing at a time. However, some tasks take time. Fetching data from an API, reading a file, waiting for a timer. These tasks would block everything if JavaScript sat around waiting.
A Promise is JavaScript’s way of saying: “I do not have the result yet, but I will get back to you.”
A Promise has three states:
- Pending – the task is still running
- Fulfilled – the task finished successfully
- Rejected – the task failed
Here is how you use one:
fetch(“https://api.example.com/user”)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
The .then() runs when the Promise fulfills. The .catch() runs when it fails. This is cleaner than the old callback approach, where nested functions would stack up into what developers call “callback hell.”
Promises do not make asynchronous code synchronous. They make it manageable.
What Is Async/Await?
Async/await is built on top of Promises. It does not replace them. It just gives you a cleaner way to write the same logic.
The async keyword before a function means that function will always return a Promise. The await keyword pauses execution inside that function until a Promise resolves. Code outside the function keeps running normally.
Here is the same fetch example written with async/await:
async function getUser() {
try {
const response = await fetch(“https://api.example.com/user”);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
This reads like regular top-to-bottom code. That is the whole point. The try/catch block handles errors, which is cleaner than chaining .catch() onto every Promise.
One thing to watch: await only works inside an async function. If you use it at the top level, modern JavaScript supports that too, but it depends on your environment.
How These Three Connect
These concepts build on each other. Closures handle scope and private data. Promises handle asynchronous operations. Async/await handles how you write code that uses Promises.
You will see all three working together in real projects. A button click handler uses a closure to remember its context. Inside that handler, an async function fetches data using await. The fetch itself returns a Promise under the hood.
Understanding each piece separately makes the combination much easier to read and debug. Start with closures. Then move to Promises. Then async/await follows naturally. Most developers who struggle with async/await never fully understood Promises first. That is where the gap usually lives.
Frequently Asked Questions(FAQs)
1. What is the difference between a Promise and async/await in JavaScript?
A Promise is the underlying object that represents an asynchronous operation. Async/await is syntax that lets you write Promise-based code in a more readable, linear style. They do the same thing. Async/await just looks cleaner in most cases.
2. Why do JavaScript developers use closures?
Closures let functions remember and access variables from their outer scope after that scope has closed. Developers use them to keep data private, build stateful functions, and avoid polluting the global scope.
3. What does “callback hell” mean and how do Promises fix it?
Callback hell refers to deeply nested callback functions that become hard to read and maintain. Promises fix this by letting you chain .then() calls instead of nesting. Async/await takes it further by making the code look like it runs step by step.