diff --git a/wasm/error.go b/wasm/error.go new file mode 100644 index 0000000..063043a --- /dev/null +++ b/wasm/error.go @@ -0,0 +1,13 @@ +package wasm + +import "syscall/js" + +// NewError returns a JS Error with the provided Go error's error message. +func NewError(goErr error) js.Value { + errConstructor, err := Global().Expect(js.TypeFunction, "Error") + if err != nil { + panic("Error constructor not found") + } + + return errConstructor.New(goErr.Error()) +} diff --git a/wasm/promise.go b/wasm/promise.go new file mode 100644 index 0000000..fa85351 --- /dev/null +++ b/wasm/promise.go @@ -0,0 +1,150 @@ +package wasm + +import "syscall/js" + +// Promise is an instance of a JS promise. +// The zero value of this struct is not a valid Promise. +type Promise struct { + Object +} + +// FromJSValue turns a JS value to a Promise. +func (p *Promise) FromJSValue(value js.Value) error { + var err error + p.Object, err = NewObject(value) + return err +} + +// JSValue turns a Promise to a JS value. +func (p Promise) JSValue() js.Value { + return p.Object.JSValue() +} + +// NewPromise returns a promise that is fulfilled or rejected when the provided handler returns. +// The handler is spawned in its own goroutine. +func NewPromise(handler func() (interface{}, error)) Promise { + resultChan := make(chan interface{}) + errChan := make(chan error) + + // Invoke the handler in a new goroutine. + go func() { + result, err := handler() + if err != nil { + errChan <- err + return + } + resultChan <- result + }() + + // Create a JS promise handler. + var jsHandler js.Func + jsHandler = js.FuncOf(func(this js.Value, args []js.Value) interface{} { + if len(args) < 2 { + panic("not enough arguments are passed to the Promise constructor handler") + } + + resolve := args[0] + reject := args[1] + + if resolve.Type() != js.TypeFunction || reject.Type() != js.TypeFunction { + panic("invalid type passed to Promise constructor handler") + } + + go func() { + select { + case r := <-resultChan: + resolve.Invoke(r) + case err := <-errChan: + reject.Invoke(NewError(err)) + } + + // Free up resources now that we are done. + jsHandler.Release() + }() + + return nil + }) + + promise, err := Global().Expect(js.TypeFunction, "Promise") + if err != nil { + panic("Promise constructor not found") + } + + return mustJSValueToPromise(promise.New(jsHandler)) +} + +// PromiseAll creates a promise that is fulfilled when all the provided promises have been fulfilled. +// The promise is rejected when any of the promises provided rejects. +// It is implemented by calling Promise.all on JS. +func PromiseAll(promise ...Promise) Promise { + promiseAll, err := Global().Expect(js.TypeFunction, "Promise", "all") + if err != nil { + panic("Promise.all not found") + } + + pInterface := make([]interface{}, 0, len(promise)) + for _, v := range promise { + pInterface = append(pInterface, v) + } + + return mustJSValueToPromise(promiseAll.Invoke(pInterface)) +} + +// PromiseAllSettled creates a promise that is fulfilled when all the provided promises have been fulfilled or rejected. +// It is implemented by calling Promise.allSettled on JS. +func PromiseAllSettled(promise ...Promise) Promise { + promiseAllSettled, err := Global().Expect(js.TypeFunction, "Promise", "allSettled") + if err != nil { + panic("Promise.allSettled not found") + } + + pInterface := make([]interface{}, 0, len(promise)) + for _, v := range promise { + pInterface = append(pInterface, v) + } + + return mustJSValueToPromise(promiseAllSettled.Invoke(pInterface)) +} + +// PromiseAny creates a promise that is fulfilled when any of the provided promises have been fulfilled. +// The promise is rejected when all of the provided promises gets rejected. +// It is implemented by calling Promise.any on JS. +func PromiseAny(promise ...Promise) Promise { + promiseAny, err := Global().Expect(js.TypeFunction, "Promise", "any") + if err != nil { + panic("Promise.any not found") + } + + pInterface := make([]interface{}, 0, len(promise)) + for _, v := range promise { + pInterface = append(pInterface, v) + } + + return mustJSValueToPromise(promiseAny.Invoke(pInterface)) +} + +// PromiseRace creates a promise that is fulfilled or rejected when one of the provided promises fulfill or reject. +// It is implemented by calling Promise.race on JS. +func PromiseRace(promise ...Promise) Promise { + promiseRace, err := Global().Expect(js.TypeFunction, "Promise", "race") + if err != nil { + panic("Promise.race not found") + } + + pInterface := make([]interface{}, 0, len(promise)) + for _, v := range promise { + pInterface = append(pInterface, v) + } + + return mustJSValueToPromise(promiseRace.Invoke(pInterface)) +} + +func mustJSValueToPromise(v js.Value) Promise { + var p Promise + err := p.FromJSValue(v) + if err != nil { + panic("Expected a Promise from JS standard library") + } + + return p +}