{"record":{"id":"84f30f9707abab92","repo":"siyuan-note/siyuan","slug":"promise-then-property-is-not-a-function","errorCode":null,"errorMessage":"'promise.then property is not a function","messagePattern":"'promise\\.then property is not a function","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":394,"sourceCode":"\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 {\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}","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/sandbox.go#L376-L412","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","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"],"exampleFix":"// before - plugin returns a broken thenable\nreturn { then: null };\n// after\nreturn (async () => { /* work */ })();","handlingStrategy":"try-catch","validationCode":"const r = handler(req); if (r && typeof r.then !== 'function') throw new Error('async handler must return a Promise');","typeGuard":"function isThenable(v) { return v != null && typeof v.then === 'function'; }","tryCatchPattern":"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; } }","preventionTips":["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"],"tags":["goja","plugin","promise","javascript-sandbox"],"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"}