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

Context — การยกเลิกและ Deadlines

ใน browser/Node.js คุณใช้ AbortController เพื่อยกเลิก fetch หรือ operation ใด ๆ ที่รับ AbortSignal:

const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // timeout 5 วินาที
const 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 ใหม่พร้อม cancel function การเรียก cancel จะ close channel ctx.Done() ซึ่งทุก goroutine ที่ฟัง context นั้นอยู่จะเห็น:

TypeScript
// TypeScript: AbortController
const controller = new AbortController();
const { signal } = controller;
async function worker(signal: AbortSignal) {
try {
await doWork(signal);
} catch (e) {
if (e.name === 'AbortError') {
console.log('Worker cancelled');
}
}
}
controller.abort(); // ยกเลิก
Go
// Go: context.WithCancel
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 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() // ยกเลิก workers ทั้งหมด
wg.Wait()
fmt.Println("All workers stopped")
}

สำหรับ operation ที่ต้องเสร็จภายใน deadline ใช้ context.WithTimeout Go จะ cancel context ให้อัตโนมัติเมื่อ timer หมดเวลา:

TypeScript
// TypeScript: timeout ด้วย Promise.race
async function withTimeout<T>(
fn: () => Promise<T>,
ms: number
): Promise<T> {
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
);
return Promise.race([fn(), timeout]);
}
Go
// Go: context.WithTimeout
package main
import (
"context"
"fmt"
"sync"
"time"
)
func slowOperation(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
select {
case <-time.After(5 * time.Second): // จำลอง slow operation
fmt.Println("Operation complete")
case <-ctx.Done():
fmt.Println("Operation timed out:", ctx.Err())
}
}
func main() {
// timeout หลัง 100ms
ctx, cancel := context.WithTimeout(
context.Background(),
100*time.Millisecond,
)
defer cancel() // ดีที่สุดคือ defer cancel() เสมอ
var wg sync.WaitGroup
wg.Add(1)
go slowOperation(ctx, &wg)
wg.Wait()
}
TypeScript
// TypeScript: ส่ง request-scoped data ผ่าน function args
async function handler(req: Request) {
const userId = req.headers.get('x-user-id');
await processRequest(userId);
}
Go
// Go: context.WithValue สำหรับ request-scoped data
package 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")
}
context.Context เปรียบเทียบกับอะไรใน JavaScript?
ทำไมถึงควร defer cancel() เสมอ?
Context ควรถูก pass อย่างไร?
channel ใดที่ close เมื่อ context ถูก cancel?