Go: syscall/js: comparing two number objects with `==` fails

Created on 9 Jun 2018  路  17Comments  路  Source: golang/go

(This issue is copied from https://github.com/neelance/go/issues/27)

I'm using 8d6062b08b6d737f773d697e5dc52d98735b1194 on neelance's wasm-wip branch

package main

import (
        "syscall/js"
)

func main() {
        n := js.Global.Get("Number")
        println(n.New(1) == n.New(1))
}

Shows false, I expected true though. I suggest == return true since it'd be more intuitive.

The current result might be intended, but was really confusing to me. For example, getting WebGL's constant like FRAMEBUFFER_COMPLETE via Get creates a number object if I understand correctly, and comparing this constant value and another value always fails.

// gl is a WebGL context of a canvas.
if s := gl.Call("checkFramebufferStatus", gl.Get("FRAMEBUFFER")); s != gl.Get("FRAMEBUFFER_COMPLETE") {
        // This always comes here, whatever s is!
        return js.Null, errors.New(fmt.Sprintf("opengl: creating framebuffer failed: %d", s.Int()))
}

String object might be in the same situation .

Arch-Wasm FrozenDueToAge NeedsInvestigation release-blocker

Most helpful comment

We need to add func Equal(a, b js.Value) bool to the syscall/js package. It is not possible to compare two js.Value with the == operator.

All 17 comments

/cc @neelance

I鈥檓 not completely sure if two distinct js.Value values should report positively for equality, even if their underlying values are equal.

A similar situation I can think of is reflect.Value, where distinct reflect.Value values that represent the same underlying value are not equal:

To compare two Values, compare the results of the Interface method. Using == on two Values does not compare the underlying values they represent.

https://play.golang.org/p/cX0RhxB_800

The original code would work correctly if you compared the underlying values, right? Something like this:

// gl is a WebGL context of a canvas.
if s := gl.Call("checkFramebufferStatus", gl.Get("FRAMEBUFFER")); s.Int() != gl.Get("FRAMEBUFFER_COMPLETE").Int() {
        return js.Null, errors.New(fmt.Sprintf("opengl: creating framebuffer failed: %d", s.Int()))
}

These are some initial thoughts, I鈥檓 open to further discussion.

@hajimehoshi

Shows false, I expected true though

Why is this your expectation? Is there some example from the Javascript world where this equates to true (that I'm missing)?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness

The original code would work correctly if you compared the underlying values, right? Something like this:

Yes, Int() worked fine. However, I think it'd be the best if I do this without Int() since we cannot expect which API returns numbers as Number objects or just numbers. == sometimes works and sometimes not. I think this is different from reflect.Value situation. EDIT: Apparently Number objects are always created via js functions. This is deterministic :-)

Of course always adding Int() would be a solution. I'm not sure but I'd be fine if there is an explicit rule like what reflect.Value's comment says.

@myitcv

Oh I was misunderstanding:

$ new Number(0) === new Number(0)
false

Looks like 'false' would be fine in my example...

OK another example would be

package main

import (
        "syscall/js"
)

func main() {
        js.Global.Set("a", 1)
        js.Global.Set("b", 1)
        println(js.Global.Get("a") == js.Global.Get("b")) // false
}

Looks like Number objects are always created whenever syscall/js function is used?

We need to add func Equal(a, b js.Value) bool to the syscall/js package. It is not possible to compare two js.Value with the == operator.

@neelance Would Equal be equivalent to ==? What about ===?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness

I thought Equal implements ===, not ==. Who wants JavaScript's ==?

Yes, Equal would be JS' ===, since this is what fits Go's semantics of equality the best. My preference would be to not offer ==, because it is considered bad practice by many.

My preference would be to not offer ==, because it is considered bad practice by many.

Indeed, my point was that it immediately raises the question of which one it refers to. There is also Object.is which is distinct from both == and ===.

Is there any reason this needs to be part of the API at all? Can we not make it incumbent on the developer to call the appropriate JS function?

Is there any reason this needs to be part of the API at all? Can we not make it incumbent on the developer to call the appropriate JS function?

What exactly do you mean?

I should start by clarifying one thing: it's my understanding that currently a == comparison of two js.Value in the Go world effectively give us the JS semantics of === (in the same way as == in GopherJS transpiles to ===) . It's possible that I misunderstood what @hajimehoshi observed above if that's not the case...

What exactly do you mean?

By doing things like:

js.Global.Get("Object").Call("is", v1, v2).Bool()

(although that said I'm not sure if there are JS function equivalents of == and ===, so maybe this breaks down...unless we resort to using eval).

Afaik there are no JS function equivalents of == and ===. Also, checking two values for equality is a common case, so it should be part of the core API.

I just got an initial idea on how to make Go's == work for js.Value. This solution might also give performance benefits in other situations.

Chatted to @neelance about this offline so just adding some points here for the record.

I think what we need to do here is make Go's comparison == work for js.Value's following the semantics of === in Javascript. Per https://github.com/golang/go/issues/25802#issuecomment-399160190 @neelance thinks he has a solution for that.

func TestEquals(t *testing.T) {
    a := js.Global.Call("eval", "5")
    b := js.Global.Call("eval", "5")
    cmp := js.Global.Call("eval", "(function(a, b) { return a === b; })")
    if !cmp.Invoke(a, b).Bool() {
        t.Fatalf("Javascript === should be true") // ok
    }
    if a != b {
        t.Fatalf("should compare equal in Go") // currently fails
    }
}

This will, I think, then mean we don't need to add Equal to the API (which is a derivative "win", the main point here being that we "need" Go's == to work for js.Value).

If users want Javascript's == or Object.is semantics they can then use the eval "trick" to do just that.

Change https://golang.org/cl/120561 mentions this issue: syscall/js: use stable references to JavaScript values

Was this page helpful?
0 / 5 - 0 ratings