🎉 Commit of go-promises

This commit is contained in:
Dan Jones 2024-12-27 15:19:49 -06:00
commit 5d84d26c3a
Signed by: dan
GPG key ID: 5B3B0F7217473A29
13 changed files with 238 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.task/

26
Taskfile.yml Normal file
View file

@ -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/

10
errors.go Normal file
View file

@ -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())
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module codeberg.org/danjones000/go-promises
go 1.23.4

5
internal/add.go Normal file
View file

@ -0,0 +1,5 @@
package internal
func Add(a, b int) int {
return a + b
}

2
internal/assets/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
app.wasm
wasm_exec.js

View file

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("app.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
</head>
<body></body>
</html>

View file

@ -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
}
}

50
internal/cmd/wasm/main.go Normal file
View file

@ -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{})
}

10
internal/struct.go Normal file
View file

@ -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}
}

8
new.go Normal file
View file

@ -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...)
}

28
promisify.go Normal file
View file

@ -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
}))
})
}

67
value.go Normal file
View file

@ -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)
}