siyuan-note/siyuan · error
expected promise object, got %T
Error message
expected promise object, got %T
What it means
After isGoPromise confirmed the result is a *goja.Promise, resultJs.ToObject(rt) returned nil — the promise value could not be turned into a goja.Object to read its .then property. Indicates a malformed/foreign thenable that Exported as a Promise type but is not object-convertible in this runtime.
Source
Thrown at kernel/plugin/sandbox.go:387
func invokeFunction(callback func(rt *goja.Runtime, result *CallResult), rt *goja.Runtime, async bool, fn goja.Callable, this goja.Value, args ...goja.Value) {
resultJs, invokeErr := fn(this, args...)
if callback == nil {
return
}
if invokeErr != nil {
callback(rt, &CallResult{Error: invokeErr})
return
}
result := resultJs.Export()
if isGoPromise(result) {
if !async {
panic(fmt.Errorf("synchronous function returned a Promise"))
}
resultObj := resultJs.ToObject(rt)
if resultObj == nil {
callback(rt, &CallResult{Error: fmt.Errorf("expected promise object, got %T", result)})
return
}
thenValue := resultObj.Get("then")
then, ok := goja.AssertFunction(thenValue)
if !ok {
callback(rt, &CallResult{Error: fmt.Errorf("'promise.then property is not a function")})
return
}
then(resultObj, rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) {
// ⚠️ call.Arguments always is an empty array.
promise, ok := result.(*goja.Promise)
if ok {
callback(rt, &CallResult{Value: promise.Result()})
} else {
callback(rt, &CallResult{Value: resultJs})
}View on GitHub (pinned to 251596fc0d)
Solutions
- Ensure the handler returns a Promise created within the same goja.Runtime (use the runtime's own Promise constructor, not an imported polyfill).
- Avoid returning wrapped/proxy thenables; return a value the runtime recognizes as its native Promise.
- If bridging runtimes, resolve the promise in the source runtime and pass the resolved value instead of the Promise object.
Example fix
// before (Promise from a polyfill) globalThis.siyuan.server.network.beforeRequest.handler = () => PromisePolyfill.resolve(1) // after const P = globalThis.Promise // runtime-native globalThis.siyuan.server.network.beforeRequest.handler = () => P.resolve(1)
Defensive patterns
Strategy: type-guard
Validate before calling
// Plugin side: only return Promises built from the runtime-native constructor. const NativePromise = globalThis.Promise handler = () => NativePromise.resolve(1)
Type guard
function isNativePromise(v: unknown): boolean {
return v instanceof globalThis.Promise
} Prevention
- Use the runtime's own Promise constructor; avoid polyfills or imported thenables.
- Do not pass Promise objects across runtime/worker boundaries.
- If bridging, resolve in the source runtime and pass the resolved value.
When it happens
Trigger: A plugin returns a value that goja's Export reports as *goja.Promise but which is a cross-runtime or proxy-wrapped thenable; invoking a Promise from a different goja.Runtime instance; a thenable constructed via Object.create/Proxy that defeats ToObject.
Common situations: Plugin shares a Promise created in another runtime/worker; plugin uses a polyfilled Promise shim that is not the runtime's native Promise; corrupted goja version mismatch.
Related errors
- path %v: expected object, got %T
- 'promise.then property is not a function
- promise rejected: %v
- globalThis.siyuan.server[%s][%s] is not an object
- siyuan.server[%s][%s].handler is not a function
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/98af6f09c3a51013.
Report an issue: GitHub.