Async Fetch: Callbacks & Channels
Fetch has three shapes. All hit the same network — you pick the one that fits how you want to wait for the reply.
Which one? Use Fetch (blocking) for a single request in a handler — it's the simplest. Use FetchAsync when you want a callback and don't want to write a goroutine. Use FetchChan to fan out many requests at once and collect them as they finish.
From JavaScript? Fetch ≈ await fetch(), FetchAsync ≈ fetch().then(), and firing several FetchChan then reading them back ≈ Promise.allSettled (each result carries its own error — no fail-fast).
// Blocking — the simplest form. Waits for the reply, then continues.
CreateWasmFunc("load", func() {
resp, err := Fetch("/api/data")
if err == nil {
SetText("out", resp.Text())
}
})FetchAsync returns right away and calls your done function when the response arrives. There's no goroutine to manage, and the callback runs in this component's scope, so it can update the DOM directly.
// Callback — FetchAsync returns immediately and runs done() when the
// reply arrives. No goroutine needed. done() runs in this component's
// scope, so it can write the DOM directly.
CreateWasmFunc("load", func() {
SetText("out", "loading...")
FetchAsync("/api/data", FetchConfig{Method: "GET"}, func(resp Response, err error) {
if err != nil {
SetText("out", "error: "+err.Error())
return
}
SetText("out", resp.Text())
})
})FetchChan returns a channel instead of blocking. Kick off each request and keep its channel; since none of them waits yet, they all run in parallel. Then read the results back — the total time is the slowest request, not the sum. Each value is a FetchResult (Response + Err). Do the receive inside a goroutine so your click handler never blocks.
// Channel — FetchChan returns a <-chan FetchResult, like a "receipt" you
// read later. FetchResult is { Response Response; Err error }.
CreateWasmFunc("loadDashboard", func() {
go func() {
// Fire all three at once — they run in PARALLEL, nothing blocks yet.
user := FetchChan("/api/user")
posts := FetchChan("/api/posts")
stats := FetchChan("/api/stats")
// Now collect them. These <- look sequential, but the requests are
// ALREADY running in parallel: <-user waits on one that's in flight
// while posts and stats keep loading. Total time = the slowest one.
userRes := <-user
postsRes := <-posts
statsRes := <-stats
if userRes.Err == nil {
SetText("user", userRes.Response.Text())
}
if postsRes.Err == nil {
SetText("posts", postsRes.Response.Text())
}
if statsRes.Err == nil {
SetText("stats", statsRes.Response.Text())
}
}()
})Cancellation is automatic. Every component owns an AbortController behind the scenes. Any request still in flight when the component is torn down — say it gets swapped out — is aborted for you. There is nothing to wire up and no leak to worry about.
Getting back JSON? Stop parsing strings by hand — Decode it straight into a Go struct. Learn Typed JSON next!
