siyuan-note/siyuan · error

promise rejected

Error message

promise rejected: %v

What it means

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.

Solutions

  1. Read the %v portion to see the original rejection reason thrown by the plugin
  2. Fix the underlying exception in the plugin's handler code (often a null/undefined access or a failed network call)
  3. Add try/catch inside the plugin handler to convert expected failures into normal error responses

Example fix

// before - plugin handler
async function handler(req) { return await riskyFetch(req); }
// after
async function handler(req) {
  try { return await riskyFetch(req); }
  catch (e) { return { code: -1, msg: String(e) }; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

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; } }

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/98b6783ae7b4f15f. Report an issue: GitHub.

Appendix: source

Thrown at kernel/plugin/sandbox.go:407

		}

		thenValue := resultObj.Get("then")
		then, ok := goja.AssertFunction(thenValue)
		if !ok {
			callback(rt, &CallResult{Error: fmt.Errorf("'promise.then property is not a function")})
			return
		}

		then(resultObj, rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) {
			// ⚠️ call.Arguments always is an empty array.
			promise, ok := result.(*goja.Promise)
			if ok {
				callback(rt, &CallResult{Value: promise.Result()})
			} else {
				callback(rt, &CallResult{Value: resultJs})
			}
		}), rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) {
			callback(rt, &CallResult{Error: fmt.Errorf("promise rejected: %v", call.Argument(0).Export())})
		}))
	} else {
		callback(rt, &CallResult{Value: resultJs})
	}
}

// isJsPromise checks if a goja.Value is a JavaScript Promise.
func isJsPromise(jsValue goja.Value) bool {
	if jsValue == nil {
		return false
	}

	goValue := jsValue.Export()
	return isGoPromise(goValue)
}

// isGoPromise checks if a Go value is a *goja.Promise.
func isGoPromise(goValue any) bool {

View on GitHub (pinned to 9f775e8a12)