go 执行js代码引擎系列之一:goja库


写在前面

本文是《go执行js代码引擎系列》文章之第一篇,其他相关文章如下

goja

它实现了ES 5.1的所有语法和大部分的ES 6语法,比 Python 的execJS要厉害得多。在一定程度上和特定场景下,它可以完全替代Chrome 的 V8引擎

基本用法

package main

import (
   "fmt"
   "github.com/dop251/goja"
   "go-js/common"
   "os"
)

func main() {
   filePath := os.Args[1]
   jsData, err := common.ReadFile(filePath)
   if err != nil {
      panic(err)
   }
   vm := goja.New()
   v, err := vm.RunString(jsData)
   if err != nil {
      panic(err)
   }
   // Export:将结果转换成go基础类型
   result, ok := v.Export().(int64)
   fmt.Printf("result:%d,ok:%t\n", result, ok)
}

执行:go run main.go ../data/add.js

var a = 2
var b = 3

result = a+b

输出:result:5,ok:true

执行:go run main.go ../data/func.js

var a = 2
var b = 3

result = min(a,b)

function min(a,b){
    if(a<b){
        return a
    }
    return b
}

输出:result:2,ok:true

go加载js function并执行

package main

import (
   "fmt"
   "github.com/dop251/goja"
   "go-js/common"
   "os"
)

func main() {
   filePath := os.Args[1]
   jsData, err := common.ReadFile(filePath)
   if err != nil {
      panic(err)
   }
   vm := goja.New()
   _, err = vm.RunString(jsData)
   if err != nil {
      panic(err)
   }
   // 加载函数
   max, ok := goja.AssertFunction(vm.Get("max"))
   if !ok {
      panic("Not a function")
   }

   result, err := max(goja.Undefined(), vm.ToValue(4), vm.ToValue(5))
   if err != nil {
      panic(err)
   }
   fmt.Printf("result:%v\n", result)
}

执行:go run main.go ../data/max.js

function max(a,b){
    if(a>=b){
        return a
    }
    return b
}

输出:result:5

ExportTo

将js对象导出成go对象

package main

import (
   "fmt"
   "github.com/dop251/goja"
   "go-js/common"
   "os"
)

func main() {
   filePath := os.Args[1]
   jsData, err := common.ReadFile(filePath)
   if err != nil {
      panic(err)
   }
   vm := goja.New()
   _, err = vm.RunString(jsData)
   if err != nil {
      panic(err)
   }
   // 加载函数
   var max func(int, int) int
   if err := vm.ExportTo(vm.Get("max"), &max); err != nil {
      panic("Not a function")
   }

   fmt.Printf("result:%v\n", max(6, 7))
}

执行:go run main.go ../data/max.js

输出:result:7

js访问go对象

package main

import (
   "fmt"
   "github.com/dop251/goja"
   "go-js/common"
   "os"
)

type Person struct {
   Name string
   Age  int64
}

func main() {
   p := &Person{
      Name: "张三",
      Age:  18,
   }
   vm := goja.New()
   // 设置go对象到js运行时环境中
   vm.Set("person", p)

   filePath := os.Args[1]
   jsData, err := common.ReadFile(filePath)
   if err != nil {
      panic(err)
   }
   // 执行js代码
   v, err := vm.RunString(jsData)
   if err != nil {
      panic(err)
   }

   result, ok := v.Export().(string)

   fmt.Printf("result:%s, ok:%t\n", result, ok)
}

执行:go run main.go ../data/print_person.js

result = '姓名:'+person.Name+',年龄:'+person.Age

输出:result:姓名:张三,年龄:18, ok:true

js访问go函数

package main

import (
   "fmt"
   "github.com/dop251/goja"
   "go-js/common"
   "os"
)

type Person struct {
   Name string
   Age  int64
}

func main() {
   p := Person{
      Name: "张三",
      Age:  18,
   }
   vm := goja.New()
   // 设置go对象到js运行时环境中
   vm.Set("p", &p)
   vm.Set("visit", visit)

   filePath := os.Args[1]
   jsData, err := common.ReadFile(filePath)
   if err != nil {
      panic(err)
   }
   // 执行js代码
   v, err := vm.RunString(jsData)
   if err != nil {
      panic(err)
   }
   result, ok := v.Export().(string)

   fmt.Printf("result:%s, ok:%t\n", result, ok)
}

func visit(p *Person, a int) string {
   return fmt.Sprintf("go visit. Name:%s, Age:%d, a:%d", p.Name, p.Age, a)
}

执行: go run main.go ../data/call_go_func.js

// p是go对象,123是js变量
var a = 123
visit(p, a)

输出:result:go visit. Name:张三, Age:18, a:123, ok:true

异常

package main

import (
   "fmt"
   "github.com/dop251/goja"
   "go-js/common"
   "os"
)

func main() {
   vm := goja.New()
   // 设置go对象到js运行时环境中

   filePath := os.Args[1]
   jsData, err := common.ReadFile(filePath)
   if err != nil {
      panic(err)
   }
   // 执行js代码
   _, err = vm.RunString(jsData)
   if err != nil {
      if jserr, ok := err.(*goja.Exception); ok {
         fmt.Printf("%v\n", jserr)
      } else {
         panic(fmt.Sprintf("wrong type err:%+v\n", jserr))
      }
   }
}

执行: go run main.go ../data/js_throw.js


throw('test js throw')

输出:test js throw at <eval>:2:1(1)

package main

import (
   "fmt"
   "github.com/dop251/goja"
   "go-js/common"
   "os"
   "time"
)

func main() {
   vm := goja.New()
   // 设置超时时间200ms
   time.AfterFunc(200*time.Millisecond, func() {
      vm.Interrupt("halt")
   })

   filePath := os.Args[1]
   jsData, err := common.ReadFile(filePath)
   if err != nil {
      panic(err)
   }
   // 执行js代码
   _, err = vm.RunString(jsData)
   if err != nil {
      if jserr, ok := err.(*goja.Exception); ok {
         fmt.Printf("%v\n", jserr)
      } else {
         panic(fmt.Sprintf("wrong type err:%+v\n", err))
      }
   }
}

执行:go run main.go ../data/Interrupting.js

输出:

panic: wrong type err:halt at <eval>:3:6(12)


goroutine 1 [running]:
main.main()
        go/src/go-js/goja/main.go:29 +0x17b
exit status 2

demo代码

.
├── common
│   └── util.go
├── data
│   ├── Interrupting.js
│   ├── add.js
│   ├── call_go_func.js
│   ├── func.js
│   ├── js_throw.js
│   ├── max.js
│   └── print_person.js
├── go.mod
├── go.sum
├── goja
│   └── main.go
├── main.go
├── otto
│   └── main.go

源码:https://github.com/ZBIGBEAR/go-js

总结

  • js调用go对象、函数,都是将go对象和函数看成对象,因为在js中是不区分对象和函数的。所以在go中封装对象和函数到js运行时环境,js就可以调用。
  • go调用js函数,这种情况一般用的少
  • js throw处理
  • js function超时处理

参考

[1]一日一技:在 Golang 中运行 JavaScript

[2]go执行js代码引擎系列之二:otto库

[3]go执行js代码引擎系列之三:v8go库

[4]go执行js代码引擎系列总结篇:比较goja、otto、v8go

[5]源码:https://github.com/dop251/goja

[6]https://github.com/ZBIGBEAR/go-js


文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录