Context — การยกเลิกและ Deadlines
AbortController vs context.Context
หัวข้อที่มีชื่อว่า “AbortController vs context.Context”ใน browser/Node.js คุณใช้ AbortController เพื่อยกเลิก fetch หรือ operation ใด ๆ ที่รับ AbortSignal:
const controller = new AbortController();setTimeout(() => controller.abort(), 5000); // cancel after 5 sconst res = await fetch(url, { signal: controller.signal });ใน Go context.Context ทำหน้าที่เดียวกัน — และไปไกลกว่านั้น เพราะเป็น interface มาตรฐานที่พก cancellation signal, deadline (ถ้ามี) และ request-scoped value ติดตัวไปด้วย วิธีใช้คือส่ง context เป็น argument แรกของทุก I/O function ใน call chain การ cancel ที่ด้านบนสุดจะได้ส่งต่อลงไปทั้งสายโดยอัตโนมัติ
context.WithCancel
หัวข้อที่มีชื่อว่า “context.WithCancel”context.WithCancel คืน context ใหม่พร้อม cancel function การเรียก cancel จะ close channel ctx.Done() ซึ่งทุก goroutine ที่ฟัง context นั้นอยู่จะเห็น:
// TypeScript: AbortControllerconst controller = new AbortController();const { signal } = controller;
async function worker(signal: AbortSignal) { // signal.aborted becomes true when abort() is called while (!signal.aborted) { await doWork(); } console.log("Worker cancelled");}
controller.abort(); // broadcast cancellation// Go: context.WithCancelpackage main
import ( "context" "fmt" "sync")
func worker(ctx context.Context, id int, wg *sync.WaitGroup) { defer wg.Done() select { case <-ctx.Done(): // unblocks when cancel() is called fmt.Printf("Worker %d cancelled: %v\n", id, ctx.Err()) }}
func main() { ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup
for i := 1; i <= 3; i++ { wg.Add(1) go worker(ctx, i, &wg) }
cancel() // broadcast cancellation to all workers wg.Wait() fmt.Println("All workers stopped")}context.WithTimeout
หัวข้อที่มีชื่อว่า “context.WithTimeout”สำหรับ operation ที่ต้องเสร็จภายใน deadline ใช้ context.WithTimeout Go จะ cancel context ให้อัตโนมัติเมื่อ timer หมดเวลา:
// TypeScript: timeout via Promise.raceasync function withTimeout<T>( fn: () => Promise<T>, ms: number): Promise<T> { const timeout = new Promise<never>((_, reject) => setTimeout(() => reject(new Error("Timed out after " + ms + "ms")), ms) ); return Promise.race([fn(), timeout]);}// Go: context.WithTimeoutpackage main
import ( "context" "fmt" "sync" "time")
func slowOperation(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() select { case <-time.After(5 * time.Second): // simulated slow work fmt.Println("Operation complete") case <-ctx.Done(): fmt.Println("Operation timed out:", ctx.Err()) }}
func main() { ctx, cancel := context.WithTimeout( context.Background(), 100*time.Millisecond, // deadline: 100 ms ) defer cancel() // always defer cancel — prevents resource leak
var wg sync.WaitGroup wg.Add(1) go slowOperation(ctx, &wg) wg.Wait()}Context Values — ส่งข้อมูลผ่าน Call Chain
หัวข้อที่มีชื่อว่า “Context Values — ส่งข้อมูลผ่าน Call Chain”// TypeScript: pass request-scoped data via function argumentsasync function handler(req: Request) { const userId = req.headers.get('x-user-id'); await processRequest(userId);}// Go: context.WithValue carries data through the call chainpackage main
import ( "context" "fmt")
type contextKey string
const userIDKey contextKey = "userID"
func processRequest(ctx context.Context) { userID := ctx.Value(userIDKey) fmt.Println("Processing for user:", userID)}
func main() { ctx := context.WithValue( context.Background(), userIDKey, "user-123", ) processRequest(ctx)}package main
import ( "context" "fmt" "sync")
func worker(ctx context.Context, id int, wg *sync.WaitGroup) { defer wg.Done() select { case <-ctx.Done(): fmt.Printf("Worker %d stopped: %v\n", id, ctx.Err()) }}
func main() { ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup
for i := 1; i <= 3; i++ { wg.Add(1) go worker(ctx, i, &wg) }
cancel() wg.Wait() fmt.Println("All workers stopped")}Compiling on go.dev (Go 1.27)… offline? an in-browser runtime (~8 MB, Go 1.26) is used instead