siyuan-note/siyuan · error
synchronous function returned a Promise
Error message
synchronous function returned a Promise
What it means
invokeFunction calls a plugin function and, when the call is marked synchronous (async == false), forbids the result from being a Promise/Go-future. If a synchronous invocation returns a Go Promise (isGoPromise true), it panics with this error because the caller cannot await it and the result contract is violated.
Solutions
- Mark the plugin function as async in its registration when the implementation returns a Promise
- Change the implementation to resolve the value synchronously before returning
- Update the API declaration to match the implementation's async behavior
- If wrapping JS functions, ensure sync-declared ones do not call async JS functions
Example fix
// before
// registration: {name: "getData", async: false}
func getData(vm) { return vm.RunString("fetchIt()") } // returns a Promise
// after
// registration: {name: "getData", async: true}
func getData(vm) { return vm.RunString("fetchIt()") } Defensive patterns
Strategy: validation
Validate before calling
result := fn()
if isGoPromise(result) && !asyncSpec {
return fmt.Errorf("function %s returns a Promise but is declared synchronous", name)
} Type guard
func isSyncCompatible(result any) bool { return !isGoPromise(result) } Try / catch
defer func() {
if r := recover(); r != nil {
callback(rt, &CallResult{Error: fmt.Errorf("sync invocation failed: %v", r)})
}
}() Prevention
- Keep the async flag in the API registry in sync with the implementation
- Never declare async JS (async/await) handlers as synchronous
- Test every registered plugin function for promise leakage in sync mode
When it happens
Trigger: Registering or invoking a plugin API function with async=false whose Go implementation returns a promise-like value (e.g. a *goja.Promise exported as a Go promise, or a function backed by an async JS handler) — detected via isGoPromise(result).
Common situations: Plugin method implemented in JS with async/await but declared synchronous in the API registry; Go wrapper returning rt.NewPromise() result for a sync API; API declaration drift between plugin manifest and implementation.
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
- expected promise object, got %T
- failed to enable extend modules
- failed to inject global context
- globalThis.siyuan.event is not an object
- globalThis.siyuan.event is
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/5485866c9e6e5bd6.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/sandbox.go:383
return
}
// invokeFunction calls a goja.Callable with the given this and arguments, handling both synchronous return values and Promises.
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 {View on GitHub (pinned to 9f775e8a12)