Source file src/syscall/js/func.go
1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build js && wasm 6 7 package js 8 9 import "sync" 10 11 var ( 12 funcsMu sync.Mutex 13 funcs = make(map[uint32]func(Value, []Value) any) 14 nextFuncID uint32 = 1 15 ) 16 17 // Func is a wrapped Go function to be called by JavaScript. 18 type Func struct { 19 Value // the JavaScript function that invokes the Go function 20 id uint32 21 } 22 23 // FuncOf returns a function to be used by JavaScript. 24 // 25 // The Go function fn is called with the value of JavaScript's "this" keyword and the 26 // arguments of the invocation. The return value of the invocation is 27 // the result of the Go function mapped back to JavaScript according to ValueOf. 28 // 29 // Invoking the wrapped Go function from JavaScript will 30 // pause the event loop and spawn a new goroutine. 31 // Other wrapped functions which are triggered during a call from Go to JavaScript 32 // get executed on the same goroutine. 33 // 34 // As a consequence, if one wrapped function blocks, JavaScript's event loop 35 // is blocked until that function returns. Hence, calling any async JavaScript 36 // API, which requires the event loop, like fetch (http.Client), will cause an 37 // immediate deadlock. Therefore a blocking function should explicitly start a 38 // new goroutine. 39 // 40 // Func.Release must be called to free up resources when the function will not be invoked any more. 41 func FuncOf(fn func(this Value, args []Value) any) Func { 42 funcsMu.Lock() 43 id := nextFuncID 44 nextFuncID++ 45 funcs[id] = fn 46 funcsMu.Unlock() 47 return Func{ 48 id: id, 49 Value: jsGo.Call("_makeFuncWrapper", id), 50 } 51 } 52 53 // Release frees up resources allocated for the function. 54 // The function must not be invoked after calling Release. 55 // It is allowed to call Release while the function is still running. 56 func (c Func) Release() { 57 funcsMu.Lock() 58 delete(funcs, c.id) 59 funcsMu.Unlock() 60 } 61 62 // setEventHandler is defined in the runtime package. 63 func setEventHandler(fn func()) 64 65 func init() { 66 setEventHandler(handleEvent) 67 } 68 69 func handleEvent() { 70 cb := jsGo.Get("_pendingEvent") 71 if cb.IsNull() { 72 return 73 } 74 jsGo.Set("_pendingEvent", Null()) 75 76 id := uint32(cb.Get("id").Int()) 77 if id == 0 { // zero indicates deadlock 78 select {} 79 } 80 funcsMu.Lock() 81 f, ok := funcs[id] 82 funcsMu.Unlock() 83 if !ok { 84 Global().Get("console").Call("error", "call to released function") 85 return 86 } 87 88 this := cb.Get("this") 89 argsObj := cb.Get("args") 90 args := make([]Value, argsObj.Length()) 91 for i := range args { 92 args[i] = argsObj.Index(i) 93 } 94 result := f(this, args) 95 cb.Set("result", result) 96 } 97