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

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

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

const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // cancel after 5 s
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) {
// signal.aborted becomes true when abort() is called
while (!signal.aborted) {
await doWork();
}
console.log("Worker cancelled");
}
controller.abort(); // broadcast cancellation
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(): // 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")
}

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

TypeScript
// TypeScript: timeout via Promise.race
async 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
// 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): // 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()
}
TypeScript
// TypeScript: pass request-scoped data via function arguments
async function handler(req: Request) {
const userId = req.headers.get('x-user-id');
await processRequest(userId);
}
Go
// Go: context.WithValue carries data through the call chain
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?