ctx.SetAsyncFunc("asyncFunction", func(this *qjs.This) {
go func() {
time.Sleep(100 * time.Millisecond)
result := this.Context().NewString("Async result from Go!")
this.Promise().Resolve(result)
}()
})
然后JS await它:
async function main() {
const result = await asyncFunction();
return result;
}
({ main: main() });
QJS是一个不依赖CGO的现代JavaScript运行时,用于Go语言,它将QuickJS引擎嵌入到WebAssembly模块中,并使用Wazero运行它,为Go应用程序提供了一个带有async/await和和紧密Go-JS互操作性的沙箱化ES2023环境。
QJS的目标是那些希望在Go进程中运行现代JavaScript而不链接本地C库的Go开发人员。它没有直接通过CGO绑定QuickJS,而是将QuickJS-NG编译为WebAssembly,并在Wazero下执行,提供:
完整的ES2023支持(模块、async/await、BigInt等)。一个完全沙箱化、内存安全的执行模型。无需CGO工具链或C运行时依赖。
该运行时与Go 1.22+兼容,并作为常规Go模块分发:
go get github.com/fastschema/qjs然后:
import "github.com/fastschema/qjs"QJS公开了一个 Runtime 和 Context API",允许Go代码评估JavaScript、绑定函数和交换数据结构。一个最简单的示例创建了一个运行时,计算脚本,并将结构化结果读回到Go中:
rt, err := qjs.New() if err != nil { log.Fatal(err) } defer rt.Close() ctx := rt.Context() result, err := ctx.Eval("test.js", qjs.Code(` const person = { name: "Alice", age: 30, city: "New York" }; const info = Object.keys(person).map(key => key + ": " + person[key] ).join(", "); ({ person: person, info: info }); )) if err != nil { log.Fatal("Eval error:", err) } defer result.Free() log.Println(result.GetPropertyStr("info").String()) log.Println(result.GetPropertyStr("person").GetPropertyStr("name").String()) log.Println(result.GetPropertyStr("person").GetPropertyStr("age").Int32())Go函数可以暴露给JavaScript,JS函数可以转换回类型化的Go可调用函数。例如,绑定一个Go函数:
ctx.SetFunc("goFunction", func(this qjs.This) (qjs.Value, error) { return this.Context().NewString("Hello from Go!"), nil }) result, err := ctx.Eval("test.js", qjs.Code( const message = goFunction(); message; )) if err != nil { log.Fatal("Eval error:", err) } defer result.Free() log.Println(result.String()) // Hello from Go!QJS还支持将更丰富的Go结构体转换为JS值,包括可以从JavaScript调用的方法,然后反序列化为类型化的Go值。
为了避免重复序列化大型或不透明的Go对象,QJS引入了Proxy",这是一个轻量级的JavaScript包装器,只保存对Go值的引用。这对于上下文、数据库句柄或JS不需要检查就可以通过的大型结构体来说很有用:
ctx.SetFunc("$context", func(this qjs.This) (qjs.Value, error) { passContext := context.WithValue(context.Background(), "key", "value123") val := ctx.NewProxyValue(passContext) return val, nil }) goFuncWithContext := func(c context.Context, num int) int { log.Println("Context value:", c.Value("key")) return num * 2 }JavaScript接收代理并将其传回Go,其中JsValueToGo恢复底层值和类型。QJS通过允许Go异步解决JS承诺来支持async/await。一个Go异步函数可以调度工作并解决一个承诺:
ctx.SetAsyncFunc("asyncFunction", func(this *qjs.This) { go func() { time.Sleep(100 * time.Millisecond) result := this.Context().NewString("Async result from Go!") this.Promise().Resolve(result) }() })然后JS await它:
async function main() { const result = await asyncFunction(); return result; } ({ main: main() });该运行时可用于在JavaScript中实现HTTP处理程序,同时保持服务器在Go中。例如, /about 和 /contact 路由在JS中定义,预编译为字节码,并从运行时池中执行:
byteCode := must(ctx.Compile("script.js", qjs.Code(script), qjs.TypeModule())) pool := qjs.NewPool(3, &qjs.Option{}, func(r *qjs.Runtime) error { results := must(r.Context().Eval("script.js", qjs.Bytecode(byteCode), qjs.TypeModule())) r.Context().Global().SetPropertyStr("handlers", results) return nil }) http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) { runtime := must(pool.Get()) defer pool.Put(runtime) handlers := runtime.Context().Global().GetPropertyStr("handlers") result := must(handlers.InvokeJS("about")) fmt.Fprint(w, result.String()) result.Free() })计算阶乘(10)1,000,000次
AreWeFastYet V8-V7
通过这种设计,QJS的目标是那些需要安全的插件系统、用户提供的脚本或用JavaScript编写的嵌入式业务逻辑,而不需要将C工具链或CGO引入构建和部署流水线的Go开发人员。
原文链接:
https://www.infoq.com/news/2025/12/javascript-golang-wasm/"