Solving JavaScript race conditions with Promises

Alex Johnson pointed an interesting misuse of promises and state at Defusing Race Conditions when Using Promises.

Suppose you have a web app that loads content via Ajax. Let’s bootstrap it:

When you press a button, you trigger onClickChangePage and dispatch a promise that loads (actually just delay a value but let’s not ruin the magic) a value and sets it as the new page.

All buttons have a different delay times. Home is 1 second and Posts 3 seconds.

I’m pretty sure you’ve seen this pattern in almost any framework or VanillaJS so what’s the problem? Just try for yourself:

Click on Posts then quickly click on Home. Home will load soon then Posts will overwrite it. If you think as your user you’d be upset since he where expecting the Home page to be the one that persists on the screen beause it’s the last been clicked.

Alex had a pretty simple solution. Compare IDs whenever setting a new application state. Pretty much handles it. Let’s take a look:

The new lines are 2, 5 and 19. The code pretty much explains itself. The most recent click got recorded as current page and before rendering anything we compare identifiers.

But there’s another interesting solution using Promise.race proposed by Gorgi Kosev.

The Promise.race(iterable) method returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, with the value or reason from that promise. (MDN)

Using Promise.race we can dispatch in parallel both the data loader promise and a promise that watches for changes. Basically, since the race resolves to the first promise that resolve and ignore the others, our render will not be called if a user change pages. Let’s code:

Now we have a pretty special promise called pageChanged. It rejects as soon as it checks that the expected page is not the same. As you can track, the currentPage variable increments every time a button click is triggered. That identifies a new page.

Even though my implementation of pageChanged might not be the best, Promise.race will fail fast. But unless there’s a magic framework (over the hundreds of thousands in JS ecosystem) that fits this logic, this might not be the best.

There we go to another solution by Gorgi, switcher pattern. Let’s do some code:

The switcher function returns a switched on object with methods to change it’s state and the wire method, that takes a value and returns it unless the switch is off.

Combining a fresh switch to your SPA router watching for page changes, you can easily turn off the switch (line 35). Whenever the page is changed (line 44), all onPageChange listener functions gets triggered and old switches off. With a simple helper function (pageUnchanged) you can chain a switch wire method into a promise resolve and throw if the value that came wasn’t wanted anymore.

And you? What you think about these solutions for the same problem? Leave a comment.