除了常规用例,有几组是专门钉住「引擎的既定事实」的,改动时会先红:
- vmstate_test goja 的硬限制:非原始值不是 goroutine 安全的、对象跨不了
Runtime。其中一条是哨兵——goja 哪天允许对象跨 Runtime 了,
它会失败,提醒我们可以简化设计。
- govalue_test Go 的切片/map 进到脚本里长什么样。这不是本库的行为而是
goja 的,但脚本作者照着它写代码,变了会静默走错分支。
- safety_test 并发下坏 VM 不会被别的 goroutine 捡到。
- bench_gobind 脚本碰 Go 对象的单次开销,README 性能一节的数据来源。
missing_global_test 里有一条断言 typeof setTimeout === "undefined":
定时器必须保持未定义,库里的特性探测才能正常降级,防止以后有人把桩加回来。
92 lines
2.5 KiB
Go
92 lines
2.5 KiB
Go
package jscriptx_test
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log"
|
||
|
||
"git.fsdpf.net/go/jscriptx"
|
||
)
|
||
|
||
// Caller 用来实现自定义的调用约定——本库不预设脚本回调该长什么样,
|
||
// 这个例子实现的是「回调 + next」这种中间件式的约定:脚本函数写成哪种形状,
|
||
// 运行时探测出来自动适配,脚本作者不用额外声明签名。
|
||
//
|
||
// 它同时也说明了为什么需要 Caller 而不是直接用 Call:next 是个 Go 闭包,
|
||
// 得先看脚本声明了几个形参,才知道要不要把它传进去——这些都必须在同一次
|
||
// VM 借出期间完成。
|
||
func ExampleCaller() {
|
||
e, err := jscriptx.New(jscriptx.WithLogger(nil))
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
defer e.Close()
|
||
|
||
// 三种回调写法,脚本作者挑顺手的用
|
||
s, err := e.Compile("ctrl.ts", `
|
||
export function twoArgs(v, next) { return next(v) + "|脚本自己调的 next" }
|
||
export function decorator(v) { return v + "|装饰过" }
|
||
export function sideEffect(v) { /* 只做事,不返回 */ }
|
||
`)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
next := func(v string) string { return "[" + v + "]" }
|
||
|
||
// dispatch 按脚本函数的形状分派
|
||
dispatch := func(fn, value string) (string, error) {
|
||
var out string
|
||
err := s.WithCall(context.Background(), fn, func(c jscriptx.Caller) error {
|
||
switch n := c.Arity(); n {
|
||
case 2:
|
||
// handle(v, next):调不调 next 由脚本决定,但得把结果返回出来
|
||
res, err := c.Call(value, next)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if res.IsEmpty() {
|
||
return fmt.Errorf("%w: 两个形参的回调必须返回结果",
|
||
jscriptx.ErrUnsupportedSignature)
|
||
}
|
||
return res.Into(&out)
|
||
|
||
case 1:
|
||
// handle(v):有返回值当装饰器,没返回值当纯副作用
|
||
res, err := c.Call(value)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if res.IsEmpty() {
|
||
out = next(value)
|
||
return nil
|
||
}
|
||
var decorated string
|
||
if err := res.Into(&decorated); err != nil {
|
||
return err
|
||
}
|
||
out = next(decorated)
|
||
return nil
|
||
|
||
default:
|
||
return fmt.Errorf("%w: 形参个数必须是 1 或 2,当前是 %d",
|
||
jscriptx.ErrUnsupportedSignature, n)
|
||
}
|
||
})
|
||
return out, err
|
||
}
|
||
|
||
for _, fn := range []string{"twoArgs", "decorator", "sideEffect"} {
|
||
got, err := dispatch(fn, "输入")
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
fmt.Printf("%-11s → %s\n", fn, got)
|
||
}
|
||
|
||
// Output:
|
||
// twoArgs → [输入]|脚本自己调的 next
|
||
// decorator → [输入|装饰过]
|
||
// sideEffect → [输入]
|
||
}
|