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
// })
//
// 框架自己的回调约定(比如 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
}
+7 -2
View File
@@ -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
+11 -14
View File
@@ -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
+2 -13
View File
@@ -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()
}
+5 -5
View File
@@ -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 {
-3
View File
@@ -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 {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/dop251/goja"
)
// DefaultFunc 传给 Call/Dispatch 的 fn 参数时,表示脚本的默认导出本身,
// DefaultFunc 传给 Call/CallInto/WithCall 的 fn 参数时,表示脚本的默认导出本身,
// 也就是 `export default function ...` 这种"整个脚本就是一个函数"的写法。
// 默认导出是 class 或对象时用不上它——那种要按方法名调用。
const DefaultFunc = ""