astwerk

Async data

A Resource exposes its loading and error states as signals alongside the data, so all three bind like anything else. The loader here sleeps instead of calling wasmwrap.Fetch, but the binding code is identical either way.

    How it works

    The resource

    go
    type Post struct {
    	ID    string
    	Title string
    }
    
    posts := reactive.NewResource(func() ([]Post, error) {
    	return reactive.FetchJSON[[]Post]("/api/posts")
    })

    NewResource runs the loader on its own goroutine and exposes Data, Err and Loading as ordinary signals — you always read the result back through Data.Get().

    The status line

    go
    reactive.BindText(status, func() string {
    	if posts.Loading.Get() {
    		return "Loading…"
    	}
    	if err := posts.Err.Get(); err != nil {
    		return "Error: " + err.Error()
    	}
    	return strconv.Itoa(len(posts.Data.Get())) + " posts loaded"
    })

    All three states bind like anything else. The loader here sleeps instead of fetching, but the code is identical either way.

    The list

    go
    reactive.BindList(list, posts.Data.Get,
    	func(p Post) string { return p.ID },
    	func(p Post) wasmwrap.Element {
    		return wasmwrap.Create("li").SetText(p.Title)
    	})

    posts.Data.Get feeds BindList exactly like any other signal — loading, error and data are all the same model.

    Reload

    go
    reactive.On(reload, "click", func(wasmwrap.Event) { posts.Reload() })

    Reload re-runs the loader. A generation counter discards a stale response, so an old, slow load can't overwrite newer data.

    Full source
    go
    type Post struct {
    	ID    string
    	Title string
    }
    
    posts := reactive.NewResource(func() ([]Post, error) {
    	return reactive.FetchJSON[[]Post]("/api/posts")
    })
    
    reactive.BindText(status, func() string {
    	if posts.Loading.Get() {
    		return "Loading…"
    	}
    	if err := posts.Err.Get(); err != nil {
    		return "Error: " + err.Error()
    	}
    	return strconv.Itoa(len(posts.Data.Get())) + " posts loaded"
    })
    
    reactive.BindList(list, posts.Data.Get,
    	func(p Post) string { return p.ID },
    	func(p Post) wasmwrap.Element {
    		return wasmwrap.Create("li").SetText(p.Title)
    	})
    
    reactive.On(reload, "click", func(wasmwrap.Event) { posts.Reload() })
    How this demo runs

    scripts/demo.go is compiled to demo.wasm by CompileFrom and loaded by WasmScript — see Scripts & WebAssembly. Without WebAssembly the page still renders; only the status stays put.