PPysolate SpineREAD THE CODE · UNDERSTAND THE SYSTEM
课程与章节
源码索引 / CURRENT IMPLEMENTATION

learning/go-patterns/stages/04-context/main.go

89 行 · 点击行号可定位;函数链接随声明位置更新,不手填行号。

跳到函数 / 声明
// Stage 04 carries context through the service and source so waiting can stop.
// Run it with: go run .
package main

import (
	"context"
	"errors"
	"fmt"
	"time"
)

var ErrUnknownItem = errors.New("unknown item")

type Price struct {
	Item  string
	Cents int
}

type PriceSource interface {
	Lookup(context.Context, string) (Price, error)
}

type Catalog map[string]Price

type DelayedCatalog struct {
	prices Catalog
	delay  time.Duration
}

func NewDelayedCatalog(prices Catalog, delay time.Duration) DelayedCatalog {
	return DelayedCatalog{prices: prices, delay: delay}
}

// Lookup waits for the source delay, but also wakes when the caller cancels.
func (c DelayedCatalog) Lookup(ctx context.Context, item string) (Price, error) {
	if err := ctx.Err(); err != nil {
		return Price{}, err
	}

	price, ok := c.prices[item]
	if !ok {
		return Price{}, fmt.Errorf("%w: %q", ErrUnknownItem, item)
	}
	if c.delay <= 0 {
		return price, nil
	}

	timer := time.NewTimer(c.delay)
	defer timer.Stop()
	select {
	case <-timer.C:
		return price, nil
	case <-ctx.Done():
		return Price{}, ctx.Err()
	}
}

type Service struct {
	source PriceSource
}

func NewService(source PriceSource) Service {
	return Service{source: source}
}

func (s Service) Quote(ctx context.Context, item string) (Price, error) {
	price, err := s.source.Lookup(ctx, item)
	if err != nil {
		return Price{}, fmt.Errorf("quote %q: %w", item, err)
	}
	return price, nil
}

func main() {
	service := NewService(NewDelayedCatalog(
		Catalog{"book": {Item: "book", Cents: 2100}},
		5*time.Millisecond,
	))

	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()

	price, err := service.Quote(ctx, "book")
	if err != nil {
		fmt.Println("quote failed:", err)
		return
	}
	fmt.Printf("%s: %d cents\n", price.Item, price.Cents)
}