Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
JavaScript

JavaScript: Fix a Promise.race() Call Whose Losing Promises Keep Running in the Background After It Resolves

- 03.10.26 - ErcanOPAK

πŸ”§ The Promise.race() Winner That Didn’t Actually Stop Anything

Promise.race() settles as soon as the first of several promises settles, and returns that result – but it has no way to reach into the other promises and stop them, because a JavaScript Promise has no built-in cancellation mechanism at all. A promise that ‘loses’ the race keeps running exactly as if it had never been raced against anything, and whatever it eventually does – resolve with a now-unused result, or reject with an error nobody is positioned to catch – still happens, just later and unnoticed.

πŸ”Ž The Problem

function withTimeout(promise, ms) {
    const timeout = new Promise((_, reject) =>
        setTimeout(() => reject(new Error("Timed out")), ms)
    );
    return Promise.race([promise, timeout]);
}

async function loadProfile(userId) {
    const result = await withTimeout(fetchProfile(userId), 2000);
    return result;
}
// fetchProfile() keeps running in the background even after the
// 2-second timeout "wins" the race - it's not cancelled, aborted,
// or told to stop in any way. If it eventually resolves, nothing
// is listening anymore. If it eventually REJECTS instead, that
// rejection has no .catch() attached to it by this point, and
// surfaces as an unhandled promise rejection seconds later, far
// from the timeout logic that seemingly already "handled" it.

βœ… Fix: Actually Cancel the Loser, Don’t Just Ignore It

  • Pass an AbortController’s signal into the underlying request (fetch and many libraries support this directly) and call controller.abort() specifically when the timeout promise wins the race, so the losing operation is actually told to stop instead of merely being ignored.
  • Attach a no-op .catch() to the original (potentially losing) promise regardless of whether it wins the race, specifically to prevent an eventual rejection from surfacing as an unhandled rejection later, even if the result itself is no longer needed.
  • For anything with a real cost to leaving it running (an open network connection, a database query, a file read), treat Promise.race() as choosing which result to use, not as a cancellation mechanism – actual cancellation needs to be wired up separately and explicitly.

⚠️ Why This Is Easy to Miss

  • The timeout path works exactly as expected in every manual test – the request that lost the race finishing (or failing) in the background a few seconds later doesn’t visibly break anything in the moment, which is why the missing cancellation goes unnoticed for a long time.
  • An unhandled rejection from the losing promise can crash a Node.js process outright (depending on configuration) or spam error-tracking tools in a browser, and by the time it surfaces, the stack trace points at generic promise machinery rather than the original timeout logic that was actually responsible.

Promise.race() picks a winner – it never tells the losers the race is over, and they keep running exactly as if they’d won it too.

β€” JavaScript Tip

Related posts:

JavaScript forEach Can’t Be awaited

JavaScript: Why Array.sort() Turns Your Numbers Into Strings and Wrecks the Order

JavaScript Event Listeners Slowly Degrade Performance

Post Views: 4

Post navigation

C#: Why an Interface’s Default Method Only Runs When You Call It Through the Interface, Not Through the Class
ASP.NET Core: Fix a Minimal API Action That Receives null Because It Expects an Object but the Client Sent a JSON Array

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

October 2026
M T W T F S S
 1234
567891011
12131415161718
19202122232425
262728293031  
« Sep    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (973)
  • How to make theater mode the default for Youtube (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (938)
  • Get the First and Last Word from a String or Sentence in SQL (847)
  • How to select distinct rows in a datatable in C# (836)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (624)
  • Add Constraint to SQL Table to ensure email contains @ (590)
  • Average of all values in a column that are not zero in SQL (553)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (526)
  • Find numbers with more than two decimal places in SQL (468)

Recent Posts

  • CSS: Fix a prefers-color-scheme Media Query That Gets Silently Overridden by a Browser Extension’s Forced Dark Mode
  • Git: Fix a Merge Commit That Silently Drops a File Because Both Branches Deleted It Differently
  • HTML5: Fix a Native Lazy-Loading Image That Never Loads Because It Sits Inside a Hidden Tab Until the User Clicks It
  • The AI Prompt That Traces a Null Reference Exception Back to the Exact Line That First Produced the Null
  • The AI Prompt That Turns a Gym Membership Contract’s Fine Print Into a Plain-English List of Cancellation Steps
  • Photoshop: Fix a Color Profile Mismatch That Makes Printed Output Look Nothing Like What You Saw On Screen
  • WordPress: Fix Search Results That Return Pages From a Theme You Deactivated Months Ago
  • Visual Studio: Fix a Test Project That Builds Fine Alone but Fails to Discover Any Tests After a NuGet Restore
  • ASP.NET Core: Fix a File Upload That Times Out on Slow Connections Only Because Kestrel’s Minimum Data Rate Feature Kicked In
  • JavaScript: Fix an Array Destructuring Default Value That Silently Never Applies Because null Was Passed Instead of Undefined

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (973)
  • How to make theater mode the default for Youtube (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (938)
  • Get the First and Last Word from a String or Sentence in SQL (847)
  • How to select distinct rows in a datatable in C# (836)

Recent Posts

  • CSS: Fix a prefers-color-scheme Media Query That Gets Silently Overridden by a Browser Extension’s Forced Dark Mode
  • Git: Fix a Merge Commit That Silently Drops a File Because Both Branches Deleted It Differently
  • HTML5: Fix a Native Lazy-Loading Image That Never Loads Because It Sits Inside a Hidden Tab Until the User Clicks It
  • The AI Prompt That Traces a Null Reference Exception Back to the Exact Line That First Produced the Null
  • The AI Prompt That Turns a Gym Membership Contract’s Fine Print Into a Plain-English List of Cancellation Steps

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com