Async Programming
Asynchronous Programming#
Asynchronous programming lets your program start long-running operations and continue working while waiting for them to complete, rather than blocking and idling. Think of it like a chef preparing multiple dishes—while one dish bakes in the oven, the chef preps vegetables for another dish instead of standing idle watching the oven.
This approach dramatically improves resource utilization and responsiveness. User interfaces stay interactive during file operations, web servers handle thousands of concurrent requests without thousands of threads, and applications make better use of available CPU time by working on other tasks while waiting for network responses or disk I/O.
How It Works#
The core concept revolves around an event loop—a single thread that manages multiple operations. When you start an asynchronous operation (like reading a file), the program registers a callback and immediately continues to other work. When the operation completes, the event loop executes the callback with the results.
Programming Styles#
Callbacks#
The simplest async approach uses callbacks—functions passed to async operations that run when work completes. While straightforward for simple cases, deeply nested callbacks create "callback hell," making code hard to read and maintain.
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class CallbackExample {
public void performAsyncOperation(Consumer<String> callback) {
// Simulate async operation
new Thread(() -> {
try {
Thread.sleep(1000); // Simulate work
String result = "Async operation completed";
callback.accept(result);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
public void example() {
performAsyncOperation(result -> {
System.out.println("Callback received: " + result);
// Handle the result
});
}
}
fun performAsyncOperation(callback: (String) -> Unit) {
// Simulate async operation
Thread {
Thread.sleep(1000) // Simulate work
val result = "Async operation completed"
callback(result)
}.start()
}
fun example() {
performAsyncOperation { result ->
println("Callback received: $result")
// Handle the result
}
}
// TypeScript/JavaScript: Callback-based programming is well supported
function performAsyncOperation(callback: (result: string) => void): void {
// Simulate async operation
setTimeout(() => {
const result = "Async operation completed";
callback(result);
}, 1000);
}
function example(): void {
performAsyncOperation((result) => {
console.log("Callback received:", result);
// Handle the result
});
}
// Usage
example();
import 'dart:async';
// Dart: Callback-based programming using Future
Future<void> performAsyncOperation(Function(String) callback) async {
// Simulate async operation
await Future.delayed(Duration(seconds: 1));
final result = "Async operation completed";
callback(result);
}
void example() {
performAsyncOperation((result) {
print("Callback received: $result");
// Handle the result
});
}
// Usage
example();
import Foundation
// Swift: Callback-based programming using closures
func performAsyncOperation(callback: @escaping (String) -> Void) {
// Simulate async operation
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
let result = "Async operation completed"
callback(result)
}
}
func example() {
performAsyncOperation { result in
print("Callback received: \(result)")
// Handle the result
}
}
// Usage
example()
import asyncio
import time
# Python: Callback-based programming using asyncio
def perform_async_operation(callback):
async def _async_work():
await asyncio.sleep(1) # Simulate work
result = "Async operation completed"
callback(result)
asyncio.create_task(_async_work())
def example():
def handle_result(result):
print(f"Callback received: {result}")
# Handle the result
perform_async_operation(handle_result)
# Usage
example()
Promises#
Promises represent future values—placeholders for results that aren't ready yet. You can chain operations with .then() and handle errors with .catch(), making the flow clearer than nested callbacks. A promise can be pending, fulfilled with a value, or rejected with an error.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class PromiseExample {
public CompletableFuture<String> fetchData() {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000); // Simulate network call
return "Data fetched successfully";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
});
}
public CompletableFuture<String> processData(String data) {
return CompletableFuture.supplyAsync(() -> {
return "Processed: " + data.toUpperCase();
});
}
public void example() {
fetchData()
.thenCompose(this::processData)
.thenAccept(result -> System.out.println("Final result: " + result))
.exceptionally(throwable -> {
System.err.println("Error: " + throwable.getMessage());
return null;
});
}
}
import java.util.concurrent.CompletableFuture
class PromiseExample {
fun fetchData(): CompletableFuture<String> {
return CompletableFuture.supplyAsync {
Thread.sleep(1000) // Simulate network call
"Data fetched successfully"
}
}
fun processData(data: String): CompletableFuture<String> {
return CompletableFuture.supplyAsync {
"Processed: ${data.uppercase()}"
}
}
fun example() {
fetchData()
.thenCompose { processData(it) }
.thenAccept { result -> println("Final result: $result") }
.exceptionally { throwable ->
System.err.println("Error: ${throwable.message}")
null
}
}
}
// TypeScript/JavaScript: Promise-based programming with async/await
class PromiseExample {
async fetchData(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched successfully");
}, 1000);
});
}
async processData(data: string): Promise<string> {
return `Processed: ${data.toUpperCase()}`;
}
// Using async/await (modern approach)
async exampleAsync(): Promise<void> {
try {
const data = await this.fetchData();
const result = await this.processData(data);
console.log("Final result:", result);
} catch (error) {
console.error("Error:", (error as Error).message);
}
}
// Using .then() chaining (traditional approach)
examplePromiseChain(): void {
this.fetchData()
.then(data => this.processData(data))
.then(result => console.log("Final result:", result))
.catch(error => console.error("Error:", error.message));
}
}
// Usage
const promiseExample = new PromiseExample();
await promiseExample.exampleAsync();
import 'dart:async';
// Dart: Promise-based programming using Future
class PromiseExample {
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 1));
return "Data fetched successfully";
}
Future<String> processData(String data) async {
return "Processed: ${data.toUpperCase()}";
}
void example() {
fetchData()
.then((data) => processData(data))
.then((result) => print("Final result: $result"))
.catchError((error) => print("Error: $error"));
}
}
// Usage
final promiseExample = PromiseExample();
promiseExample.example();
import Foundation
// Swift: Promise-based programming using async/await
class PromiseExample {
func fetchData() async -> String {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "Data fetched successfully"
}
func processData(_ data: String) async -> String {
return "Processed: \(data.uppercased())"
}
func example() async {
do {
let data = await fetchData()
let result = await processData(data)
print("Final result: \(result)")
} catch {
print("Error: \(error)")
}
}
}
// Usage
let promiseExample = PromiseExample()
if #available(macOS 10.15, iOS 13.0, *) {
Task {
await promiseExample.example()
}
}
import asyncio
# Python: Promise-based programming using asyncio
class PromiseExample:
async def fetch_data(self):
await asyncio.sleep(1)
return "Data fetched successfully"
async def process_data(self, data):
return f"Processed: {data.upper()}"
async def example(self):
try:
data = await self.fetch_data()
result = await self.process_data(data)
print(f"Final result: {result}")
except Exception as error:
print(f"Error: {error}")
# Usage
promise_example = PromiseExample()
asyncio.run(promise_example.example())