{"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":"critical","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/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/sandbox.go#L365-L401","documentation":"A plugin handler registered as synchronous returned a JavaScript Promise. invokeFunction detected isGoPromise(result)==true while async==false, and panics because the synchronous call contract cannot await. This is a contract violation by the plugin, not a transient runtime fault.","triggerScenarios":"A plugin registers a synchronous hook (event.on or server[scope][requestType].handler invoked in sync mode) whose implementation returns a Promise (uses async function or returns fetch/thenable). The kernel's sync invocation path has no way to await it.","commonSituations":"Plugin author wrote an async function for a hook the kernel calls synchronously; refactored a sync handler to async without updating the registration flag; mixing await-based helpers into a sync hook.","solutions":["Make the handler synchronous: drop async and await, return a plain value.","If async work is required, register the handler through the async-aware registration path so invokeFunction receives async==true.","Verify the handler does not implicitly return a Promise via an arrow body calling a Promise-returning helper."],"exampleFix":"// before\nglobalThis.siyuan.event.on = async (e) => { return await compute(e) }\n// after\nglobalThis.siyuan.event.on = (e) => { return compute(e) } // sync, returns value not Promise","handlingStrategy":"validation","validationCode":"// Plugin side: refuse to register an async function for a sync hook.\nfunction registerSyncHook(path: string[], fn: Function): void {\n  if (fn.constructor.name === 'AsyncFunction') {\n    throw new Error(`refusing async fn for sync hook ${path.join('.')}`)\n  }\n  // ...assign fn\n}","typeGuard":"function isAsyncFunction(fn: unknown): fn is (...a: any[]) => Promise<any> {\n  return typeof fn === 'function' && (fn as any).constructor.name === 'AsyncFunction'\n}","tryCatchPattern":null,"preventionTips":["Keep synchronous hooks synchronous: no async, no await, no Promise return.","When refactoring a sync handler to async, switch its registration to the async-aware path.","Audit arrow bodies that call Promise-returning helpers — they implicitly return a Promise."],"tags":["plugin","goja","javascript-runtime","async","contract-violation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}