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:
| JavaScript | Rust |
|---|---|
async function | async fn |
await somePromise | someValue.await |
Promise<T> | impl Future<Output = T> |
Promise.all(...) | tokio::join!(...) หรือ futures::join_all(...) |
| Node.js event loop (built-in) | Tokio runtime (dependency อย่างชัดเจน) |
Promise.allSettled | tokio::join! (ทั้งหมดทำงานจนเสร็จเสมอ) |
ความแตกต่างที่สำคัญที่สุด: runtime ของ JavaScript (V8, Node.js) ทำงานอยู่เสมอ ใน Rust ไม่มี async runtime แบบ built-in — คุณต้องนำมาเองอย่างชัดเจน Tokio คือมาตรฐานโดยพฤตินัยสำหรับ async Rust ฝั่ง server (web servers, databases, network services)
async fn และ .await
หัวข้อที่มีชื่อว่า “async fn และ .await”async fn คืน Future — การคำนวณแบบ lazy ที่ยังไม่ทำอะไรจนกว่า runtime จะเข้ามา poll คุณใช้ .await เพื่อสั่งให้ Future ทำงานแล้วรับผลลัพธ์ ตรงนี้ syntax เหมือน await ของ JavaScript ทุกประการ
// TypeScript async/awaitasync function fetchUser(id: number): Promise<string> { const resp = await fetch(`/users/${id}`); const data = await resp.json(); return data.name;}
// รันแบบ concurrent ด้วย Promise.allasync function main() { const [user1, user2] = await Promise.all([ fetchUser(1), fetchUser(2), ]); console.log(user1, user2);}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}สร้าง async task ด้วย tokio::spawn
หัวข้อที่มีชื่อว่า “สร้าง async task ด้วย tokio::spawn”tokio::spawn คือ async เทียบเท่าของ thread::spawn โดยจะ schedule future ลงบน Tokio thread pool แล้วคืน JoinHandle<T> ต่างจาก thread::spawn ตรงที่ task ทุกตัวรันบน thread pool ที่ Tokio จัดการให้ — โดยทั่วไปหนึ่ง thread ต่อหนึ่ง CPU core
// 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}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}");}Compiling…