From 5d84d26c3abbe6a4f0064debd685a1cfef83670a Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 27 Dec 2024 15:19:49 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Commit=20of=20go-promises?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Taskfile.yml | 26 ++++++++++++++ errors.go | 10 ++++++ go.mod | 3 ++ internal/add.go | 5 +++ internal/assets/.gitignore | 2 ++ internal/assets/index.html | 14 ++++++++ internal/cmd/server/main.go | 14 ++++++++ internal/cmd/wasm/main.go | 50 +++++++++++++++++++++++++++ internal/struct.go | 10 ++++++ new.go | 8 +++++ promisify.go | 28 ++++++++++++++++ value.go | 67 +++++++++++++++++++++++++++++++++++++ 13 files changed, 238 insertions(+) create mode 100644 .gitignore create mode 100644 Taskfile.yml create mode 100644 errors.go create mode 100644 go.mod create mode 100644 internal/add.go create mode 100644 internal/assets/.gitignore create mode 100644 internal/assets/index.html create mode 100644 internal/cmd/server/main.go create mode 100644 internal/cmd/wasm/main.go create mode 100644 internal/struct.go create mode 100644 new.go create mode 100644 promisify.go create mode 100644 value.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1c37ad --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.task/ diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..f469ccf --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,26 @@ +# https://taskfile.dev + +version: '3' + +tasks: + wasm-compile: + desc: "Compile wasm" + sources: + - '**/*go' + generates: + - internal/assets/app.wasm + cmds: + - GOOS=js GOARCH=wasm go build -o internal/assets/app.wasm ./internal/cmd/wasm/ + get-wasm-exec: + desc: "Copies wasm_exec.js into internal/assets" + generates: + - internal/assets/wasm_exec.js + cmds: + - cp -v "$(go env GOROOT)"/misc/wasm/wasm_exec.js internal/assets/ + run-server: + desc: "Run HTTP server" + deps: + - get-wasm-exec + - wasm-compile + cmds: + - go run ./internal/cmd/server/ diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..c5af77d --- /dev/null +++ b/errors.go @@ -0,0 +1,10 @@ +package promises + +import "syscall/js" + +func GoErrorToJSError(err error) js.Value { + if err == nil { + return js.Null() + } + return NewGlobal("Error", err.Error()) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..643b4d0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module codeberg.org/danjones000/go-promises + +go 1.23.4 diff --git a/internal/add.go b/internal/add.go new file mode 100644 index 0000000..0b04b79 --- /dev/null +++ b/internal/add.go @@ -0,0 +1,5 @@ +package internal + +func Add(a, b int) int { + return a + b +} diff --git a/internal/assets/.gitignore b/internal/assets/.gitignore new file mode 100644 index 0000000..3edfba4 --- /dev/null +++ b/internal/assets/.gitignore @@ -0,0 +1,2 @@ +app.wasm +wasm_exec.js diff --git a/internal/assets/index.html b/internal/assets/index.html new file mode 100644 index 0000000..a465f44 --- /dev/null +++ b/internal/assets/index.html @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/internal/cmd/server/main.go b/internal/cmd/server/main.go new file mode 100644 index 0000000..089a5e8 --- /dev/null +++ b/internal/cmd/server/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "net/http" +) + +func main() { + err := http.ListenAndServe(":9090", http.FileServer(http.Dir("./internal/assets"))) + if err != nil { + fmt.Println("Failed to start server", err) + return + } +} diff --git a/internal/cmd/wasm/main.go b/internal/cmd/wasm/main.go new file mode 100644 index 0000000..bff7177 --- /dev/null +++ b/internal/cmd/wasm/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "errors" + "fmt" + "syscall/js" + + promises "codeberg.org/danjones000/go-promises" + "codeberg.org/danjones000/go-promises/internal" +) + +func main() { + fmt.Println("Some go") + + js.Global().Set("add", promises.PromisifyGoFunc(func(_ js.Value, args []js.Value) (any, error) { + if len(args) < 2 { + return nil, errors.New("add called with too few arguments") + } + a := args[0] + b := args[1] + + if a.Type() != js.TypeNumber { + return nil, fmt.Errorf("First argument must be a number. %s given", a.Type()) + } + + if b.Type() != js.TypeNumber { + return nil, fmt.Errorf("secong argument must be a number. %s given", b.Type()) + } + + return internal.Add(a.Int(), b.Int()), nil + })) + + js.Global().Set("getUser", promises.PromisifyGoFunc(func(this js.Value, args []js.Value) (any, error) { + if len(args) < 2 { + return nil, errors.New("getUser called with too few arguments") + } + name := args[0] + age := args[1] + + if name.Type() != js.TypeString { + return nil, fmt.Errorf("First argument must be a string. %s given", name.Type()) + } + if age.Type() != js.TypeNumber { + return nil, fmt.Errorf("Second argument must be a string. %s given", age.Type()) + } + return internal.GetUser(name.String(), age.Int()), nil + })) + + <-make(chan struct{}) +} diff --git a/internal/struct.go b/internal/struct.go new file mode 100644 index 0000000..9daef80 --- /dev/null +++ b/internal/struct.go @@ -0,0 +1,10 @@ +package internal + +type User struct { + Name string `json:"name"` + Age int `json:"age"` +} + +func GetUser(name string, age int) User { + return User{name, age} +} diff --git a/new.go b/new.go new file mode 100644 index 0000000..6c62c0a --- /dev/null +++ b/new.go @@ -0,0 +1,8 @@ +package promises + +import "syscall/js" + +func NewGlobal(thing string, args ...any) js.Value { + newThing := js.Global().Get(thing) + return newThing.New(args...) +} diff --git a/promisify.go b/promisify.go new file mode 100644 index 0000000..88aad2e --- /dev/null +++ b/promisify.go @@ -0,0 +1,28 @@ +package promises + +import "syscall/js" + +type JSWrapper func(this js.Value, args []js.Value) (any, error) + +func PromisifyGoFunc(caller JSWrapper) js.Func { + return js.FuncOf(func(this js.Value, args []js.Value) any { + return NewGlobal("Promise", js.FuncOf(func(_ js.Value, promArgs []js.Value) any { + resolve := promArgs[0] + reject := promArgs[1] + go func() { + val, err := caller(this, args) + if err != nil { + reject.Invoke(GoErrorToJSError(err)) + } else { + jsVal, jsErr := ValueOf(val) + if jsErr != nil { + reject.Invoke(GoErrorToJSError(jsErr)) + } else { + resolve.Invoke(jsVal) + } + } + }() + return nil + })) + }) +} diff --git a/value.go b/value.go new file mode 100644 index 0000000..d149ab3 --- /dev/null +++ b/value.go @@ -0,0 +1,67 @@ +package promises + +import ( + "encoding/json" + "fmt" + "syscall/js" +) + +func ValueOf(val any) (js.Value, error) { + switch v := val.(type) { + case js.Value: + return v, nil + case js.Func: + return js.ValueOf(v), nil + case nil: + return js.Null(), nil + case bool: + return js.ValueOf(v), nil + case int: + return js.ValueOf(v), nil + case int8: + return js.ValueOf(v), nil + case int16: + return js.ValueOf(v), nil + case int32: + return js.ValueOf(v), nil + case int64: + return js.ValueOf(v), nil + case uint: + return js.ValueOf(v), nil + case uint8: + return js.ValueOf(v), nil + case uint16: + return js.ValueOf(v), nil + case uint32: + return js.ValueOf(v), nil + case uint64: + return js.ValueOf(v), nil + case float32: + return js.ValueOf(v), nil + case float64: + return js.ValueOf(v), nil + case string: + return js.ValueOf(v), nil + case []any: + return js.ValueOf(v), nil + case map[string]any: + return js.ValueOf(v), nil + case []byte: + return js.ValueOf(string(v)), nil + default: + by, marshalErr := json.Marshal(val) + if marshalErr == nil { + m := make(map[string]any) + unmarshallErr := json.Unmarshal(by, &m) + if unmarshallErr == nil { + return js.ValueOf(m), nil + } + s := []any{} + unmarshallErr = json.Unmarshal(by, &s) + if unmarshallErr == nil { + return js.ValueOf(s), nil + } + } + } + return js.Value{}, fmt.Errorf("unable to create a JS value for %T", val) +}