refactor: 清掉指向不存在的 Dispatch 的文档,删掉为它留的死导出

Dispatch 在仓库里出现 7 次,全是注释和错误文案,没有任何实现。三处错误文案
写着「需要回调语义请用 Dispatch」——使用者按这句去查会找不到东西,真正该指的
是 WithCall。caller.go 那句还指向不存在的子包 jscriptx/dispatch。

连带删掉四个为它留的导出(全仓库零调用):

  Target                   统一 Script/Instance 的接口。有未导出方法 owner(),
                           外部实现不了;也没有任何函数以它为参数或返回值
  ErrUnsupportedSignature  哨兵错误,库自己从不产生它
  KindSignature            错误分类,全仓库唯一一次出现就是它自己的声明。
                           留着会让写 switch 的人为一个永不出现的分支写代码
  OverlayLoader.Loaders    零调用的 getter,连测试都没有

另外删掉 Instance.IdleFor 和 lastUsed 字段:它是给「空闲回收」用的,而
doc.go 明确写着本库不代管实例生命周期、没有空闲回收——字段注释和包文档直接
对立。代价是每次 Call 白付两次 time.Now() + atomic store。业务侧真要自己回收,
记一个时间戳是一行的事。

caller 的示例原来拿 ErrUnsupportedSignature 当哨兵,改成自己声明一个——
回调签名的约定本来就是调用方定的,哨兵该归调用方。

