-
Notifications
You must be signed in to change notification settings - Fork 18k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: syscall/js: Reinstate js.Wrapper interface #50310
Comments
@abrander Could you please provide some real world examples that show why it is not a good solution to simply get the a.DoStuffInJavascriptLand(obj.JSValue()) |
If But to answer your question. We often do stuff like this: package a
import (
"syscall/js"
)
type Displayable interface {
js.Wrapper
Display()
}
type RevealContainer struct {
js.Value
children []Displayable
}
var _ Displayable = &RevealContainer{}
func NewRevealContainer() *RevealContainer {
return &RevealContainer{
Value: js.Global().Get("revealContainer").New(),
}
}
func (r *RevealContainer) Add(element Displayable) {
r.children = append(r.children, element)
}
func (r *RevealContainer) Display() {
for _, c := range r.children {
r.Call("append", c.JSValue())
c.Display()
}
} package b
import (
"syscall/js"
)
type Widget struct {
js.Value
}
var _ a.Displayable = &Widget{}
func NewWidget() *Widget {
return &Widget{js.Global().Get("widget").New()}
}
func (w *Widget) Display() {
w.Call("display")
} And putting it all together: package main
import (
"a"
"b"
)
func main() {
c := a.NewRevealContainer()
w1 := b.NewWidget()
w2 := b.NewWidget()
c.Add(w1)
c.Add(w2)
c.Display()
} This produces very readable code that's easy to understand and write. If every type implementing Some convention need to exist for package interoperability. Maybe I'm missing something. Maybe I'm seeing monsters where none is to be seen ;) |
Personally, I think the syscall/js package is rather a low level package (as the name "syscall" suggests), and it should include only things that directly interact with "the system". In this criteria, "solv[ing] this case of inter-package calling" is not such a low level thing. Maybe a package out of the standard library is a better place for this. |
Is it possible to write this function / interface in a third-party package? |
This proposal has been added to the active column of the proposals project |
The To implement I see three ways forward: A: Reinstate both method and interfacePackages can embed the B: Reinstate just the methodPackages can implement C: Reinstate nothingPackages cannot implement I can understand why the method and interface were removed. They both looked like a leftover from the undocumented feature removed from TL;DR: |
I think it does not fundamentally have to be in syscall. The Wrapper interface or the JSValue method is not used anywhere in the syscall/js package or anywhere in the standard library. And JSValue, being an identity function, doesn't provide any functionality that cannot be accessed otherwise. A third party package could wrap a js.Value and has a JSValue method, and let other packages to use/wrap that type. |
I believe the goal is to have a type that says "js.Value or a wrapper of js.Value". This is not really possible any more. Maybe we could do without accepting a plain However, this might cause a lot of boilerplate, e.g. instead of @abrander Maybe you could post a full example of how such a |
Exactly! And I see interfaces like type Thing interface {
js.Wrapper
Something() error
} And as of Go 1.18beta1 we lost the ability to define interfaces like that without wrapping
This is an actual wrapper for package js
import (
"syscall/js"
)
type Value struct{ js.Value }
type Func struct{ js.Func }
type Wrapper interface {
JSValue() Value
}
var ValueOf = js.ValueOf
func FuncOf(fn func(this Value, args []Value) interface{}) Func {
wrapper := func(realThis js.Value, realArgs []js.Value) interface{} {
wrappedArgs := make([]Value, len(realArgs))
for i, a := range realArgs {
wrappedArgs[i] = Value{a}
}
return fn(Value{realThis}, wrappedArgs)
}
return Func{js.FuncOf(wrapper)}
}
func CopyBytesToGo(dst []byte, src Value) int {
return js.CopyBytesToGo(dst, src.Value)
}
func Undefined() Value {
return Value{js.Undefined()}
}
func Global() Value {
return Value{js.Global()}
}
const TypeFunction = js.TypeFunction
func (v Value) JSValue() Value {
return v
}
func (v Value) Get(p string) Value {
return Value{v.Value.Get(p)}
}
func translateArgs(args []interface{}) []interface{} {
realArgs := make([]interface{}, len(args))
for i, a := range args {
switch v := a.(type) {
case Value:
realArgs[i] = v.Value
case Func:
realArgs[i] = v.Func
default:
realArgs[i] = a
}
}
return realArgs
}
func (v Value) Invoke(args ...interface{}) Value {
return Value{v.Value.Invoke(translateArgs(args)...)}
}
func (v Value) Call(m string, args ...interface{}) Value {
return Value{v.Value.Call(m, translateArgs(args)...)}
}
func (v Value) New(args ...interface{}) Value {
return Value{v.Value.New(translateArgs(args)...)}
}
func (v Value) Equal(w Value) bool {
return v.Value.Equal(w.Value)
}
func (v Value) Index(i int) Value {
return Value{v.Value.Index(i)}
} (Note the beautiful Less code will do if the I'm sure there are other ways of making this possible, though. |
It seems clear that js.ValueOf cannot contain a test for a JSValue method (named interface or not), because that leads to the performance problem that #44006 aimed to fix. It seems like the minimum thing we'd need to do to satisfy the request in this issue would be to add a JSValue method back to js.Value. But if we do put a JSValue method on js.Value, it seems very weird for js.ValueOf not to use it. What would be the explanation for js.ValueOf not using a JSValue method? And how would we make sure it didn't get added back? Just a wart for performance reasons? The question seems to be: how often does this come up? Third-party packages can define their own interfaces and agree on the method name and signature without coordination. For example if they agree on interface { JSValue() js.Value } then that should be enough, much like there are multiple definitions of fmt.Stringer floating around but anything that has String() string implements all of them. |
Yep. Very weird indeed.
I believe it's not an issue right now (!). Go-wasm projects are fragmented, and the community hasn't consolidated on anything yet. But I have a feeling it will matter shortly.
And that's where I think we'll end up. Or projects implement wrappers for
|
Referring back to my last comment, we have: (1) js.ValueOf cannot contain a test for a JSValue method [performance] The implication then seems to be that we shouldn't add a JSValue method to js.Value. Especially since @abrander says this is not causing any problems right now, let's leave it out for Go 1.18. |
Based on the discussion above, this proposal seems like a likely decline. |
No change in consensus, so declined. |
This commit removes references to js.Wrapper as js.Wrapper has been removed as part of golang/go#50310. Fixes #12.
This commit removes references to js.Wrapper as js.Wrapper has been removed as part of golang/go#50310. Fixes #12.
This commit removes references to js.Wrapper as js.Wrapper has been removed as part of golang/go#50310. Fixes #12.
Fixes build against go1.19 js.Wrapper was removed: add it back as JSWrapper. golang/go#50310 golang/go@6c0daa7#diff-38b359833111770b96ea3bb8bc5e2b807e18bdbb80684a397ce63ff05e6d02ecL31 Signed-off-by: Christian Stewart <christian@paral.in>
Fixes build against go1.19 js.Wrapper was removed: add it back as JSWrapper. golang/go#50310 golang/go@6c0daa7#diff-38b359833111770b96ea3bb8bc5e2b807e18bdbb80684a397ce63ff05e6d02ecL31 Signed-off-by: Christian Stewart <christian@paral.in>
js.Wrapper
was removed when removing support forjs.Wrapper
injs.ValueOf()
in 6c0daa7. In #39740 @finnbear suggested writing this proposal after I asked about the decision in #39740 (comment) and #39740 (comment).I understand the reasoning for removing the
case Wrapper:
fromjs.ValueOf()
, which makes good sense.What I regret is the removal of the interface itself, it served a purpose besides the case in
js.ValueOf()
. It allowed packages and functions to signal very easily and precisely that they accept a "type backed by an underlying javascript object.".js.Wrapper
was perfect for this purpose, it provided glue for packages in the Go wasm community. We could write packages like this, and inter-package calling was well-defined:Users of
package a
could satisfy this interface either by embeddingjs.Value
or by satisfying the interface in another way. It was very convenient and ensured that all packages agreed allowing simple inter-package calling.I propose the following:
js.Wrapper
interface.JSValue()
method onjs.Value
.This will solve this case of inter-package calling in the Go wasm community, and we can avoid packages defining their own (incompatible) interfaces for this purpose.
The text was updated successfully, but these errors were encountered: