Technologie WebAssembly a GopherJS: předávání argumentů mezi Go a JavaScriptem

16. 1. 2025
Doba čtení: 30 minut

Sdílet

Autor: Depositphotos
Popíšeme si předávání hodnot mezi javascriptovým kódem na jedné straně a funkcemi a metodami naprogramovanými v jazyce Go na straně druhé. Kvůli odlišnému typovému systému obou jazyků se jedná o netriviální problém.

Obsah

1. Technologie WebAssembly a GopherJS: předávání argumentů mezi Go a JavaScriptem

2. Typový systém programovacího jazyka JavaScript

3. Typový systém programovacího jazyky Go

4. Zavolání funkce naprogramované v Go z JavaScriptu

5. První demonstrační příklad: zavolání Go funkce bez předání argumentů

6. Typ argumentů funkce volané z JavaScriptu

7. Druhý demonstrační příklad: výpis všech argumentů předaných funkci volané z JavaScriptu

8. Třetí demonstrační příklad: výpis typu argumentů, které jsou obaleny strukturou js.Value

9. Metoda js.Value.Type() a identifikace typu js.Type

10. Čtvrtý demonstrační příklad: vylepšené řešení tisku argumentů funkce volané z JavaScriptu

11. Konverze předaných argumentů na hodnoty kompatibilní s jazykem Go

12. Dostupné konverzní metody

13. Pátý demonstrační příklad: realizace předání a konverze dvou argumentů typu celé číslo

14. Šestý demonstrační příklad: realizace předání a konverze dvou argumentů typu double

15. Vrácení hodnot z Go do volajícího programu v JavaScriptu

16. Explicitní převod na JavaScriptovou hodnotu

17. Sedmý demonstrační příklad: vrácení hodnoty typu int do JavaScriptu

18. Osmý demonstrační příklad: zjednodušené vrácení hodnoty typu int do JavaScriptu

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Technologie WebAssembly a GopherJS: předávání argumentů mezi Go a JavaScriptem

Na článek GopherJS: transpřekladač z jazyka Go do JavaScriptu, který vyšel minulý týden, dnes navážeme. Řekneme si totiž, jakým způsobem se vlastně předávají hodnoty (argumenty) mezi kódem napsaným v JavaScriptu na jedné straně a funkcemi a metodami naprogramovanými v jazyce Go na straně druhé. To se sice může na první pohled zdát jako triviální úloha, ovšem kvůli tomu, že se typové systémy obou programovacích jazyků v mnoha ohledech odlišují, vyžaduje předávání hodnot poměrně mnoho podpůrného kódu na straně jazyka Go (minimálně v současné verzi Go).

Nejprve si řekneme, jak se předávají hodnoty jednoduchých (primitivních) datových typů a příště se zaměříme na složené datové typy, tedy zejména pole, řezy, mapy, struktury a obecně „objekty“ (i když tento pojem v Go vlastně, striktně řečeno, neexistuje; ovšem v JavaScriptu je prakticky vše kromě primitivní hodnoty objektem).

Poznámka: i když si budeme vše ukazovat na příkladech překládaných pomocí transpřekladače GopherJS do JavaScriptu, naprosto stejná technologie se používá i tehdy, pokud se zdrojové kódy napsané v Go překládají do WebAssembly. Shodné jsou jak parametry funkcí, tak i pravidla typových konverzí.

2. Typový systém programovacího jazyka JavaScript

Nejprve se, prozatím ve stručnosti, podívejme na typový systém programovacího jazyka JavaScript. Najdeme v něm sedm primitivních datových typů a dále typ Object:

(totéž platí i pro zápornou mezní hodnotu)

# Datový typ Stručný popis
1 Boolean datový typ se dvěma možnými hodnotami truefalse
2 String sekvence znaků (jediný znak je taktéž považován za řetězec)
3 Number reprezentuje hodnoty typu double podle IEEE 754; řada celých čísel je tedy omezena mezní hodnotou 253-1 (vlastně šířkou mantisy, i když skutečnost je nepatrně složitější, dtto pro zápornou mezní hodnotu)
4 Bigint speciální typ celočíselných numerických hodnot používaných v případě, že se ukládají hodnoty větší než 253-1
5 Null typ s jedinou hodnotou null
6 Undefined specialita JavaScriptu (a bolehlav), představuje ještě nepřiřazenou hodnotu (pozor: rozdílné od Null)
7 Symbol unikátní identifikátor
8 Object objekty (je to na první pohled zvláštní, ale sem spadají například i funkce)
Poznámka: pole a slovníky (asociativní pole) v této hierarchii spadají do poslední skupiny – jedná se tedy o objekty.

3. Typový systém programovacího jazyky Go

Typový systém programovacího jazyka Go se od JavaScriptu poměrně značně odlišuje, což je ostatně patrné již při pohledu na počet dostupných standardních typů a například na striktní rozdělení celočíselných datových typů od numerických typů s plovoucí řádovou čárkou (floating point). Celou hierarchii typového systému programovacího jazyka Go, která obsahuje všechny v současnosti podporované datové typy i jejich základní vztahy, si můžeme vizualizovat následujícím způsobem:

  • Jednoduché datové typy
    • Ordinální
      • Pravdivostní typ (boolean)
      • Celočíselné typy (integer)
      • Kód znaku v Unicode (rune) jako speciální případ celočíselného typu
    • Neordinální
      • Hodnoty s plovoucí řádovou čárkou (float)
      • Komplexní čísla (complex)
  • Složené datové typy
    • Řetězce (string)
    • Pole (array)
    • Řezy (slice)
    • Mapy (map)
    • Záznamy (struct)
  • Zvláštní datové typy
    • Ukazatel (pointer)
    • Funkce (function) – ano, to je taktéž plnohodnotný datový typ
    • Rozhraní (interface)
    • Kanál (channel)
Poznámka: k tomu musíme připočítat podporu pro generické datové typy, které jsme si popsali v článku Dlouho očekávaná novinka v Go 1.18 – generické datové typy a kterými se v dnešním článku již nebudeme zabývat.

4. Zavolání funkce naprogramované v Go z JavaScriptu

Již v předchozím článku jsme si ukázali, jakým způsobem je možné z JavaScriptu zavolat funkci naprogramovanou v jazyce Go (s jejím následným transpřekladem do JavaScriptu či do WebAssembly). Takové funkci se předává argument this (lze tedy realizovat volání metody) a dále řez libovolných JavaScriptových hodnot, přesněji řečeno hodnot, které jsou obaleny do typu syscall/js.Value (což si vysvětlíme v navazující textu). Taková funkce navíc může vracet libovolnou hodnotu, protože jméno any je typ odpovídající prázdnému rozhraní (interface{}):

// funkce, která se bude z JavaScriptu tak, jakoby
// se jednalo o nativní JavaScriptovou funkci
func MojeFunkce(this js.Value, args []js.Value) any {
 
        // je nutné vrátit nějakou hodnotu
        return nil
}

Tuto funkci je nutné zaregistrovat tak, aby byla viditelná z JavaScriptového virtuálního stroje. Přitom lze specifikovat jméno funkce z pohledu JavaScriptu (může být odlišné od jména funkce v Go):

js.Global().Set("jmenoFukce", js.FuncOf(PrintHello))

5. První demonstrační příklad: zavolání Go funkce bez předání argumentů

V dnešním prvním demonstračním příkladu je ukázáno, jak lze z JavaScriptu zavolat funkci naprogramovanou v Go a následně transpřeloženou do JavaScriptu nebo do WebAssembly. Této funkci prozatím nepředáváme žádné argumenty a funkce nevrací žádnou „rozumnou“ hodnotu, takže se jedná o ten nejjednodušší možný případ:

