{"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":"exception","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/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/sandbox.go#L376-L412","documentation":"invokeFunction confirmed the result is a Promise and ToObject succeeded, but obj.Get(\"then\") is not callable via goja.AssertFunction — the object lacks a real .then method. This means a thenable-shaped object whose then is missing or not a function.","triggerScenarios":"A plugin returns an object that isGoPromise matched (e.g. has promise-like type) but whose .then was deleted, is undefined, or is a non-function property; a custom thenable with then: null; tampered Promise.prototype.then.","commonSituations":"Plugin overrides/deletes Promise.prototype.then; plugin returns a duck-typed thenable missing then; sandbox strips the then property for security.","solutions":["Return a genuine native Promise from the handler rather than a hand-built thenable.","Verify Promise.prototype.then is intact in the sandbox (no monkey-patching that removes it).","If returning a thenable, ensure it exposes a callable .then(onFulfilled, onRejected)."],"exampleFix":"// before\nhandler = () => ({ then: null })\n// after\nhandler = () => new globalThis.Promise((resolve) => resolve(1))","handlingStrategy":"type-guard","validationCode":"// Plugin side: verify .then is a function before returning a thenable.\nfunction safeThenable(v: any): v is Promise<any> {\n  return v != null && typeof (v as any).then === 'function'\n}","typeGuard":"function hasCallableThen(v: unknown): v is { then: (...a: any[]) => any } {\n  return !!v && typeof (v as any)?.then === 'function'\n}","tryCatchPattern":null,"preventionTips":["Return native Promises, not hand-built thenables.","Do not delete or override Promise.prototype.then in the sandbox.","If constructing a thenable, always expose a callable then(onFulfilled, onRejected)."],"tags":["plugin","goja","javascript-runtime","promise","thenable"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}