diff --git a/caller.go b/caller.go index a0e510f..ac66617 100644 --- a/caller.go +++ b/caller.go @@ -22,7 +22,8 @@ import ( // return nil // }) // -// 框架自己的回调约定(比如 orm 那套三种签名)就是这么实现的,见 jscriptx/dispatch。 +// 框架自己的回调约定(比如 orm 那套「一元/二元」两种签名)就是这么实现的: +// 先用 Arity 看脚本写了几个形参,再决定怎么调。 // // Caller 只在 do 回调执行期间有效,别存下来跨调用用。 type Caller interface { @@ -118,23 +119,3 @@ func (r *result) Into(out any) error { } return nil } - -// Target 是能发起脚本调用的对象:*Script(从 VM 池借用,脚本无跨调用状态) -// 或 *Instance(独占一个 VM,脚本里的状态跨调用保持)。 -// -// 这个接口不对外开放实现,只是让 dispatch 这类函数能同时接受两者。 -type Target interface { - // Name 返回脚本名。 - Name() string - // Has 判断脚本里有没有这个函数。 - Has(fn string) bool - // Call 调用脚本函数,返回值导出成 Go 值。 - Call(ctx context.Context, fn string, args ...any) (any, error) - // CallInto 调用脚本函数,并把返回值转换进 out 指向的变量。 - CallInto(ctx context.Context, fn string, out any, args ...any) error - // WithCall 借一个 VM,在借出期间把控制权交给 do,用来实现自定义的调用约定。 - WithCall(ctx context.Context, fn string, do func(Caller) error) error - - // 未导出方法,接口不对外开放实现。 - owner() *Script -} diff --git a/caller_example_test.go b/caller_example_test.go index 9f26377..36937c4 100644 --- a/caller_example_test.go +++ b/caller_example_test.go @@ -2,6 +2,7 @@ package jscriptx_test import ( "context" + "errors" "fmt" "log" @@ -34,6 +35,10 @@ func ExampleCaller() { next := func(v string) string { return "[" + v + "]" } + // 回调签名的约定是**调用方**定的(这里定成"1 个或 2 个形参"), + // 所以哨兵错误也归调用方声明,本库不提供。 + errBadSignature := errors.New("回调签名不受支持") + // dispatch 按脚本函数的形状分派 dispatch := func(fn, value string) (string, error) { var out string @@ -47,7 +52,7 @@ func ExampleCaller() { } if res.IsEmpty() { return fmt.Errorf("%w: 两个形参的回调必须返回结果", - jscriptx.ErrUnsupportedSignature) + errBadSignature) } return res.Into(&out) @@ -70,7 +75,7 @@ func ExampleCaller() { default: return fmt.Errorf("%w: 形参个数必须是 1 或 2,当前是 %d", - jscriptx.ErrUnsupportedSignature, n) + errBadSignature, n) } }) return out, err diff --git a/errors.go b/errors.go index a498e49..a7402f6 100644 --- a/errors.go +++ b/errors.go @@ -23,8 +23,6 @@ var ( ErrInterrupted = errors.New("jscriptx: 脚本被中断") // ErrClosed 脚本或引擎已经关闭。 ErrClosed = errors.New("jscriptx: 已关闭") - // ErrUnsupportedSignature 回调函数的形参个数不在 Dispatch 支持的范围内。 - ErrUnsupportedSignature = errors.New("jscriptx: 不支持的回调签名") // ErrValueEscape 脚本试图把只在 VM 内部有效的值(函数/闭包)传到 Go 侧。 ErrValueEscape = errors.New("jscriptx: 该值不能跨出脚本边界") // ErrBadGlobal 全局白名单配置不合法。 @@ -39,17 +37,16 @@ var ( type Kind string const ( - KindLoad Kind = "load" // 加载脚本源码失败 - KindCompile Kind = "compile" // 编译(语法解析)失败 - KindBind Kind = "bind" // 注入全局白名单失败 - KindNotFound Kind = "not_found" // 脚本或函数不存在 - KindRuntime Kind = "runtime" // 脚本运行期抛出异常 - KindTimeout Kind = "timeout" // 超时被中断 - KindCanceled Kind = "canceled" // 调用方 context 被取消 - KindPanic Kind = "panic" // Go 侧 panic,已兜住转成 error - KindType Kind = "type" // 返回值/参数类型不匹配 - KindSignature Kind = "signature" // 回调签名不受支持 - KindClosed Kind = "closed" // 脚本已关闭 + KindLoad Kind = "load" // 加载脚本源码失败 + KindCompile Kind = "compile" // 编译(语法解析)失败 + KindBind Kind = "bind" // 注入全局白名单失败 + KindNotFound Kind = "not_found" // 脚本或函数不存在 + KindRuntime Kind = "runtime" // 脚本运行期抛出异常 + KindTimeout Kind = "timeout" // 超时被中断 + KindCanceled Kind = "canceled" // 调用方 context 被取消 + KindPanic Kind = "panic" // Go 侧 panic,已兜住转成 error + KindType Kind = "type" // 返回值/参数类型不匹配 + KindClosed Kind = "closed" // 脚本已关闭 ) // Frame 是脚本调用栈的一帧。从 goja 的栈信息里摘出来重新包装, @@ -156,7 +153,7 @@ func newError(kind Kind, script, fn string, cause error, format string, a ...any } // classify 把 goja 抛出来的各种错误翻译成 *Error 并补上脚本上下文。 -// 已经是 *Error 的(比如 Dispatch 内部自己造的类型错误)原样返回。 +// 已经是 *Error 的(比如 invoke 自己造的类型错误)原样返回。 func classify(err error, script, fn string, args []any) error { if err == nil { return nil diff --git a/instance.go b/instance.go index 9738ba3..98597f8 100644 --- a/instance.go +++ b/instance.go @@ -4,7 +4,6 @@ import ( "context" "sync" "sync/atomic" - "time" ) // Instance 是脚本导出的 class 的一个实例,独占一个 VM,用起来跟普通 Go 对象差不多: @@ -40,9 +39,8 @@ type Instance struct { vm *vmHandle done bool - lastUsed atomic.Int64 // UnixNano,空闲回收用 - calls atomic.Int64 - resets atomic.Int64 + calls atomic.Int64 + resets atomic.Int64 } // New 实例化脚本导出的 class,构造参数直接传给 constructor。返回的实例独占一个 VM, @@ -63,7 +61,6 @@ func (s *Script) New(ctx context.Context, ctorArgs ...any) (*Instance, error) { i.scope = sc i.label = "作用域 " + strconvQuote(sc.key) + " 的实例" } - i.touch() if err := i.warmup(ctx); err != nil { return nil, err } @@ -79,11 +76,6 @@ func (i *Instance) Calls() int64 { return i.calls.Load() } // Resets 返回这个实例的 VM 被重建过几次。每重建一次,脚本里 this 上的状态就归零一次。 func (i *Instance) Resets() int64 { return i.resets.Load() } -// IdleFor 返回这个实例空闲了多久。 -func (i *Instance) IdleFor() time.Duration { - return time.Since(time.Unix(0, i.lastUsed.Load())) -} - // Call 在这个实例上调用方法,语义跟 Script.Call 一致,只是 this 绑定到本实例。 func (i *Instance) Call(ctx context.Context, fn string, args ...any) (any, error) { return callAny(ctx, i, fn, args) @@ -111,7 +103,6 @@ func (i *Instance) Has(fn string) bool { func (i *Instance) Close() { i.shutdown() } func (i *Instance) owner() *Script { return i.script } -func (i *Instance) touch() { i.lastUsed.Store(time.Now().UnixNano()) } // warmup 提前建好 VM,让 constructor 的错误在 New 阶段就暴露出来。 func (i *Instance) warmup(ctx context.Context) error { @@ -149,7 +140,6 @@ func (i *Instance) acquire(ctx context.Context) (*vmHandle, error) { i.mu.Unlock() return nil, err } - i.touch() i.calls.Add(1) return i.vm, nil } @@ -161,7 +151,6 @@ func (i *Instance) finish(_ *vmHandle, healthy bool) { i.vm = nil i.resets.Add(1) } - i.touch() i.mu.Unlock() } diff --git a/invoke.go b/invoke.go index f9bcf76..78a913d 100644 --- a/invoke.go +++ b/invoke.go @@ -22,8 +22,8 @@ type frame struct { name string } -// arity 返回 JS 函数声明的形参个数(函数对象的 length 属性), -// Dispatch 靠它判断脚本写的是哪种回调形状。 +// arity 返回 JS 函数声明的形参个数(函数对象的 length 属性)。 +// WithCall 的回调靠它判断脚本写的是哪种形状,见 Caller.Arity。 func (f *frame) arity() int64 { return f.fnVal.ToObject(f.rt).Get("length").ToInteger() } @@ -56,7 +56,7 @@ func empty(v goja.Value) bool { // 传进去的 Go 对象原样回来)。fn 传 DefaultFunc 表示调用脚本自身求值出的那个函数。 // // 脚本返回函数/闭包会被拒绝:那种值只在 VM 内部有效,VM 归还池子后再调用会出问题。 -// 需要把脚本函数当回调用,走 Dispatch。 +// 需要把脚本函数当回调用,走 WithCall——在 VM 借出期间调,别把函数带出来。 func (s *Script) Call(ctx context.Context, fn string, args ...any) (any, error) { return callAny(ctx, s, fn, args) } @@ -84,7 +84,7 @@ func callAny(ctx context.Context, r runner, fn string, args []any) (any, error) } if _, isFunc := goja.AssertFunction(res); isFunc { return newError(KindType, r.owner().name, fn, ErrValueEscape, - "返回值是 JS 函数,只在脚本内部有效;需要回调语义请用 Dispatch") + "返回值是 JS 函数,只在脚本内部有效;需要回调语义请用 WithCall") } out = res.Export() return nil @@ -104,7 +104,7 @@ func callInto(ctx context.Context, r runner, fn string, out any, args []any) err // 而这个 VM 马上就要还回池子给别的请求用了。 if rv.Type().Elem().Kind() == reflect.Func { return newError(KindType, name, fn, ErrValueEscape, - "不能把脚本函数导出成 Go 函数(VM 归还池子后它就失效了);需要回调语义请用 Dispatch") + "不能把脚本函数导出成 Go 函数(VM 归还池子后它就失效了);需要回调语义请用 WithCall") } return invoke(ctx, r, fn, args, func(f *frame) error { diff --git a/overlay.go b/overlay.go index 8309896..9ca9a6d 100644 --- a/overlay.go +++ b/overlay.go @@ -195,9 +195,6 @@ func (o *OverlayLoader) Versions() []string { // Prepared 返回成员们一致的取值,见 Overlay 的说明。 func (o *OverlayLoader) Prepared() bool { return o.prepared } -// Loaders 返回成员,顺序即叠放顺序(后面的盖前面的)。 -func (o *OverlayLoader) Loaders() []Loader { return append([]Loader(nil), o.loaders...) } - // Names 汇总所有成员的脚本名,去重后按字典序排列。 // 成员得有 Names() []string 方法才算得上,没有的(比如数据库来源)就跳过。 func (o *OverlayLoader) Names() []string { diff --git a/script.go b/script.go index 6c5d3fb..0f7bfa3 100644 --- a/script.go +++ b/script.go @@ -8,7 +8,7 @@ import ( "github.com/dop251/goja" ) -// DefaultFunc 传给 Call/Dispatch 的 fn 参数时,表示脚本的默认导出本身, +// DefaultFunc 传给 Call/CallInto/WithCall 的 fn 参数时,表示脚本的默认导出本身, // 也就是 `export default function ...` 这种"整个脚本就是一个函数"的写法。 // 默认导出是 class 或对象时用不上它——那种要按方法名调用。 const DefaultFunc = ""