{"record":{"id":"5485866c9e6e5bd6","repo":"siyuan-note/siyuan","slug":"synchronous-function-returned-a-promise","errorCode":null,"errorMessage":"synchronous function returned a Promise","messagePattern":"synchronous function returned a Promise","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":383,"sourceCode":"\treturn\n}\n\n// invokeFunction calls a goja.Callable with the given this and arguments, handling both synchronous return values and Promises.\nfunc invokeFunction(callback func(rt *goja.Runtime, result *CallResult), rt *goja.Runtime, async bool, fn goja.Callable, this goja.Value, args ...goja.Value) {\n\tresultJs, invokeErr := fn(this, args...)\n\tif callback == nil {\n\t\treturn\n\t}\n\n\tif invokeErr != nil {\n\t\tcallback(rt, &CallResult{Error: invokeErr})\n\t\treturn\n\t}\n\n\tresult := resultJs.Export()\n\tif isGoPromise(result) {\n\t\tif !async {\n\t\t\tpanic(fmt.Errorf(\"synchronous function returned a Promise\"))\n\t\t}\n\t\tresultObj := resultJs.ToObject(rt)\n\t\tif resultObj == nil {\n\t\t\tcallback(rt, &CallResult{Error: fmt.Errorf(\"expected promise object, got %T\", result)})\n\t\t\treturn\n\t\t}\n\n\t\tthenValue := resultObj.Get(\"then\")\n\t\tthen, ok := goja.AssertFunction(thenValue)\n\t\tif !ok {\n\t\t\tcallback(rt, &CallResult{Error: fmt.Errorf(\"'promise.then property is not a function\")})\n\t\t\treturn\n\t\t}\n\n\t\tthen(resultObj, rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) {\n\t\t\t// ⚠️ call.Arguments always is an empty array.\n\t\t\tpromise, ok := result.(*goja.Promise)\n\t\t\tif ok {","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/sandbox.go#L365-L401","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\n// registration: {name: \"getData\", async: false}\nfunc getData(vm) { return vm.RunString(\"fetchIt()\") } // returns a Promise\n// after\n// registration: {name: \"getData\", async: true}\nfunc getData(vm) { return vm.RunString(\"fetchIt()\") }","handlingStrategy":"validation","validationCode":"result := fn()\nif isGoPromise(result) && !asyncSpec {\n    return fmt.Errorf(\"function %s returns a Promise but is declared synchronous\", name)\n}","typeGuard":"func isSyncCompatible(result any) bool { return !isGoPromise(result) }","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        callback(rt, &CallResult{Error: fmt.Errorf(\"sync invocation failed: %v\", r)})\n    }\n}()","preventionTips":["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"],"tags":["goja","plugin-sandbox","async","promise"],"backgroundTag":"type-mismatch","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}