{"record":{"id":"98af6f09c3a51013","repo":"siyuan-note/siyuan","slug":"expected-promise-object-got-t","errorCode":null,"errorMessage":"expected promise object, got %T","messagePattern":"expected promise object, got %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":387,"sourceCode":"func 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 {\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}","sourceCodeStart":369,"sourceCodeEnd":405,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/sandbox.go#L369-L405","documentation":"After isGoPromise confirmed the result is a *goja.Promise, resultJs.ToObject(rt) returned nil — the promise value could not be turned into a goja.Object to read its .then property. Indicates a malformed/foreign thenable that Exported as a Promise type but is not object-convertible in this runtime.","triggerScenarios":"A plugin returns a value that goja's Export reports as *goja.Promise but which is a cross-runtime or proxy-wrapped thenable; invoking a Promise from a different goja.Runtime instance; a thenable constructed via Object.create/Proxy that defeats ToObject.","commonSituations":"Plugin shares a Promise created in another runtime/worker; plugin uses a polyfilled Promise shim that is not the runtime's native Promise; corrupted goja version mismatch.","solutions":["Ensure the handler returns a Promise created within the same goja.Runtime (use the runtime's own Promise constructor, not an imported polyfill).","Avoid returning wrapped/proxy thenables; return a value the runtime recognizes as its native Promise.","If bridging runtimes, resolve the promise in the source runtime and pass the resolved value instead of the Promise object."],"exampleFix":"// before (Promise from a polyfill)\nglobalThis.siyuan.server.network.beforeRequest.handler = () => PromisePolyfill.resolve(1)\n// after\nconst P = globalThis.Promise // runtime-native\nglobalThis.siyuan.server.network.beforeRequest.handler = () => P.resolve(1)","handlingStrategy":"type-guard","validationCode":"// Plugin side: only return Promises built from the runtime-native constructor.\nconst NativePromise = globalThis.Promise\nhandler = () => NativePromise.resolve(1)","typeGuard":"function isNativePromise(v: unknown): boolean {\n  return v instanceof globalThis.Promise\n}","tryCatchPattern":null,"preventionTips":["Use the runtime's own Promise constructor; avoid polyfills or imported thenables.","Do not pass Promise objects across runtime/worker boundaries.","If bridging, resolve in the source runtime and pass the resolved value."],"tags":["plugin","goja","javascript-runtime","promise","type-mismatch"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}