{"record":{"id":"98b6783ae7b4f15f","repo":"siyuan-note/siyuan","slug":"promise-rejected-v","errorCode":null,"errorMessage":"promise rejected: %v","messagePattern":"promise rejected: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":407,"sourceCode":"\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 {\n\t\t\t\tcallback(rt, &CallResult{Value: promise.Result()})\n\t\t\t} else {\n\t\t\t\tcallback(rt, &CallResult{Value: resultJs})\n\t\t\t}\n\t\t}), rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) {\n\t\t\tcallback(rt, &CallResult{Error: fmt.Errorf(\"promise rejected: %v\", call.Argument(0).Export())})\n\t\t}))\n\t} else {\n\t\tcallback(rt, &CallResult{Value: resultJs})\n\t}\n}\n\n// isJsPromise checks if a goja.Value is a JavaScript Promise.\nfunc isJsPromise(jsValue goja.Value) bool {\n\tif jsValue == nil {\n\t\treturn false\n\t}\n\n\tgoValue := jsValue.Export()\n\treturn isGoPromise(goValue)\n}\n\n// isGoPromise checks if a Go value is a *goja.Promise.\nfunc isGoPromise(goValue any) bool {","sourceCodeStart":389,"sourceCodeEnd":425,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/sandbox.go#L389-L425","documentation":"invokeFunction awaits the plugin handler's Promise by attaching a rejection callback via then(); when the Promise rejects, the rejection reason (exported to Go) is wrapped in this error and delivered through the CallResult. It is the sandbox's way of surfacing a failed async handler to the kernel caller.","triggerScenarios":"An async plugin handler (siyuan.server[scope][requestType].handler) throws inside its async body or its returned Promise rejects with any JS value (Error, string, object) - the exported rejection value appears in the %v placeholder.","commonSituations":"Plugin code throwing inside async request handlers (e.g. failed fetch in a server-scope handler, unhandled null dereference in an awaited call, network failures from within the plugin).","solutions":["Read the %v portion to see the original rejection reason thrown by the plugin","Fix the underlying exception in the plugin's handler code (often a null/undefined access or a failed network call)","Add try/catch inside the plugin handler to convert expected failures into normal error responses"],"exampleFix":"// before - plugin handler\nasync function handler(req) { return await riskyFetch(req); }\n// after\nasync function handler(req) {\n  try { return await riskyFetch(req); }\n  catch (e) { return { code: -1, msg: String(e) }; }\n}","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"try { const r = await callPluginHandler(); } catch (e) { if (String(e).startsWith('promise rejected:')) { const reason = String(e).slice('promise rejected:'.length); log(reason); } else { throw e; } }","preventionTips":["Wrap async handler bodies in try/catch and return structured error responses","Log rejection reasons inside the plugin before they propagate","Test handlers with failing inputs to confirm graceful rejection"],"tags":["goja","plugin","promise-rejection","javascript-sandbox"],"backgroundTag":"promise-rejected","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"}