You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 11:24:41