go
55 lines · 8 steps
Concurrent API fetches with errgroup in Go
Fetch a user, their repos, and orgs in parallel, cancelling the rest the moment any request fails.
Explained by
highlit
1package fetch
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8
9 "golang.org/x/sync/errgroup"
10)
11
12type Profile struct {
13 User User
14 Repos []Repo
15 Orgs []Org
16}
17
18func LoadProfile(ctx context.Context, client *http.Client, username string) (*Profile, error) {
19 g, ctx := errgroup.WithContext(ctx)
20 var profile Profile
21
22 g.Go(func() error {
23 return getJSON(ctx, client, "/users/"+username, &profile.User)
24 })
25
26 g.Go(func() error {
27 return getJSON(ctx, client, "/users/"+username+"/repos", &profile.Repos)
28 })
29
30 g.Go(func() error {
31 return getJSON(ctx, client, "/users/"+username+"/orgs", &profile.Orgs)
32 })
33
34 if err := g.Wait(); err != nil {
35 return nil, fmt.Errorf("load profile %q: %w", username, err)
36 }
37 return &profile, nil
38}
39
40func getJSON(ctx context.Context, client *http.Client, path string, dst any) error {
41 req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com"+path, nil)
42 if err != nil {
43 return err
44 }
45 resp, err := client.Do(req)
46 if err != nil {
47 return err
48 }
49 defer resp.Body.Close()
50
51 if resp.StatusCode != http.StatusOK {
52 return fmt.Errorf("%s: unexpected status %s", path, resp.Status)
53 }
54 return json.NewDecoder(resp.Body).Decode(dst)
55}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1errgroup.WithContext ties a set of goroutines to one context that cancels as soon as any of them returns an error.
- 2Each goroutine writing to a distinct field of a shared struct avoids data races without explicit locking.
- 3Wrapping the aggregated error with %w preserves the underlying cause for callers to inspect.
Related explainers
java
public class TimedFetchService { private final ExecutorService executor = Executors.newFixedThreadPool(8); private final HttpClient httpClient = HttpClient.newHttpClient();
Enforcing HTTP timeouts with a Future
concurrency
timeouts
thread-pool
Intermediate
8 steps
go
package middleware import ( "net/http"
Role-based access control middleware in Gin
middleware
authorization
closures
Intermediate
7 steps
python
from fastapi import FastAPI, Request, status from fastapi.encoders import jsonable_encoder from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse
Redacting sensitive fields in FastAPI errors
validation
error-handling
security
Intermediate
7 steps
go
func UserDashboardCache(rdb *redis.Client, ttl time.Duration) gin.HandlerFunc { return func(c *gin.Context) { claims, ok := c.Get("claims") if !ok {
Per-user response caching in Gin with Redis
middleware
caching
redis
Advanced
9 steps
javascript
import { unstable_cache, revalidateTag } from 'next/cache' import { db } from '@/lib/db' export const getDashboardStats = unstable_cache(
Caching dashboard stats in Next.js
caching
cache-invalidation
tag-based-revalidation
Intermediate
8 steps
go
package middleware import ( "compress/gzip"
How gzip HTTP middleware works in Go
middleware
compression
object-pooling
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/concurrent-api-fetches-with-errgroup-in-go-explained-go-1664/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.