{"record":{"id":"b2f93e40cd15fa34","repo":"wailsapp/wails","slug":"cancellablepromise-prototype-then-called-on-an-inv","errorCode":null,"errorMessage":"CancellablePromise.prototype.then called on an invalid object.","messagePattern":"CancellablePromise\\.prototype\\.then called on an invalid object\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts","lineNumber":370,"sourceCode":"     * The returned promise is hooked up to propagate cancellation requests up the chain, but not down:\n     *\n     *   - if the parent promise is cancelled, the `onrejected` handler will be invoked with a `CancelError`\n     *     and the returned promise _will resolve regularly_ with its result;\n     *   - conversely, if the returned promise is cancelled, _the parent promise is cancelled too;_\n     *     the `onrejected` handler will still be invoked with the parent's `CancelError`,\n     *     but its result will be discarded\n     *     and the returned promise will reject with a `CancelError` as well.\n     *\n     * The promise returned from {@link cancel} will fulfill only after all attached handlers\n     * up the entire promise chain have been run.\n     *\n     * If either callback returns a cancellable promise,\n     * cancellation requests will be diverted to it,\n     * and the specified `oncancelled` callback will be discarded.\n     */\n    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1> | CancellablePromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2> | CancellablePromiseLike<TResult2>) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise<TResult1 | TResult2> {\n        if (!(this instanceof CancellablePromise)) {\n            throw new TypeError(\"CancellablePromise.prototype.then called on an invalid object.\");\n        }\n\n        // NOTE: TypeScript's built-in type for then is broken,\n        // as it allows specifying an arbitrary TResult1 != T even when onfulfilled is not a function.\n        // We cannot fix it if we want to CancellablePromise to implement PromiseLike<T>.\n\n        if (!isCallable(onfulfilled)) { onfulfilled = identity as any; }\n        if (!isCallable(onrejected)) { onrejected = thrower; }\n\n        if (onfulfilled === identity && onrejected == thrower) {\n            // Shortcut for trivial arguments.\n            return new CancellablePromise((resolve) => resolve(this as any));\n        }\n\n        const barrier: Partial<PromiseWithResolvers<void>> = {};\n        this[barrierSym] = barrier;\n\n        return new CancellablePromise<TResult1 | TResult2>((resolve, reject) => {","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/wailsapp/wails/blob/0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3/v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts#L352-L388","documentation":"SetTextColor sets the foreground color for text and vector drawing on a DC and returns the previous color. The w32 wrapper panics with 'SetTextColor failed' when the return equals CLR_INVALID (0xFFFFFFFF), the documented failure value. An invalid or deleted HDC is the usual cause; the color value itself is almost never the problem because nearly every 32-bit value is a legal COLORREF.","triggerScenarios":"Calling SetTextColor with an HDC after EndPaint/ReleaseDC/DeleteDC; calling it on a DC owned by another thread while GDI internal state is being torn down; stacking color setup calls in a helper that outlives the paint cycle.","commonSituations":"Custom WM_PAINT handlers that stash the HDC for later use after the handler returns; drawing during app shutdown when the window DC is gone; color-setup helpers called from a goroutine rather than the UI thread.","solutions":["Keep all SetTextColor calls inside the same paint scope that owns the HDC; do not cache HDCs across messages.","Confirm the color is built with a valid RGB() (avoid passing CLR_INVALID itself as the 'color').","Run all GDI drawing on the thread that created the window to avoid DC lifetime races.","If it panics only at exit, add an is-closing guard around paint logic."],"exampleFix":"// before\nhdc := w32.GetDC(hwnd)\nqueueDraw(func(){ w32.SetTextColor(w32.HDC(savedHdc), c) }) // savedHdc released already\n\n// after\nhdc := w32.GetDC(hwnd)\nw32.SetTextColor(hdc, c)\ndrawTextNow(hdc)\nw32.ReleaseDC(hwnd, hdc)","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"defer func() {\n\tif r := recover(); r != nil {\n\t\tif msg, _ := r.(string); strings.HasPrefix(msg, \"SetTextColor failed\") {\n\t\t\tlog.Printf(\"color setup skipped: DC likely released\")\n\t\t\treturn\n\t\t}\n\t\tpanic(r)\n\t}\n}()\nw32.SetTextColor(hdc, color)","preventionTips":["Do not cache HDCs across messages or goroutines.","Perform color setup immediately before the draw call in the same handler.","Ensure ReleaseDC/EndPaint run after all drawing, exactly once."],"tags":["windows","gdi","text-rendering","syscall","panic"],"backgroundTag":null,"analyzedSha":"0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3","analyzedAt":"2026-08-15T14:17:36.034Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}