siyuan-note/siyuan · error
'promise.then property is not a function
Error message
'promise.then property is not a function
What it means
When a plugin handler declared as async returns a thenable, the sandbox's invokeFunction inspects the returned object's 'then' property and requires it to be a callable function via goja.AssertFunction. If the resolved object is not a genuine Promise/thenable (or its 'then' was overwritten with a non-function), this error is reported to the callback instead of awaiting a result.
Solutions
- Inspect the plugin handler's return value and ensure it returns a real Promise (async function or new Promise) without overriding 'then'
- Check for plugin code that assigns to Promise.prototype.then or to a property named 'then' on the returned object
- Upgrade/fix the plugin; this is a contract violation of the async handler API
Example fix
// before - plugin returns a broken thenable
return { then: null };
// after
return (async () => { /* work */ })(); Defensive patterns
Strategy: try-catch
Validate before calling
const r = handler(req); if (r && typeof r.then !== 'function') throw new Error('async handler must return a Promise'); Type guard
function isThenable(v) { return v != null && typeof v.then === 'function'; } Try / catch
try { const r = await invokeFn(); } catch (e) { if (String(e).includes('promise.then property is not a function')) { /* fix handler return value */ } else { throw e; } } Prevention
- Always return real Promises from async handlers (async functions)
- Never assign to a property named 'then' on returned objects
- Do not mutate Promise.prototype in plugin code
When it happens
Trigger: A handler registered via siyuan.server[scope][requestType].handler returns a truthy object that isGoPromise classifies as a promise (i.e. Export() yields *goja.Promise), but resultObj.Get("then") is not callable - e.g. a thenable whose 'then' property is a value, null, or was overwritten by plugin code.
Common situations: Plugin code mutates the returned Promise (assigning then = something), returns a custom thenable-like object wrapped incorrectly, or a Go-side object leaks into the return path where 'then' is data rather than a method. Rare; usually indicates a plugin bug corrupting the Promise prototype or shadowing 'then'.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- globalThis.siyuan.server
- promise rejected
- expected promise object, got %T
- failed to serialize config.
- failed to serialize inputSchema
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/84f30f9707abab92.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/sandbox.go:394
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})
}
}), rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) {
callback(rt, &CallResult{Error: fmt.Errorf("promise rejected: %v", call.Argument(0).Export())})
}))
} else {
callback(rt, &CallResult{Value: resultJs})
}
}View on GitHub (pinned to 9f775e8a12)