验证:framework-v2 和 lx-bid 都仍能编译。
This commit is contained in:
2026-09-10 15:15:10 +08:00
parent ad722b12f9
commit 9b3509ae29
7 changed files with 28 additions and 59 deletions
+2 -21
View File
@@ -22,7 +22,8 @@ import (
// return nil // return nil
// }) // })
// //
// 框架自己的回调约定(比如 orm 那套种签名)就是这么实现的,见 jscriptx/dispatch。 // 框架自己的回调约定(比如 orm 那套「一元/二元」两种签名)就是这么实现的
// 先用 Arity 看脚本写了几个形参,再决定怎么调。
// //
// Caller 只在 do 回调执行期间有效,别存下来跨调用用。 // Caller 只在 do 回调执行期间有效,别存下来跨调用用。
type Caller interface { type Caller interface {
@@ -118,23 +119,3 @@ func (r *result) Into(out any) error {
} }
return nil 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
}
+7 -2
View File
@@ -2,6 +2,7 @@ package jscriptx_test
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log" "log"
@@ -34,6 +35,10 @@ func ExampleCaller() {
next := func(v string) string { return "[" + v + "]" } next := func(v string) string { return "[" + v + "]" }
// 回调签名的约定是**调用方**定的(这里定成"1 个或 2 个形参"),
// 所以哨兵错误也归调用方声明,本库不提供。
errBadSignature := errors.New("回调签名不受支持")
// dispatch 按脚本函数的形状分派 // dispatch 按脚本函数的形状分派
dispatch := func(fn, value string) (string, error) { dispatch := func(fn, value string) (string, error) {
var out string var out string
@@ -47,7 +52,7 @@ func ExampleCaller() {
} }
if res.IsEmpty() { if res.IsEmpty() {
return fmt.Errorf("%w: 两个形参的回调必须返回结果", return fmt.Errorf("%w: 两个形参的回调必须返回结果",
jscriptx.ErrUnsupportedSignature) errBadSignature)
} }
return res.Into(&out) return res.Into(&out)
@@ -70,7 +75,7 @@ func ExampleCaller() {
default: default:
return fmt.Errorf("%w: 形参个数必须是 1 或 2,当前是 %d", return fmt.Errorf("%w: 形参个数必须是 1 或 2,当前是 %d",
jscriptx.ErrUnsupportedSignature, n) errBadSignature, n)
} }
}) })
return out, err return out, err
+1 -4
View File
@@ -23,8 +23,6 @@ var (
ErrInterrupted = errors.New("jscriptx: 脚本被中断") ErrInterrupted = errors.New("jscriptx: 脚本被中断")
// ErrClosed 脚本或引擎已经关闭。 // ErrClosed 脚本或引擎已经关闭。
ErrClosed = errors.New("jscriptx: 已关闭") ErrClosed = errors.New("jscriptx: 已关闭")
// ErrUnsupportedSignature 回调函数的形参个数不在 Dispatch 支持的范围内。
ErrUnsupportedSignature = errors.New("jscriptx: 不支持的回调签名")
// ErrValueEscape 脚本试图把只在 VM 内部有效的值(函数/闭包)传到 Go 侧。 // ErrValueEscape 脚本试图把只在 VM 内部有效的值(函数/闭包)传到 Go 侧。
ErrValueEscape = errors.New("jscriptx: 该值不能跨出脚本边界") ErrValueEscape = errors.New("jscriptx: 该值不能跨出脚本边界")
// ErrBadGlobal 全局白名单配置不合法。 // ErrBadGlobal 全局白名单配置不合法。
@@ -48,7 +46,6 @@ const (
KindCanceled Kind = "canceled" // 调用方 context 被取消 KindCanceled Kind = "canceled" // 调用方 context 被取消
KindPanic Kind = "panic" // Go 侧 panic,已兜住转成 error KindPanic Kind = "panic" // Go 侧 panic,已兜住转成 error
KindType Kind = "type" // 返回值/参数类型不匹配 KindType Kind = "type" // 返回值/参数类型不匹配
KindSignature Kind = "signature" // 回调签名不受支持
KindClosed Kind = "closed" // 脚本已关闭 KindClosed Kind = "closed" // 脚本已关闭
) )
@@ -156,7 +153,7 @@ func newError(kind Kind, script, fn string, cause error, format string, a ...any
} }
// classify 把 goja 抛出来的各种错误翻译成 *Error 并补上脚本上下文。 // classify 把 goja 抛出来的各种错误翻译成 *Error 并补上脚本上下文。
// 已经是 *Error 的(比如 Dispatch 内部自己造的类型错误)原样返回。 // 已经是 *Error 的(比如 invoke 自己造的类型错误)原样返回。
func classify(err error, script, fn string, args []any) error { func classify(err error, script, fn string, args []any) error {
if err == nil { if err == nil {
return nil return nil
-11
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
) )
// Instance 是脚本导出的 class 的一个实例,独占一个 VM,用起来跟普通 Go 对象差不多: // Instance 是脚本导出的 class 的一个实例,独占一个 VM,用起来跟普通 Go 对象差不多:
@@ -40,7 +39,6 @@ type Instance struct {
vm *vmHandle vm *vmHandle
done bool done bool
lastUsed atomic.Int64 // UnixNano,空闲回收用
calls atomic.Int64 calls atomic.Int64
resets atomic.Int64 resets atomic.Int64
} }
@@ -63,7 +61,6 @@ func (s *Script) New(ctx context.Context, ctorArgs ...any) (*Instance, error) {
i.scope = sc i.scope = sc
i.label = "作用域 " + strconvQuote(sc.key) + " 的实例" i.label = "作用域 " + strconvQuote(sc.key) + " 的实例"
} }
i.touch()
if err := i.warmup(ctx); err != nil { if err := i.warmup(ctx); err != nil {
return nil, err return nil, err
} }
@@ -79,11 +76,6 @@ func (i *Instance) Calls() int64 { return i.calls.Load() }
// Resets 返回这个实例的 VM 被重建过几次。每重建一次,脚本里 this 上的状态就归零一次。 // Resets 返回这个实例的 VM 被重建过几次。每重建一次,脚本里 this 上的状态就归零一次。
func (i *Instance) Resets() int64 { return i.resets.Load() } 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 绑定到本实例。 // Call 在这个实例上调用方法,语义跟 Script.Call 一致,只是 this 绑定到本实例。
func (i *Instance) Call(ctx context.Context, fn string, args ...any) (any, error) { func (i *Instance) Call(ctx context.Context, fn string, args ...any) (any, error) {
return callAny(ctx, i, fn, args) 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) Close() { i.shutdown() }
func (i *Instance) owner() *Script { return i.script } func (i *Instance) owner() *Script { return i.script }
func (i *Instance) touch() { i.lastUsed.Store(time.Now().UnixNano()) }
// warmup 提前建好 VM,让 constructor 的错误在 New 阶段就暴露出来。 // warmup 提前建好 VM,让 constructor 的错误在 New 阶段就暴露出来。
func (i *Instance) warmup(ctx context.Context) error { func (i *Instance) warmup(ctx context.Context) error {
@@ -149,7 +140,6 @@ func (i *Instance) acquire(ctx context.Context) (*vmHandle, error) {
i.mu.Unlock() i.mu.Unlock()
return nil, err return nil, err
} }
i.touch()
i.calls.Add(1) i.calls.Add(1)
return i.vm, nil return i.vm, nil
} }
@@ -161,7 +151,6 @@ func (i *Instance) finish(_ *vmHandle, healthy bool) {
i.vm = nil i.vm = nil
i.resets.Add(1) i.resets.Add(1)
} }
i.touch()
i.mu.Unlock() i.mu.Unlock()
} }
+5 -5
View File
@@ -22,8 +22,8 @@ type frame struct {
name string name string
} }
// arity 返回 JS 函数声明的形参个数(函数对象的 length 属性) // arity 返回 JS 函数声明的形参个数(函数对象的 length 属性)
// Dispatch 靠它判断脚本写的是哪种回调形状 // WithCall 的回调靠它判断脚本写的是哪种形状,见 Caller.Arity
func (f *frame) arity() int64 { func (f *frame) arity() int64 {
return f.fnVal.ToObject(f.rt).Get("length").ToInteger() return f.fnVal.ToObject(f.rt).Get("length").ToInteger()
} }
@@ -56,7 +56,7 @@ func empty(v goja.Value) bool {
// 传进去的 Go 对象原样回来)。fn 传 DefaultFunc 表示调用脚本自身求值出的那个函数。 // 传进去的 Go 对象原样回来)。fn 传 DefaultFunc 表示调用脚本自身求值出的那个函数。
// //
// 脚本返回函数/闭包会被拒绝:那种值只在 VM 内部有效,VM 归还池子后再调用会出问题。 // 脚本返回函数/闭包会被拒绝:那种值只在 VM 内部有效,VM 归还池子后再调用会出问题。
// 需要把脚本函数当回调用,走 Dispatch // 需要把脚本函数当回调用,走 WithCall——在 VM 借出期间调,别把函数带出来
func (s *Script) Call(ctx context.Context, fn string, args ...any) (any, error) { func (s *Script) Call(ctx context.Context, fn string, args ...any) (any, error) {
return callAny(ctx, s, fn, args) 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 { if _, isFunc := goja.AssertFunction(res); isFunc {
return newError(KindType, r.owner().name, fn, ErrValueEscape, return newError(KindType, r.owner().name, fn, ErrValueEscape,
"返回值是 JS 函数,只在脚本内部有效;需要回调语义请用 Dispatch") "返回值是 JS 函数,只在脚本内部有效;需要回调语义请用 WithCall")
} }
out = res.Export() out = res.Export()
return nil return nil
@@ -104,7 +104,7 @@ func callInto(ctx context.Context, r runner, fn string, out any, args []any) err
// 而这个 VM 马上就要还回池子给别的请求用了。 // 而这个 VM 马上就要还回池子给别的请求用了。
if rv.Type().Elem().Kind() == reflect.Func { if rv.Type().Elem().Kind() == reflect.Func {
return newError(KindType, name, fn, ErrValueEscape, return newError(KindType, name, fn, ErrValueEscape,
"不能把脚本函数导出成 Go 函数(VM 归还池子后它就失效了);需要回调语义请用 Dispatch") "不能把脚本函数导出成 Go 函数(VM 归还池子后它就失效了);需要回调语义请用 WithCall")
} }
return invoke(ctx, r, fn, args, func(f *frame) error { return invoke(ctx, r, fn, args, func(f *frame) error {
-3
View File
@@ -195,9 +195,6 @@ func (o *OverlayLoader) Versions() []string {
// Prepared 返回成员们一致的取值,见 Overlay 的说明。 // Prepared 返回成员们一致的取值,见 Overlay 的说明。
func (o *OverlayLoader) Prepared() bool { return o.prepared } func (o *OverlayLoader) Prepared() bool { return o.prepared }
// Loaders 返回成员,顺序即叠放顺序(后面的盖前面的)。
func (o *OverlayLoader) Loaders() []Loader { return append([]Loader(nil), o.loaders...) }
// Names 汇总所有成员的脚本名,去重后按字典序排列。 // Names 汇总所有成员的脚本名,去重后按字典序排列。
// 成员得有 Names() []string 方法才算得上,没有的(比如数据库来源)就跳过。 // 成员得有 Names() []string 方法才算得上,没有的(比如数据库来源)就跳过。
func (o *OverlayLoader) Names() []string { func (o *OverlayLoader) Names() []string {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/dop251/goja" "github.com/dop251/goja"
) )
// DefaultFunc 传给 Call/Dispatch 的 fn 参数时,表示脚本的默认导出本身, // DefaultFunc 传给 Call/CallInto/WithCall 的 fn 参数时,表示脚本的默认导出本身,
// 也就是 `export default function ...` 这种"整个脚本就是一个函数"的写法。 // 也就是 `export default function ...` 这种"整个脚本就是一个函数"的写法。
// 默认导出是 class 或对象时用不上它——那种要按方法名调用。 // 默认导出是 class 或对象时用不上它——那种要按方法名调用。
const DefaultFunc = "" const DefaultFunc = ""