如何在Golang中通过v8go、Otto运行带Fetch请求的JavaScript代码?
Golang嵌入式JS环境支持fetch调用实现方案
不管是Otto还是v8go,本质都是纯ECMAScript标准解释器/引擎,默认不带浏览器、Node环境的内置API,fetch属于浏览器Web API范畴,两个库默认都没有实现,直接调用必然报错。解决思路非常统一:把Go侧的HTTP请求能力封装成符合fetch调用习惯的函数,注入到JS上下文即可,不需要换依赖。
方案一:沿用Otto(使用成本最低)
Otto的API最简洁,注入自定义函数的逻辑非常简单,适合JS逻辑不复杂、性能要求不高的场景。
完整示例代码:
import ( "github.com/robertkrimen/otto" "io" "net/http" ) func main() { vm := otto.New() // 先注入console.log,默认Otto没有实现console,方便调试 vm.Set("console", map[string]any{ "log": func(call otto.FunctionCall) otto.Value { for _, arg := range call.ArgumentList { s, _ := arg.ToString() println(s) } return otto.Value{} }, }) // 注入fetch方法 vm.Set("fetch", func(call otto.FunctionCall) otto.Value { // 取第一个参数作为请求URL,需要扩展POST、请求头的话解析第二个参数即可 reqUrl := call.Argument(0).String() resp, err := http.Get(reqUrl) if err != nil { panic("fetch request failed: " + err.Error()) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { panic("read response failed: " + err.Error()) } // 包装成和标准fetch返回一致的Response对象 respVal, _ := vm.Object(`({ status: 0, ok: false, json: function() { return JSON.parse(this._rawBody); }, text: function() { return this._rawBody; } })`) respVal.Set("status", resp.StatusCode) respVal.Set("ok", resp.StatusCode >= 200 && resp.StatusCode < 300) respVal.Set("_rawBody", string(body)) return respVal.Value() }) // 直接运行原有JS逻辑即可,需要Promise异步支持可以在这个基础上扩展 vm.Run(` function tryfunc() { console.log("Started"); const res = fetch('https://www.example.com/APIendpoint'); const data = res.json(); console.log("Success", JSON.stringify(data)); } tryfunc(); `) }
上面是最简可运行实现,需要支持POST请求、自定义请求头、超时控制的话,在这个基础上扩展参数解析和返回逻辑就行,核心逻辑不变。
方案二:v8go实现(性能更高)
v8go基于Google V8引擎,执行JS的性能远高于解释器类的Otto/goja,适合JS逻辑重、性能要求高的场景。之前运行报错的核心原因是v8go默认上下文根本没有挂载fetch方法,不是参数传错。
完整示例代码:
import ( v8 "rogchap.com/v8go" "io" "net/http" ) func main() { iso := v8.NewIsolate() globalTpl := v8.NewObjectTemplate(iso) // 注入fetch到全局对象 fetchFn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value { args := info.Args() if len(args) < 1 { panic("fetch: missing required url argument") } reqUrl := args[0].String() resp, err := http.Get(reqUrl) if err != nil { panic("fetch request failed: " + err.Error()) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { panic("read response failed: " + err.Error()) } ctx := info.Context() // 构造Response对象 respObj, _ := v8.NewObject(iso, ctx) respObj.Set("status", int32(resp.StatusCode)) respObj.Set("ok", v8.NewBoolean(iso, resp.StatusCode >= 200 && resp.StatusCode < 300)) // 挂载json方法 jsonFn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value { jsonVal, err := v8.JSONParse(iso, ctx, string(body)) if err != nil { panic("parse json failed: " + err.Error()) } return jsonVal }) respObj.Set("json", jsonFn.GetFunction(ctx)) return respObj.Value }) globalTpl.Set("fetch", fetchFn) ctx := v8.NewContext(iso, globalTpl) // 执行JS脚本 ctx.RunScript(` async function fetchData() { const res = await fetch("https://www.example.com/APIendpoint"); const data = await res.json(); console.log(data); } fetchData(); `, "") }
通用避坑点
- 所有嵌入式JS引擎默认只实现ECMAScript核心语法,浏览器Web API(fetch、localStorage、console)、Node.js内置模块(fs、path、http)都不会默认提供,需要什么能力手动注入即可。
- 如果不想自己实现完整的fetch规范,可以找对应引擎社区已经写好的polyfill直接用,本质都是一样的注入逻辑。
- 对异步要求高的场景,优先选v8go或者goja,Otto的异步支持相对弱一些。
内容的提问来源于stack exchange,提问作者Filippo
相关产品推荐
相关产品推荐

