Async vs Threads
Async to Threads
Introduction
In this lesson, you'll learn about async vs threads in TypeScript. Coming from Java, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Java, you're familiar with async to threads.
TypeScript has its own approach to async to threads, which we'll explore step by step.
The TypeScript Way
Let's see how TypeScript handles this concept. Here's a typical example:
// TypeScript - async/await (single-threaded event loop)
async function loadData(): Promise<string> {
try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data.message.toUpperCase();
} catch (err) {
return `ERROR: ${(err as Error).message}`;
}
}
// Run multiple in parallel (like CompletableFuture.allOf)
const [users, posts] = await Promise.all([
fetchUsers(),
fetchPosts(),
]);Comparing to Java
Here's how you might have written similar code in Java:
import java.util.concurrent.CompletableFuture;
// Java - CompletableFuture for async tasks (uses real threads)
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> fetchData()) // runs on ForkJoinPool thread
.thenApply(data -> process(data)) // transform result
.thenApply(String::toUpperCase)
.exceptionally(err -> "ERROR: " + err.getMessage());
String result = future.join(); // blocks current threadYou may be used to different syntax or behavior.
Java CompletableFuture uses a real thread pool (ForkJoinPool); TypeScript runs on a single-threaded event loop
You may be used to different syntax or behavior.
async/await is cleaner and more readable than .thenApply() chains
You may be used to different syntax or behavior.
Promise<T> is the TypeScript equivalent of CompletableFuture<T>
You may be used to different syntax or behavior.
Promise.all() replaces CompletableFuture.allOf() for parallel execution
You may be used to different syntax or behavior.
You cannot block the TypeScript main thread — avoid synchronous-looking patterns that would block Node.js
Step-by-Step Breakdown
1. Promise Basics
A Promise<T> represents a future value, just like CompletableFuture<T>. Use .then() and .catch() or async/await.
CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> "hello");
f.thenAccept(s -> System.out.println(s));const p: Promise<string> = Promise.resolve("hello");
// .then() style (like thenAccept/thenApply)
p.then(s => console.log(s));
// async/await style (preferred — reads like sync code)
async function run() {
const s = await p;
console.log(s);
}2. async/await
Mark a function async, then await any Promise inside it. Errors are caught with try/catch.
CompletableFuture.supplyAsync(() -> fetch())
.thenApply(r -> parse(r))
.exceptionally(e -> handleError(e))
.join();async function fetchData(): Promise<Data> {
try {
const response = await fetch("/api/data");
if (!response.ok) throw new Error("HTTP " + response.status);
return await response.json() as Data;
} catch (err) {
console.error(err);
throw err; // re-throw or return fallback
}
}3. Parallel Execution
Promise.all() runs multiple async operations in parallel and waits for all of them.
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);
all.join();// Wait for all — fails fast if any rejects
const [user, orders, prefs] = await Promise.all([
fetchUser(id),
fetchOrders(id),
fetchPreferences(id),
]);
// Wait for all even if some fail
const results = await Promise.allSettled([f1(), f2(), f3()]);
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.error(r.reason);
});4. No Blocking Operations
Unlike Java, you must never block the Node.js event loop. Long synchronous work should be offloaded.
// BAD — blocks the event loop for everyone
function slowSync() {
const end = Date.now() + 3000;
while (Date.now() < end) {} // spinning — blocks!
}
// GOOD — use async I/O, never busy-wait
import { readFile } from "fs/promises";
async function readConfig() {
const content = await readFile("config.json", "utf8");
return JSON.parse(content);
}Common Mistakes
When coming from Java, developers often make these mistakes:
- Java CompletableFuture uses a real thread pool (ForkJoinPool); TypeScript runs on a single-threaded event loop
- async/await is cleaner and more readable than .thenApply() chains
- Promise<T> is the TypeScript equivalent of CompletableFuture<T>
Key Takeaways
- Promise<T> is the TypeScript equivalent of CompletableFuture<T>
- async/await is cleaner than .thenApply() chains — prefer it always
- Promise.all() runs async tasks in parallel
- TypeScript runs on a single-threaded event loop — never block it with synchronous work