ข้ามไปยังเนื้อหา

Async/Await และ Tokio

Async/await: โมเดล concurrency ของ Rust ที่คุ้นเคยที่สุดสำหรับนักพัฒนา TS

หัวข้อที่มีชื่อว่า “Async/await: โมเดล concurrency ของ Rust ที่คุ้นเคยที่สุดสำหรับนักพัฒนา TS”

ถ้าคุณเคยเขียน TypeScript คุณก็รู้จักโมเดลทางความคิดเบื้องหลัง Rust async/await แล้ว ทั้งสองภาษาใช้ syntax เดียวกัน (async fn, .await) เพื่อเขียนโค้ดที่ non-blocking แต่อ่านเหมือน sequential logic แนวคิดหลักแมปกันได้เกือบ 1:1:

JavaScriptRust
async functionasync fn
await somePromisesomeValue.await
Promise<T>impl Future<Output = T>
Promise.all(...)tokio::join!(...) หรือ futures::join_all(...)
Node.js event loop (built-in)Tokio runtime (dependency อย่างชัดเจน)
Promise.allSettledtokio::join! (ทั้งหมดทำงานจนเสร็จเสมอ)

ความแตกต่างที่สำคัญที่สุด: runtime ของ JavaScript (V8, Node.js) ทำงานอยู่เสมอ ใน Rust ไม่มี async runtime แบบ built-in — คุณต้องนำมาเองอย่างชัดเจน Tokio คือมาตรฐานโดยพฤตินัยสำหรับ async Rust ฝั่ง server (web servers, databases, network services)

async fn คืน Future — การคำนวณแบบ lazy ที่ยังไม่ทำอะไรจนกว่า runtime จะเข้ามา poll คุณใช้ .await เพื่อสั่งให้ Future ทำงานแล้วรับผลลัพธ์ ตรงนี้ syntax เหมือน await ของ JavaScript ทุกประการ

TypeScript
// TypeScript async/await
async function fetchUser(id: number): Promise<string> {
const resp = await fetch(`/users/${id}`);
const data = await resp.json();
return data.name;
}
// รันแบบ concurrent ด้วย Promise.all
async function main() {
const [user1, user2] = await Promise.all([
fetchUser(1),
fetchUser(2),
]);
console.log(user1, user2);
}
Rust
use tokio::time::{sleep, Duration};
// async fn คืน Future<Output = String>
async fn fetch_user(id: u32) -> String {
// .await คืน control ให้ runtime ขณะ "รอ"
sleep(Duration::from_millis(10)).await;
format!("user_{id}")
}
#[tokio::main] // ตั้งค่า Tokio runtime สำหรับ main()
async fn main() {
// tokio::join! รัน futures สอง future แบบ concurrent บน thread pool เดียวกัน
// — เทียบเท่ากับ Promise.all
let (user1, user2) = tokio::join!(
fetch_user(1),
fetch_user(2),
);
println!("{user1} {user2}"); // user_1 user_2
}

tokio::spawn คือ async เทียบเท่าของ thread::spawn โดยจะ schedule future ลงบน Tokio thread pool แล้วคืน JoinHandle<T> ต่างจาก thread::spawn ตรงที่ task ทุกตัวรันบน thread pool ที่ Tokio จัดการให้ — โดยทั่วไปหนึ่ง thread ต่อหนึ่ง CPU core

TypeScript
// TypeScript: fire-and-forget ด้วย Promise (หรือ await เพื่อรวบรวมผล)
async function work(id: number): Promise<number> {
await new Promise(resolve => setTimeout(resolve, 10));
return id * 2;
}
async function main() {
// Spawn ทั้งหมด แล้ว await ทั้งหมด — เทียบเท่ากับ tokio::spawn + join
const handles = [1, 2, 3].map(id => work(id));
const results = await Promise.all(handles);
const total = results.reduce((a, b) => a + b, 0);
console.log('total:', total); // total: 12
}
Rust
use tokio::time::{sleep, Duration};
async fn work(id: u32) -> u32 {
sleep(Duration::from_millis(10)).await;
id * 2
}
#[tokio::main]
async fn main() {
let handles: Vec<_> = (1u32..=3)
.map(|id| tokio::spawn(work(id)))
.collect();
let mut total = 0u32;
for h in handles {
total += h.await.unwrap();
}
println!("total: {total}"); // total: 12
}
use tokio::time::{sleep, Duration};
async fn fetch_user(id: u32) -> String {
sleep(Duration::from_millis(10)).await;
format!("user_{id}")
}
#[tokio::main]
async fn main() {
let (user1, user2) = tokio::join!(
fetch_user(1),
fetch_user(2),
);
println!("{user1} {user2}");
}
ความแตกต่างที่สำคัญที่สุดระหว่าง Rust async และ JavaScript async คืออะไร?
#[tokio::main] ทำอะไร?
คู่เทียบของ tokio::join!(a, b) ใน JavaScript คืออะไร?
เกิดอะไรขึ้นถ้าคุณสร้าง Rust Future แล้วไม่เคย .await เลย?