// Technologie WebAssembly a GopherJS
//
// - rozhraní mezi jazyky Go a JavaScript
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func PrintHello(this js.Value, args []js.Value) any {
        fmt.Println("function PrintHello called")
 
        // je nutné vrátit nějakou hodnotu
        return nil
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce PrintHello tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("printHello", js.FuncOf(PrintHello))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

Tato funkce je do virtuálního stroje JavaScriptu zaregistrována pod jménem „printHello“ a pod tímto jménem je i zavolána:

<!doctype html>
<html>
    <head>
        <title>Function call: without arguments</title>
    </head>
    <body>
        <h1 id="header">Function call: without arguments</h2>
        <script src="func_call_no_arguments.js" type="text/javascript"></script>
        <script type="text/javascript">
            printHello();
        </script>
    </body>
</html>

Výsledek je zobrazen v konzoli webového prohlížeče:

Obrázek 1: Funkce naprogramovaná v Go je skutečně zavolána z JavaScriptu.

6. Typ argumentů funkce volané z JavaScriptu

Podívejme se ještě jednou na hlavičku funkce PrintHello(), kterou je možné (po registraci) volat i z JavaScriptu:

func PrintHello(this js.Value, args []js.Value) any {
 
        // je nutné vrátit nějakou hodnotu
        return nil
}

Této funkci se předává jeden parametr this a dále řez (tedy dopředu neznámý počet prvků). V obou případech je ovšem použit totožný typ js.Value deklarovaný v balíčku syscall/js. S vlastnostmi tohoto typu se podrobněji seznámíme v navazujícím textu, už nyní je ale vhodné si vypsat všechny jeho viditelné metody (interní strukturu nemáme k dispozici):

func (v Value) Bool() bool
func (v Value) Call(m string, args ...any) Value
func (v Value) Delete(p string)
func (v Value) Equal(w Value) bool
func (v Value) Float() float64
func (v Value) Get(p string) Value
func (v Value) Index(i int) Value
func (v Value) InstanceOf(t Value) bool
func (v Value) Int() int
func (v Value) Invoke(args ...any) Value
func (v Value) IsNaN() bool
func (v Value) IsNull() bool
func (v Value) IsUndefined() bool
func (v Value) Length() int
func (v Value) New(args ...any) Value
func (v Value) Set(p string, x any)
func (v Value) SetIndex(i int, x any)
func (v Value) String() string
func (v Value) Truthy() bool
func (v Value) Type() Type

V balíčku syscall/js je deklarována je i čtveřice funkcí, které dovolují hodnotu typu js.Value vytvořit či nějakým způsobem získat:

func Global() Value
func Null() Value
func Undefined() Value
func ValueOf(x any) Value

7. Druhý demonstrační příklad: výpis všech argumentů předaných funkci volané z JavaScriptu

Ukažme si nyní, jakým postupem se zobrazí počet a hodnoty všech argumentů, které jsou předány funkci naprogramované v jazyce Go, jež je volána z JavaScriptu. Je to ve skutečnosti snadné. Počet argumentů se zjistí standardní funkcí len(), protože všechny argumenty jsou předány v jediném řezu. A hodnoty argumentů můžeme zobrazit funkcí fmt.Printf v případě, že použijeme formátovací znak %v. Ten způsobí, že se hodnoty (interně libovolného typu) převedenou na řetězec a ten se následně zobrazí.

V Go tedy můžeme napsat funkci PrintArguments, která provede výše uvedené operace:

// Technologie WebAssembly a GopherJS
//
// - rozhraní mezi jazyky Go a JavaScript
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func PrintArguments(this js.Value, args []js.Value) any {
        fmt.Println("function PrintArguments called")
        fmt.Printf("# of parameters: %d\n", len(args))
 
        for i, arg := range args {
                fmt.Printf("parameter # %d: %v\n", i, arg)
        }
 
        // je nutné vrátit nějakou hodnotu
        return nil
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce PrintArguments tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("printArguments", js.FuncOf(PrintArguments))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

Tuto funkci několikrát zavoláme z JavaScriptového kódu napsaného přímo v HTML stránce. Povšimněte si, že předáváme různý počet argumentů s různými typy:

<!doctype html>
<html>
    <head>
        <title>Function call: with arguments</title>
    </head>
    <body>
        <h1 id="header">Function call: with arguments</h2>
        <script src="func_call_with_arguments.js" type="text/javascript"></script>
        <script type="text/javascript">
            printArguments();
            console.log();
 
            printArguments(1);
            console.log();
 
            printArguments(1, 2);
            console.log();
 
            printArguments("foo", "bar", "baz");
            console.log();
 
            printArguments(true, false);
            console.log();
 
            printArguments(null);
            console.log();
 
            printArguments(undefined);
            console.log();
        </script>
    </body>
</html>

Výsledek bude opět viditelný v konzoli webového prohlížeče:

Obrázek 2: Naše testovací funkce byla několikrát zavolána s různým počtem i hodnotami argumentů.

8. Třetí demonstrační příklad: výpis typu argumentů, které jsou obaleny strukturou js.Value

V dnešním třetím demonstračním příkladu je ukázán ten nejjednodušší dostupný způsob zjištění konkrétního typu argumentu, který je předán z JavaScriptu do funkce naprogramované v jazyce Go. Už z předchozího příkladu je totiž patrné, že hodnoty typu js.Value vlastně pouze vhodným způsobem „obalují“ konkrétní hodnoty. Pokusme se tedy typy argumentů vypsat tak, že použijeme funkci fmt.Printf společně s formátovacím znakem %T

for i, arg := range args {
        fmt.Printf("parameter # %d with type %T: %v\n", i, arg, arg)
}

Celý zdrojový kód napsaný v jazyce Go se tedy do značné míry podobá předchozímu příkladu, liší se jen způsobem výpisu argumentů:

// Technologie WebAssembly a GopherJS
//
// - rozhraní mezi jazyky Go a JavaScript
// - metoda PrintArguments vytiskne typy svých argumentů
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func PrintArguments(this js.Value, args []js.Value) any {
        fmt.Println("function PrintArguments called")
        fmt.Printf("# of parameters: %d\n", len(args))
 
        for i, arg := range args {
                fmt.Printf("parameter # %d with type %T: %v\n", i, arg, arg)
        }
 
        // je nutné vrátit nějakou hodnotu
        return nil
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce PrintArguments tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("printArguments", js.FuncOf(PrintArguments))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

Z HTML stránky opět funkci PrintArguments (resp. z pohledu JavaScriptu spíše printArguments) budeme volat s různými počty a typy argumentů:

<!doctype html>
<html>
    <head>
        <title>Function call: with arguments</title>
    </head>
    <body>
        <h1 id="header">Function call: with arguments</h2>
        <script src="func_call_argument_types_1.js" type="text/javascript"></script>
        <script type="text/javascript">
            printArguments();
            console.log();
 
            printArguments(1);
            console.log();
 
            printArguments(1, 2);
            console.log();
 
            printArguments("foo", "bar", "baz");
            console.log();
 
            printArguments(true, false);
            console.log();
 
            printArguments(null);
            console.log();
 
            printArguments(undefined);
            console.log();
        </script>
    </body>
</html>

Podívejme se na výsledky vypsané tímto demonstračním příkladem do webové konzole:

Obrázek 3: Hodnoty i typy parametrů vypsané třetím demonstračním příkladem do konzole webového prohlížeče.

9. Metoda js.Value.Type() a identifikace typu js.Type

V mnoha situacích je nutné konkrétní hodnotu předaných argumentů nejenom vytisknout na standardní výstup, ale získat v podobě vhodné pro jejich další zpracování (například pro provedení konverze atd.). Přesně k tomuto účelu slouží metoda Type datového typu js.Value. Pro každý předaný argument (jak this, tak i pro každý prvek předaného řezu) tedy můžeme zavolat tuto metodu:

func (v Value) Type() Type

Tato metoda vrací hodnotu typu js.Type, což ovšem není nic jiného, než typ odvozený od celého čísla:

type Type int

Konkrétně se může vrátit jedna z následujících konstant:

const (
        TypeUndefined Type = iota
        TypeNull
        TypeBoolean
        TypeNumber
        TypeString
        TypeSymbol
        TypeObject
        TypeFunction
)
Poznámka: povšimněte si, že zde chybí typ BigInt, což však, jak uvidíme dále, nemusí vždy představovat praktický problém.

V aplikacích tedy můžeme velmi zjistit, jakého typu je libovolný z předaných argumentů. V současnosti je to vlastně jediná rozumná možnost, jak zahájit konverzi argumentů na hodnoty kompatibilní s jazykem Go, jak ostatně uvidíme níže.

10. Čtvrtý demonstrační příklad: vylepšené řešení tisku argumentů funkce volané z JavaScriptu

V dnešním čtvrtém demonstračním příkladu je ukázáno, jakým způsobem je možné realizovat vylepšené řešení získání a tisku typů argumentů funkce napsané v jazyku Go, která je volána z JavaScriptu. Pro získání typu argumentu použijeme metodu js.Value.Type(), která vrátí hodnotu, kterou můžeme snadno převést na řetězec zavoláním metody js.Type.String(). Tisk pozic, hodnot a typů předávaných argumentů tedy můžeme realizovat touto programovou smyčkou:

for i, arg := range args {
        fmt.Printf("parameter # %d with type '%s': %s\n",
                i,
                arg.Type().String(),
                arg.String())
}

Úplný zdrojový kód tohoto příkladu vypadá následovně:

// Technologie WebAssembly a GopherJS
//
// - rozhraní mezi jazyky Go a JavaScript
// - metoda PrintArguments vytiskne typy svých argumentů
// - lepší řešení založené na typu js.Type
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func PrintArguments(this js.Value, args []js.Value) any {
        fmt.Println("function PrintArguments called")
        fmt.Printf("# of parameters: %d\n", len(args))
 
        for i, arg := range args {
                fmt.Printf("parameter # %d with type '%s': %s\n",
                        i,
                        arg.Type().String(),
                        arg.String())
        }
 
        // je nutné vrátit nějakou hodnotu
        return nil
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce PrintArguments tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("printArguments", js.FuncOf(PrintArguments))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

Příslušná HTML stránka, z níž se volá zaregistrovaná funkce printArguments:

<!doctype html>
<html>
    <head>
        <title>Function call: with arguments</title>
    </head>
    <body>
        <h1 id="header">Function call: with arguments</h2>
        <script src="func_call_argument_types_2.js" type="text/javascript"></script>
        <script type="text/javascript">
            printArguments();
            console.log();
 
            printArguments(1);
            console.log();
 
            printArguments(1, 2);
            console.log();
 
            printArguments("foo", "bar", "baz");
            console.log();
 
            printArguments(true, false);
            console.log();
 
            printArguments(null);
            console.log();
 
            printArguments(undefined);
            console.log();
        </script>
    </body>
</html>

Opět se podívejme na výsledky vypsané tímto demonstračním příkladem do webové konzole:

Obrázek 4: Hodnoty i typy parametrů vypsané čtvrtým demonstračním příkladem do konzole webového prohlížeče.

11. Konverze předaných argumentů na hodnoty kompatibilní s jazykem Go

Z předchozí dvojice demonstračních příkladů je patrné, že hodnoty předávané do funkce naprogramované v jazyce Go (přes řez), nemají typy kompatibilní s jazykem Go. Abychom získali hodnoty základních datových typů, je nutné provést explicitní datové konverze. A ještě před provedením konverze je nutné zjistit, hodnota jakého typu je vlastně předávána. Není totiž možné provést konverzi z například JavaScriptového řetězce na celé číslo. Kontrola, jakého typu je předávaná hodnota, je relativně snadná, i když se (v současnosti) musí provádět ručně. Například pro i-tý předávaný argument můžeme zjistit, jestli se z pohledu JavaScriptu jedná o číslo (Number) či nikoli:

// přečíst typ i-tého argumentu
typ := args[i].Type()
 
// zkontrolovat, zda se předává numerická hodnota
if typ != js.TypeNumber {
        fmt.Printf("Argument #%d has incorrect type %s\n", index, typ.String())
        return nil
}
Poznámka: podobně lze provést testy na další typy, tedy na TypeNull, TypeBoolean, TypeString, TypeSymbol, TypeObject, TypeFunction.

12. Dostupné konverzní metody

Předávané argumenty jsou, jak již dobře víme, typu js.Value. Pro tento typ jsou definovány čtyři konverzní metody, které vrací přímo hodnotu nativního Go typu. Tyto metody postupně vrací pravdivostní hodnotu, celé číslo, číslo s plovoucí řádovou čárkou nebo řetězec. Pro ty typy JavaScriptu, které nemají přímou obdobu v Go, pak existují metody vracející pravdivostní hodnotu – zda se předává null, undefined nebo Not a Number (což je ale vlastně současně float64):

Metoda Návratový typ
func (v Value) Bool() bool
func (v Value) Int() int
func (v Value) Float() float64
func (v Value) String() string
   
func (v Value) IsNaN() bool
func (v Value) IsNull() bool
func (v Value) IsUndefined() bool
Poznámka: konverzní metody není vhodné volat bez předchozí kontroly datového typu. Je tomu tak z toho důvodu, že pokud se konverze nepovede (pokoušíme se převádět řetězec na int atd.), vyvolá se panic. A ten není pěkné přímo zachytávat v programovém kódu (není to idiomatické, na rozdíl od Pythonu).
Poznámka2: je poněkud nešťastné, že metoda Int() vrací hodnotu typu int a nikoli int64. Na 64bitových platformách to nepředstavuje problém, ale kód nebude přímo přenositelný na 32bitové platformy.

13. Pátý demonstrační příklad: realizace předání a konverze dvou argumentů typu celé číslo

V dnešním pátém demonstračním příkladu je ukázána realizace funkce PrintSum naprogramované v jazyce Go, která vyžaduje, aby byla z JavaScriptu zavolána s předáním dvojice numerických hodnot, které jsou následně zkontrolovány a poté zkonvertovány na celé číslo. Pokud jsou předány odlišné parametry (či pokud je jich předáno více či naopak méně), funkce vypíše chybové hlášení a je ukončena:

// Technologie WebAssembly a GopherJS
//
// - rozhraní mezi jazyky Go a JavaScript
// - kontrola počtu argumentů předaných funkci PrintSum
// - kontrola typu argumentů předaných funkci PrintSum
// - provedení konverze na nativní typy jazyka Go
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func PrintSum(this js.Value, args []js.Value) any {
        // kontrola počtu předaných argumentů
        if len(args) != 2 {
                fmt.Printf("incorrect number of arguments %d, but just two are accepted\n", len(args))
                return nil
        }
 
        // kontrola typu předaných argumentů
        // (pozor - původní syntaxe zápisu smyčky)
        for index := 0; index < 2; index++ {
                typ := args[index].Type()
                if typ != js.TypeNumber {
                        fmt.Printf("Argument #%d has incorrect type %s\n", index, typ.String())
                        return nil
                }
        }
 
        // počet i typ argumentů je korektní
        // lze tedy provést jejich konverzi
        x := args[0].Int()
        y := args[1].Int()
 
        // vypočítat výsledek
        z := x + y
 
        // zobrazit výsledek
        fmt.Printf("%d + %d = %d\n", x, y, z)
 
        // je nutné vrátit nějakou hodnotu
        return nil
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce PrintSum tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("printSum", js.FuncOf(PrintSum))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

Příslušná HTML stránka, z níž funkci PrintSum voláme s různými počty a typy argumentů, vypadá následovně:

<!doctype html>
<html>
    <head>
        <title>Function call: with two integer arguments</title>
    </head>
    <body>
        <h1 id="header">Function call: with two integer arguments</h2>
        <script src="func_call_two_ints.js" type="text/javascript"></script>
        <script type="text/javascript">
            // korektní argumenty
            printSum(1, 2);
            printSum(10, 20);
            printSum(100, 200);
            printSum(-1, -2);
 
            // špatný počet argumentů
            printSum(10, 10, 20);
            printSum(10);
 
            // špatný typ argumentů
            printSum("1", 2);
            printSum(1, "2");
            printSum(1, true);
            printSum(1, null);
 
            // mezní případ
            printSum(1.1, 2.2);
        </script>
    </body>
</html>

Obrázek 5: Funkce PrintSum vypisuje své výsledky i chybová hlášení do webové konzole.

14. Šestý demonstrační příklad: realizace předání a konverze dvou argumentů typu double

Šestý demonstrační příklad se do značné míry podobá příkladu předchozímu, ovšem namísto konverze předané dvojice argumentů na typ int se pokoušíme o konverzi na typ double, což by mělo být prakticky ve všech případech možné:

// Technologie WebAssembly a GopherJS
//
// - rozhraní mezi jazyky Go a JavaScript
// - kontrola počtu argumentů předaných funkci PrintSum
// - kontrola typu argumentů předaných funkci PrintSum
// - provedení konverze na nativní typy jazyka Go
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func PrintSum(this js.Value, args []js.Value) any {
        // kontrola počtu předaných argumentů
        if len(args) != 2 {
                fmt.Printf("incorrect number of arguments %d, but just two are accepted\n", len(args))
                return nil
        }
 
        // kontrola typu předaných argumentů
        // (pozor - původní syntaxe zápisu smyčky)
        for index := 0; index < 2; index++ {
                typ := args[index].Type()
                if typ != js.TypeNumber {
                        fmt.Printf("Argument #%d has incorrect type %s\n", index, typ.String())
                        return nil
                }
        }
 
        // počet i typ argumentů je korektní
        // lze tedy provést jejich konverzi
        x := args[0].Float()
        y := args[1].Float()
 
        // vypočítat výsledek
        z := x + y
 
        // zobrazit výsledek
        fmt.Printf("%g + %g = %g\n", x, y, z)
 
        // je nutné vrátit nějakou hodnotu
        return nil
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce PrintSum tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("printSum", js.FuncOf(PrintSum))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

Příslušná HTML stránka je opět prakticky totožná s příkladem předchozím, tedy:

<!doctype html>
<html>
    <head>
        <title>Function call: with two FP arguments</title>
    </head>
    <body>
        <h1 id="header">Function call: with two FP arguments</h2>
        <script src="func_call_two_floats.js" type="text/javascript"></script>
        <script type="text/javascript">
            // korektní argumenty
            printSum(1.1, 2.2);
            printSum(1e10, 1e10);
            printSum(1e100, 1e100);
 
            // špatný počet argumentů
            printSum(10, 10, 20);
            printSum(10);
 
            // špatný typ argumentů
            printSum("1", 2.0);
            printSum(1.0, "2");
            printSum(1.0, true);
            printSum(1.0, null);
        </script>
    </body>
</html>

Obrázek 6: Funkce PrintSum vypisuje své výsledky i chybová hlášení do webové konzole. Tentokrát jsou výpočty provedeny s hodnotami typu double.

15. Vrácení hodnot z Go do volajícího programu v JavaScriptu

V závěrečné části dnešního článku si vysvětlíme, jakým způsobem se vrací hodnoty z Go do JavaScriptu. Prozatím se omezíme na primitivní datové typy. Připomeňme si nejdříve, jak vypadá hlavička funkce naprogramované v jazyce Go, ale volatelné z JavaScriptu (po registraci takové funkce):

// funkce, která se bude z JavaScriptu tak, jakoby
// se jednalo o nativní JavaScriptovou funkci
func MojeFunkce(this js.Value, args []js.Value) any {
 
        // je nutné vrátit nějakou hodnotu
        return nil
}

Povšimněte si, že návratový typ je any, což odpovídá prázdnému rozhraní. Můžeme tedy vracet „cokoli“, ovšem samozřejmě i zde existují pravidla pro typovou konverzi mezi Go a JavaScriptem.

16. Explicitní převod na JavaScriptovou hodnotu

Funkce naprogramovaná v jazyce Go může do JavaScriptu vrátit výsledek své činnosti. Přitom se vrací jen jediná hodnota – nemůžeme zde tedy využít možnost nabízenou jazykem Go a vrátit větší množství hodnot. Typ návratové hodnoty funkce je any, což může být zpočátku matoucí. Ovšem pro převod hodnoty nějakého Go typu na JavaScriptovou hodnotu můžeme použít funkci z balíčku syscall/js, která se jmenuje ValueOf. Tato funkce převede (prakticky jakoukoli) hodnotu z Go na její JavaScriptový protějšek typu Value:

func ValueOf(x any) Value

Příklad použití pro proměnnou x, která může být teoreticky libovolného typu:

// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func Funkce(this js.Value, args []js.Value) any {
        ...
        ...
        ...
        // vrátit výsledek s explicitní konverzí
        return js.ValueOf(x)
}

17. Sedmý demonstrační příklad: vrácení hodnoty typu int do JavaScriptu

Podívejme se, jak by mohla vypadat realizace funkce pro součet hodnot dvou předaných argumentů. Nejedná se o krátký kód, protože musíme zkontrolovat počty a typy předávaných argumentů a následně provést jejich konverzi na primitivní typy jazyka Go (což již známe). A výsledek součtu konvertujeme zpět na JavaScriptovou hodnotu funkcí js.ValueOf. Tato hodnota se následně z Go funkce vrací a je zpracována virtuálním strojem JavaScriptu:

// Technologie WebAssembly
//
// - rozhraní mezi jazyky Go a JavaScript
// - kontrola počtu argumentů předaných funkci CalcSum
// - kontrola typu argumentů předaných funkci CalcSum
// - provedení konverze na nativní typy jazyka Go
// - převedení výsledku zpět na JavaScriptový typ
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func CalcSum(this js.Value, args []js.Value) any {
        // kontrola počtu předaných argumentů
        if len(args) != 2 {
                fmt.Printf("incorrect number of arguments %d, but just two are accepted\n", len(args))
                return nil
        }
 
        // kontrola typu předaných argumentů
        // (pozor - původní syntaxe zápisu smyčky)
        for index := 0; index < 2; index++ {
                typ := args[index].Type()
                if typ != js.TypeNumber {
                        fmt.Printf("Argument #%d has incorrect type %s\n", index, typ.String())
                        return nil
                }
        }
 
        // počet i typ argumentů je korektní
        // lze tedy provést jejich konverzi
        x := args[0].Int()
        y := args[1].Int()
 
        // vypočítat výsledek
        z := x + y
 
        // vrátit výsledek s explicitní konverzí
        return js.ValueOf(z)
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce CalcSum tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("calcSum", js.FuncOf(CalcSum))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

Do příslušné HTML stránky je vloženo několik volání funkce calcSum se zobrazením jejího výsledku (návratové hodnoty) do webové konzole:

<!doctype html>
<html>
    <head>
        <title>Function call: returning integer value</title>
    </head>
    <body>
        <h1 id="header">Function call: returning integer value</h2>
        <script src="func_call_return_int_1.js" type="text/javascript"></script>
        <script type="text/javascript">
            <i>// korektní argumenty</i>
            console.log(calcSum(1, 2));
            console.log(calcSum(10, 20));
            console.log(calcSum(100, 200));
            console.log(calcSum(-1, -2));
        </script>
    </body>
</html>

A takto by měly vypadat vypočtené výsledky:

Obrázek 7: Výsledky součtu různých hodnot zobrazené ve webové konzoli.

18. Osmý demonstrační příklad: zjednodušené vrácení hodnoty typu int do JavaScriptu

Ve skutečnosti je rozhraní mezi Go a jazykem JavaScript realizováno takovým způsobem, že z funkce naprogramované v Go je možné vracet libovolnou hodnotu bez jejího explicitního převodu pomocí konverzní funkce js.ValueOf. Ukázkový příklad z předchozí kapitoly lze tedy nepatrně zjednodušit do podoby, kdy z Go funkce přímo vracíme hodnotu proměnné typu int:

// Technologie WebAssembly
//
// - rozhraní mezi jazyky Go a JavaScript
// - kontrola počtu argumentů předaných funkci CalcSum
// - kontrola typu argumentů předaných funkci CalcSum
// - provedení konverze na nativní typy jazyka Go
// - automatická konverze výsledku zpět na JavaScriptový typ
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func CalcSum(this js.Value, args []js.Value) any {
        // kontrola počtu předaných argumentů
        if len(args) != 2 {
                fmt.Printf("incorrect number of arguments %d, but just two are accepted\n", len(args))
                return nil
        }
 
        // kontrola typu předaných argumentů
        // (pozor - původní syntaxe zápisu smyčky)
        for index := 0; index < 2; index++ {
                typ := args[index].Type()
                if typ != js.TypeNumber {
                        fmt.Printf("Argument #%d has incorrect type %s\n", index, typ.String())
                        return nil
                }
        }
 
        // počet i typ argumentů je korektní
        // lze tedy provést jejich konverzi
        x := args[0].Int()
        y := args[1].Int()
 
        // vypočítat výsledek
        z := x + y
 
        // vrátit výsledek s automatickou konverzí
        return z
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce CalcSum tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("calcSum", js.FuncOf(CalcSum))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

HTML stránka zůstane totožná, jako tomu bylo v příkladu předchozím, pouze pochopitelně importujeme jiný (transpilovaný) zdrojový kód:

<!doctype html>
<html>
    <head>
        <title>Function call: returning integer value</title>
    </head>
    <body>
        <h1 id="header">Function call: returning integer value</h2>
        <script src="func_call_return_int_2.js" type="text/javascript"></script>
        <script type="text/javascript">
            <i>// korektní argumenty</i>
            console.log(calcSum(1, 2));
            console.log(calcSum(10, 20));
            console.log(calcSum(100, 200));
            console.log(calcSum(-1, -2));
        </script>
    </body>
</html>

Obrázek 8: Výsledky součtu různých hodnot zobrazené ve webové konzoli.

hacking_tip

19. Repositář s demonstračními příklady

Demonstrační příklady napsané v jazyce Go, které jsou určené pro transpřeklad do JavaScriptu s využitím nástroje GopherJS, byly uloženy do Git repositáře, jenž je dostupný na adrese https://github.com/RedHatOf­ficial/GoCourse. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

# Příklad Stručný popis Adresa
1 func_call_no_arguments.go první demonstrační příklad: zavolání Go funkce bez předání argumentů https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_no_argumen­ts.go
2 func_call_no_arguments.html HTML stránka s kódem pro načtení prvního demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_no_argumen­ts.html
       
3 func_call_with_arguments.go druhý demonstrační příklad: výpis všech argumentů předaných funkci volané z JavaScriptu https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_with_argu­ments.go
4 func_call_with_arguments.html HTML stránka s kódem pro načtení druhého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_with_argu­ments.html
       
5 func_call_argument_types1.go třetí demonstrační příklad: výpis typu argumentů, které jsou obaleny strukturou js.Value https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_argument_ty­pes1.go
6 func_call_argument_types1.html HTML stránka s kódem pro načtení třetího demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_argument_ty­pes1.html
       
7 func_call_argument_types2.go čtvrtý demonstrační příklad: vylepšené řešení tisku argumentů funkce volané z JavaScriptu https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_argument_ty­pes2.go
8 func_call_argument_types2.html HTML stránka s kódem pro načtení čtvrtého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_argument_ty­pes2.html
       
9 func_call_two_ints.go pátý demonstrační příklad: realizace předání a konverze dvou argumentů typu celé číslo https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_two_ints.go
10 func_call_two_ints.html HTML stránka s kódem pro načtení pátého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_two_ints.html
       
11 func_call_two_floats.go šestý demonstrační příklad: realizace předání a konverze dvou argumentů typu double https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_two_float­s.go
12 func_call_two_floats.html HTML stránka s kódem pro načtení šestého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_two_float­s.html
       
13 func_call_return_int1.go sedmý demonstrační příklad: vrácení hodnoty typu int do JavaScriptu https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_return_in­t1.go
14 func_call_return_int1.html HTML stránka s kódem pro načtení sedmého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_return_in­t1.html
       
15 func_call_return_int2.go osmý demonstrační příklad: vrácení hodnoty typu int do JavaScriptu https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_return_in­t2.go
16 func_call_return_int2.html HTML stránka s kódem pro načtení osmého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/func_call_return_in­t2.html

Pro úplnost si uveďme i odkazy na ukázkové příklady použité minule:

# Příklad Stručný popis Adresa
1 hello_world.go zdrojový kód prvního demonstračního příkladu: výpis zprávy na konzoli webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/hello_world.go
1 hello_world.html HTML stránka s kódem pro načtení prvního demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/hello_world.html
       
2 dom_manipulation.go zdrojový kód druhého demonstračního příkladu: manipulace s DOMem webové stránky https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/dom_manipulation.go
2 dom_manipulation.html HTML stránka s kódem pro načtení druhého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/dom_manipulation.html
       
3 dom_add_element.go zdrojový kód třetího demonstračního příkladu: přidání elementů do DOMu webové stránky https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/dom_add_element.go
3 dom_add_element.html HTML stránka s kódem pro načtení třetího demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/dom_add_element.html
       
4 draw_into_canvas.go zdrojový kód čtvrtého demonstračního příkladu: kreslení do canvasu https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/draw_into_canvas.go
4 draw_into_canvas.html HTML stránka s kódem pro načtení čtvrtého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/draw_into_canvas.html
       
5 js_interop1.go zdrojový kód pátého demonstračního příkladu: komunikace s JavaScriptem https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/js_interop1.go
5 js_interop1.html HTML stránka s kódem pro načtení pátého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/js_interop1.html
       
6 js_interop2.go zdrojový kód šestého demonstračního příkladu: komunikace s JavaScriptem https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/js_interop2.go
6 js_interop2.html HTML stránka s kódem pro načtení šestého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/js_interop2.html
       
7 http_server.go implementace HTTP serveru, který dokáže webovému prohlížeči předávat obsah požadovaných souborů https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/http_server.go
       
8 hello_world2.go varianta programu typu „Hello, world!“, která volá pouze funkci println() https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/hello_world2.go

20. Odkazy na Internetu

  1. go2js
    https://github.com/tredoe/go2js
  2. GitHub repositář projektu GopherJS
    https://github.com/gopherjs/gopherjs
  3. How to use GopherJS to turn Go code into a JavaScript library
    https://medium.com/@kentquirk/how-to-use-gopherjs-to-turn-go-code-into-a-javascript-library-1e947703db7a
  4. Source to source compiler
    https://en.wikipedia.org/wiki/Source-to-source_compiler
  5. Binary recompiler
    https://en.wikipedia.org/wi­ki/Binary_recompiler
  6. py2many na GitHubu
    https://github.com/py2many/py2many
  7. py2many na PyPi
    https://pypi.org/project/py2many/
  8. Awesome Transpilers
    https://github.com/milahu/awesome-transpilers
  9. WebAssembly
    https://webassembly.org/
  10. WebAssembly na Wiki Golangu
    https://github.com/golang/go/wi­ki/WebAssembly
  11. The future of WebAssembly – A look at upcoming features and proposals
    https://blog.scottlogic.com/2018/07/20/wasm-future.html
  12. Writing WebAssembly By Hand
    https://blog.scottlogic.com/2018/04/26/we­bassembly-by-hand.html
  13. WebAssembly Specification
    https://webassembly.github­.io/spec/core/index.html
  14. Index of Instructions
    https://webassembly.github­.io/spec/core/appendix/in­dex-instructions.html
  15. The WebAssembly Binary Toolkit
    https://github.com/WebAssembly/wabt
  16. Will WebAssembly replace JavaScript? Or Will WASM Make JavaScript More Valuable in Future?
    https://dev.to/vaibhavshah/will-webassembly-replace-javascript-or-will-wasm-make-javascript-more-valuable-in-future-5c6e
  17. Roadmap (pro WebAssemly)
    https://webassembly.org/roadmap/
  18. Transcrypt
    https://transcrypt.org/
  19. JavaScript Data Types
    https://www.geeksforgeeks­.org/javascript-data-types/
  20. Standardní balíček syscall/js
    https://pkg.go.dev/syscall/js
  21. Data types
    https://javascript.info/types
  22. Datové typy (napsáno poněkud zjednodušeně)
    https://naucme.it/chapter/qa-04
  23. Primitive (JavaScript)
    https://developer.mozilla.org/en-US/docs/Glossary/Primitive
  24. JavaScript type: String
    https://developer.mozilla.org/en-US/docs/Glossary/String
  25. JavaScript type: Number
    https://developer.mozilla.org/en-US/docs/Glossary/Number
  26. JavaScript type: Boolean
    https://developer.mozilla.org/en-US/docs/Glossary/Boolean
  27. JavaScript type: Undefined
    https://developer.mozilla.org/en-US/docs/Glossary/Undefined
  28. JavaScript type: Null
    https://developer.mozilla.org/en-US/docs/Glossary/Null
  29. JavaScript type: Symbol
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Re­ference/Global_Objects/Sym­bol
  30. JavaScript type: BigInt
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Re­ference/Global_Objects/Bi­gInt

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.