Replies: 2 comments 2 replies
-
jsruntime.go package application
import (
"cherrynotes/timedmap"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/runtime"
"time"
)
const (
EventJsrInvoke = "JSR_INVOKE"
EventJsrInvokeReturned = "JSR_INVOKE_RET"
)
type JsRuntime struct {
app *App
callbacks *timedmap.TimedMap
cleanupInterval time.Duration
}
type InvokeCallback func(timeout bool, err error, ret interface{})
type _InvokeCallbackWrap struct {
callback InvokeCallback
called bool
}
type _InvokeBody struct {
Id string `json:"id"`
Method string `json:"method"`
Args []interface{} `json:"args"`
}
type _ReturnBody struct {
Id string `mapstructure:"id"`
Method string `mapstructure:"method"`
Err string `mapstructure:"err"`
Returns interface{} `mapstructure:"returns"`
}
func NewJsRuntime(cleanupInterval time.Duration) *JsRuntime {
ttlmap := timedmap.New(cleanupInterval)
ttlmap.StopCleaner()
return &JsRuntime{
cleanupInterval: cleanupInterval,
callbacks: ttlmap,
}
}
func (jsr *JsRuntime) Init(app *App) {
jsr.app = app
jsr.callbacks.StartCleanerInternal(jsr.cleanupInterval)
runtime.EventsOn(app.GetContext(), EventJsrInvokeReturned, jsr.onInvokeReturned)
}
func (jsr *JsRuntime) Stop() {
runtime.EventsOff(jsr.app.GetContext(), EventJsrInvokeReturned)
jsr.app = nil
jsr.callbacks.StopCleaner()
jsr.callbacks.Flush()
}
func (jsr *JsRuntime) IsInit() bool {
return jsr.app != nil && jsr.app.IsStarted()
}
func (jsr *JsRuntime) Invoke(method string, callback InvokeCallback, timeout time.Duration, args ...interface{}) {
if !jsr.IsInit() {
return
}
body := _InvokeBody{
Id: uuid.New().String(),
Method: method,
Args: args,
}
if timeout > 0 {
jsr.callbacks.Set(
body.Id,
&_InvokeCallbackWrap{
called: false,
callback: callback,
},
timeout,
func(key timedmap.Key, value timedmap.Value) {
// 超时回调
wrap := value.(*_InvokeCallbackWrap)
if wrap.called {
return
}
wrap.called = true
if wrap.callback != nil {
wrap.callback(true, nil, nil)
}
},
)
}
// 发送事件
runtime.EventsEmit(jsr.app.GetContext(), EventJsrInvoke, body)
}
func (jsr *JsRuntime) Call(method string, args ...interface{}) {
jsr.Invoke(method, nil, 0, args...)
}
func (jsr *JsRuntime) onInvokeReturned(rets ...interface{}) {
if !jsr.IsInit() || rets == nil || len(rets) != 1 {
return
}
tmp := rets[0]
ret := _ReturnBody{}
err := mapstructure.Decode(tmp, &ret)
if err != nil {
return
}
wrap := jsr.callbacks.Take(ret.Id).(*_InvokeCallbackWrap)
if wrap == nil {
return
}
if wrap.called {
return
}
wrap.called = true
// 响应回调
if wrap.callback != nil {
var retErr error = nil
if ret.Err != "" {
retErr = errors.New(ret.Err)
}
wrap.callback(false, retErr, ret.Returns)
}
} use case func usecase() {
start := time.Now().UnixMilli()
JsRuntime.Invoke("doWork", func(timeout bool, err error, ret interface{}) {
fmt.Println("past: ", time.Now().UnixMilli()-start)
fmt.Println(timeout, err, ret)
}, 10*time.Second, 1, 2.0, "hello world", "")
} jsruntime.js const EventJsrInvoke = "JSR_INVOKE"
const EventJsrInvokeReturned = "JSR_INVOKE_RET"
class ReturnBody {
id
method
err
returns
constructor(id, method, err, returns) {
this.id = id
this.method = method
this.err = err
this.returns = returns
}
}
class JsRuntime {
apis = {}
unregisteredHandler = null
constructor() {
window.runtime.EventsOn(EventJsrInvoke, (args) => {
this._onInvoke(args)
});
}
register(method, callback) {
this.apis[method] = callback
}
unregister(method) {
delete this.apis[method]
}
isRegistered(method) {
return !(this.apis[method] == null || this.apis[method] === undefined)
}
setUnregisteredHandler(handler) {
this.unregisteredHandler = handler
}
_onInvoke(arg) {
let { method, id, args } = arg;
let ret = null;
let err = null;
try {
if (method && id) {
let api = this.apis[method]
if (api) {
ret = api(args)
} else {
let handler = this.unregisteredHandler;
if (handler) {
ret = handler(method, id, args);
} else {
ret = null;
throw new Error("api not registered")
}
}
} else {
throw new Error("bad invoke")
}
} catch (e) {
err = e.message
} finally {
console.log("return:", ret);
window.runtime.EventsEmit(EventJsrInvokeReturned, new ReturnBody(
id, method, err, ret
));
}
}
}
export {
JsRuntime
} use case <template>
<div></div>
</template>
<script>
export default {
name: "IndexPage",
};
</script>
<script setup>
import { onMounted } from "vue";
import { JsRuntime } from "../../jsruntime.js";
let jsRuntime = null;
onMounted(() => {
jsRuntime = new JsRuntime()
jsRuntime.register("doWork", function (args) {
console.log(args);
return 123;
})
});
</script>
<style scoped></style> Go output:
it takes 1-3 ms to invoke js and get it return value on my computer. |
Beta Was this translation helpful? Give feedback.
0 replies
-
Thanks for the detailed write up. We don't recommend having application logic in JS. However if you really are sure it's a good idea, you can use events to do this: send an event from Go, in JS listen for it, run code, send a return event from JS and pick that up in Go. EDIT: looks like we posted at the same time 🤣 |
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Recently, I am learning how to use wails to develop desktop apps. There is a very interesting question: how to invoke a JavaScript function from the Go and get its return value or its exception? The first thing I thought of was the api
We usually use this api like this
This is equivalent to entering a "window. someFunc(1, 'hello')" statement on the browser console. It works well, but there are several problems:
We can't get the return value of someFunc()
If someFunc() throws an exception, we will not know
It is very inconvenient to pass parameters to someFunc(). It is not only very difficult but also time-consuming to convert these complex types, such as slice, map, and structures, into appropriate string forms.
As shown in the wails document: WindowExecJS (ctx context. Context, js string) is a function with no return value, and
The second method I think of is RPC. Of course, it is not a complete RPC implementation, but a very simple "RPC"-like mechanism.
The interface may be like this:
In Go we use it as below:
In Js, we do like this:
The above mechanism can be implemented by using the wails' event system. here are several difficulties:
How to map calls and returns one by one
How to implement the timeout mechanism
Finally, I used a map with timeout mechanism to preliminarily solve these two problems.I will post some codes later and hope to get your suggestions.
These codes may look "ugly" because I have just learned Go for a few days. Any comments or suggestions are welcome
Beta Was this translation helpful? Give feedback.
All reactions