Introduction to Asynchronous Programming Flow
In synchronous programming, each line of code waits for the previous one to finish. But in the real world, not all tasks complete instantly — for example, reading a file or fetching data from the internet. These operations can delay the entire program. To avoid that, we use asynchronous programming, which allows non-blocking execution.
Callbacks
A callback is a function passed as an argument to another function, to be executed later. This is the most basic approach to handle asynchronous operations.
// Simulating an asynchronous operation using a callback
function fetchData(callback):
print("Fetching data... (simulated delay)")
wait 2 seconds
data = "result"
callback(data)
function handleData(result):
print("Received:", result)
fetchData(handleData)
Output:
Fetching data... (simulated delay) Received: result
Why use callbacks?
Callbacks allow us to run code *after* something finishes without blocking the flow of the entire program. But they can lead to messy code when nested too deeply — this is known as “callback hell.”
Question:
What happens if the callback is not invoked inside the async function?
Answer: The subsequent operation relying on the result never runs, leading to incomplete logic execution or silent failures.
Promises
A Promise is a construct that represents a value that might be available now, in the future, or never. It has three states: pending
, fulfilled
, or rejected
.
// Simulating a Promise-like behavior
function getPromise():
promise = createPromise()
wait 2 seconds
if success:
promise.resolve("Success Data")
else:
promise.reject("Error Occurred")
return promise
promise = getPromise()
promise.then(handleSuccess)
.catch(handleFailure)
function handleSuccess(data):
print("Data received:", data)
function handleFailure(error):
print("Something went wrong:", error)
Output:
Data received: Success Data
Promises make the code more readable and manageable than callbacks by chaining responses using then()
and handling errors with catch()
.
Question:
What if you don’t call catch()
on a rejected promise?
Answer: The error goes unhandled, potentially crashing your application or leading to hard-to-find bugs.
Futures
A Future is a placeholder for a value that will be computed later, commonly seen in multi-threaded or concurrent environments. While similar to Promises, Futures are more common in typed or compiled languages and are tied closely with execution threads.
// Simulating a future
function computeHeavyTask():
future = createFuture()
runInBackground(() -> {
wait 3 seconds
result = 42
future.setResult(result)
})
return future
future = computeHeavyTask()
value = future.get() // This waits until result is ready
print("Computed value:", value)
Output:
Computed value: 42
Question:
How is a Future different from a Promise?
Answer: While both represent a value that becomes available later, Futures are often blocking when accessed (e.g., get()
waits), whereas Promises typically use non-blocking handlers like then()
.
Summary
- Callbacks are functions invoked after an asynchronous task finishes.
- Promises offer a structured way to handle success and failure paths of async tasks.
- Futures represent a value that becomes available later and may block when accessed.
When to Use What?
- Use callbacks for very simple async tasks or legacy code.
- Use promises when you want clean chaining and error handling.
- Use futures in systems where threading and concurrency are important.