- Abstraction
- AI Pair Programming
- Algorithm
- API
- Array
- Array methods
- Booleans
- Callback
- Class
- Class Members
- Closure
- Closure
- Code refactoring
- Comment
- Computer programming
- Conditional statements
- Constant
- Constructor
- Coupling and Cohesion
- Data types
- Debugging
- Decorator
- Dependency
- Destructuring
- Dictionary
- Enum
- Event
- Exception / Error handling
- Function
- Generic / Template
- Higher-order function
- IDE
- Immutability
- Inheritance
- Input validation
- Integer
- Interface
- Iteration patterns
- Legacy code
- Loop
- Machine learning
- Memoization
- Memory and references
- Method
- Module
- Null / Undefined / None
- Null safety / Optional values
- Object
- Object-Oriented Programming (OOP)
- Operator
- Parameter
- Parsing
- Promise and Async/Await
- Prompt Engineering
- Recursion
- Regular expression
- Return statement
- Rollback
- Runtime
- Scope
- Script
- Sequence
- Set
- Spaghetti code
- Spread and Rest operators
- State management
- String
- Switch statement
- Synchronous vs Asynchronous execution
- Syntax
- Technical debt
- Ternary operator
- Testing
- This / Self
- Tuple
- Type casting
- Type conversion
- Variable
- Vibe coding
- Webhook
PROGRAMMING-CONCEPTS
Promise and Async/Await: Definition, Purpose, and Examples
A Promise represents a value that will be available in the future. It acts as a placeholder for the result of an asynchronous operation — something that takes time, like fetching data from an API, reading a file, or waiting for a timer.
Async/await is a more modern syntax built on top of Promises that lets you write asynchronous code in a natural, step-by-step style without getting lost in callback chains. Both are essential to modern JavaScript and TypeScript, especially in browser applications and React.
Why Asynchronous Code Exists in the First Place
Many tasks don’t finish immediately.
A program may need to:
- Request data from a server
- Delay execution for a second
- Read a file or compute something expensive
- Wait for user interaction
Instead of freezing the entire program until the task finishes, asynchronous programming allows the rest of the code to continue running. Promises and async/await give developers a structured way to work with these delayed results.
What a Promise Is
A Promise is an object that represents a future value. It can be in one of three states:
- Pending — still waiting
- Fulfilled — completed successfully
- Rejected — failed with an error
Once fulfilled or rejected, the Promise becomes settled, and its result can be used.
JavaScript example:
const dataPromise = fetch("/api/user");
fetch() immediately returns a Promise. The network request happens in the background; your code stays responsive.
To access the result:
dataPromise.then(response => response.json())
The then method provides a callback that runs when the Promise resolves.
This pattern is the foundation for modern web APIs.
Promise Chains
If several asynchronous steps depend on each other, Promises can chain operations:
fetch("/api/user")
.then(res => res.json())
.then(user => fetch(`/api/orders/${user.id}`))
.then(res => res.json())
Each .then(...) waits for the previous step to finish.
This chaining avoids callback nesting, but long chains can still become unwieldy — which is why async/await was introduced.
Async/Await: A Better Way to Work with Promises
Async/await sits on top of Promises but makes asynchronous code look synchronous.
async function loadUser() {
const res = await fetch("/api/user");
const user = await res.json();
return user;
}
Each await pauses the function until the Promise resolves, without blocking the entire program.
This style improves readability and makes complex asynchronous flows easier to understand.
Using this function:
loadUser().then(user => console.log(user));
Even though loadUser looks synchronous, it still returns a Promise. This keeps the entire system consistent.
Handling Errors
Promises use .catch() to handle failures:
fetch("/api/user")
.then(res => res.json())
.catch(err => console.error("Error:", err));
Async/await uses try / catch:
async function getUser() {
try {
const res = await fetch("/api/user");
return await res.json();
} catch (err) {
console.error("Failed:", err);
}
}
This makes error-handling feel closer to normal control flow.
Parallel vs Sequential Execution
Sometimes multiple asynchronous tasks can run at the same time.
Parallel execution:
const [user, orders] = await Promise.all([
fetch("/api/user").then(r => r.json()),
fetch("/api/orders").then(r => r.json())
]);
This waits for both operations to finish and is much faster than running them sequentially.
Sequential execution:
const user = await fetch("/api/user").then(r => r.json());
const orders = await fetch(`/api/orders/${user.id}`).then(r => r.json());
This runs one step after the other — necessary when the second depends on the first.
Understanding this difference greatly impacts performance.
TypeScript and Promises
TypeScript improves safety by giving async code explicit return types.
async function loadProfile(): Promise<User> {
const res = await fetch("/profile");
return res.json();
}
This guarantees the function returns a Promise resolving to User.
It prevents mistakes where a developer returns the wrong type or forgets to handle null/undefined scenarios.
React and Promises
React doesn’t allow async functions directly inside components, but async/await is common in effects.
useEffect(() => {
async function load() {
const res = await fetch("/api/tasks");
setTasks(await res.json());
}
load();
}, []);
This pattern:
- fetches data
- updates state
- triggers automatic re-rendering
React also uses Promises internally in features like Suspense, which prepares the framework for more parallel UI rendering.
Python’s async/await (for comparison)
Python 3.7+ includes its own async model inspired by Promises.
Python
import asyncio
async def load_user():
return {"name": "Alice"}
async def main():
user = await load_user()
print(user)
asyncio.run(main())
Python’s version uses coroutines, but the core idea is the same:
async defines an asynchronous function; await pauses it until a future value is ready.
This parallel helps learners transfer understanding across languages.
Swift’s Concurrency Model (for comparison)
Swift also uses async/await:
func fetchUser() async throws -> User {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
Swift’s design strongly resembles JavaScript’s async/await, reinforcing the consistency of modern asynchronous patterns.
Real-World Example: Loading Data for a Dashboard
A dashboard may need several pieces of data: user info, tasks, notifications.
Using async/await:
async function loadDashboard() {
const [user, tasks, notifications] = await Promise.all([
fetch("/api/user").then(r => r.json()),
fetch("/api/tasks").then(r => r.json()),
fetch("/api/notifications").then(r => r.json())
]);
return { user, tasks, notifications };
}
Each API call runs in parallel, and the result is assembled cleanly.
This approach is both fast and readable.
Real-World Example: Graceful Error Handling
When one API might fail while others succeed:
async function loadSafely(url) {
try {
const res = await fetch(url);
return await res.json();
} catch {
return null;
}
}
const user = await loadSafely("/api/user");
const settings = await loadSafely("/api/settings");
This makes the application resilient to partial failures without crashing.
Common Mistakes
Because asynchronous code behaves differently than synchronous code, beginners often fall into predictable traps.
Forgetting to return Promises
async function load() {
fetch("/api/data"); // does nothing useful by itself
}
If you don’t await or return it, nothing waits for the result.
Mixing callback-style and Promise-style code
This leads to unpredictable behavior.
Using async/await inside forEach
forEach does not handle asynchronous control flow the way people expect.
Treating async functions as synchronous
For example:
const result = asyncFunction(); // not the value — this is a Promise
Understanding that async functions always return Promises is essential.
Best Practices
- Prefer async/await over long Promise chains
- Use
Promise.allwhen tasks can run in parallel - Wrap asynchronous code in
try/catch - Never block the main thread
- Keep asynchronous steps small and composable
- Use TypeScript to catch incorrect Promise usage early
- In React, avoid making
useEffectitself async — define an inner async function
Learning these habits early prevents a large category of real-world bugs.
Summary
Promises represent future values, while async/await provides a clearer way to work with those values without getting trapped in nested callbacks. They enable smooth handling of tasks that take time, such as API requests, timers, file reads, and user interactions. Today, every major ecosystem — JavaScript, TypeScript, React, Python, Swift — relies on these asynchronous tools to build responsive, reliable applications.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.