{"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":"error_code","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/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/sandbox.go#L369-L405","documentation":"In invokeFunction's async path, a Go-promise result must be convertible to a JS object exposing .then. If resultJs.ToObject(rt) returns nil, the runtime cannot wrap the promise, so the callback receives this error describing the actual Go type.","triggerScenarios":"Async plugin invocation whose exported result passes isGoPromise but fails goja's ToObject conversion — e.g. a custom Go type that merely resembles a promise but has no JS representation in this runtime, or the runtime is in a broken state.","commonSituations":"Custom promise-like Go types returned from plugin handlers; mixing runtimes (promise created in runtime A, awaited in runtime B); runtime interrupted between export and conversion.","solutions":["Return a genuine *goja.Promise created by the same runtime instead of a custom promise-like type","Check the %T in the message to identify the offending Go type","Ensure the promise and the runtime used for conversion are the same instance","Verify the runtime is not interrupted at conversion time"],"exampleFix":"// before\ntype myPromise struct{ done chan struct{} }\nreturn &myPromise{} // not a real goja promise\n// after\npromise, resolve, reject := rt.NewPromise()\nreturn promise","handlingStrategy":"type-guard","validationCode":"result := fn()\nif isGoPromise(result) {\n    if _, ok := result.(*goja.Promise); !ok {\n        return fmt.Errorf(\"must return *goja.Promise, got %T\", result)\n    }\n}","typeGuard":"func isRealPromise(rt *goja.Runtime, result any) bool {\n    p, ok := result.(*goja.Promise)\n    return ok && p != nil\n}","tryCatchPattern":"resultObj := resultJs.ToObject(rt)\nif resultObj == nil {\n    callback(rt, &CallResult{Error: fmt.Errorf(\"unconvertible promise result\")})\n    return\n}","preventionTips":["Return *goja.Promise created via rt.NewPromise() from the same runtime","Avoid custom promise-like Go structs in plugin handlers","Never share promise objects across goja runtimes","Check the %T in the error message to find the wrong return type quickly"],"tags":["goja","plugin-sandbox","async","promise"],"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"}