

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Still calibrating
click for more info
Not enough gems
Cost: 6 gems
1: PokeAPI
incomplete
2: Caching
incomplete
3: Explore
incomplete
4: Catch
incomplete
5: Inspect
incomplete
This lesson's interactive features are locked, please to keep using them
It's time to implement caching! This will make moving around the map feel a lot snappier. We'll be building a flexible caching system to help with performance in future steps.
A cache temporarily stores data so that future requests for that data can be served faster.
In our case, we'll be caching responses from the PokeAPI so that when we need that same data again, we can grab it from memory instead of making another network request.
I used a Cache struct to hold a map[string]cacheEntry and a mutex to protect the map across goroutines. A cacheEntry should be a struct with two fields:
createdAt - A time.Time that represents when the entry was created.val - A []byte that represents the raw data we're caching.You'll probably want to expose a NewCache() function that creates a new cache with a configurable interval (time.Duration).
I used a time.Ticker to make this happen. If you want additional help, see the Tips section below.
Maps are not thread-safe in Go. You should use a sync.Mutex to lock access to the map when you're adding, getting entries or reaping entries. It's unlikely that you'll have issues because reaping only happens every ~5 seconds, but it's still possible, so you should make your cache package safe for concurrent use.
Run and submit the CLI tests from the root of the repo.
You can use a time.Ticker inside a goroutine started by NewCache. In a loop like for range ticker.C { ... }, check the entries and remove any whose createdAt is older than the cache's interval.
You can run tests for all packages in a Go module by running go test ./... from the root of the module.
func TestAddGet(t *testing.T) {
const interval = 5 * time.Second
cases := []struct {
key string
val []byte
}{
{
key: "https://example.com",
val: []byte("testdata"),
},
{
key: "https://example.com/path",
val: []byte("moretestdata"),
},
}
for i, c := range cases {
t.Run(fmt.Sprintf("Test case %v", i), func(t *testing.T) {
cache := NewCache(interval)
cache.Add(c.key, c.val)
val, ok := cache.Get(c.key)
if !ok {
t.Errorf("expected to find key")
return
}
if string(val) != string(c.val) {
t.Errorf("expected to find value")
return
}
})
}
}
func TestReapLoop(t *testing.T) {
const baseTime = 5 * time.Millisecond
const waitTime = baseTime + 5*time.Millisecond
cache := NewCache(baseTime)
cache.Add("https://example.com", []byte("testdata"))
_, ok := cache.Get("https://example.com")
if !ok {
t.Errorf("expected to find key")
return
}
time.Sleep(waitTime)
_, ok = cache.Get("https://example.com")
if ok {
t.Errorf("expected to not find key")
return
}
}