Preventing Race Conditions with switchMap

In the Days of Olde, when magic still roamed the land, a High Programmer insulted a Network, and ever since then, the networks have had it out for us programmers. A typeahead race condition bug typically manifests itself like so:

  1. user types a
  2. get/render response for a
  3. user types ab
  4. user types abc
  5. get/render response for abc
  6. get/render response for ab

This could happen for many reasons—an ISP could have directed the abc query through a less-congested router, abc could have had fewer possible answers, resulting in a faster query, or the Network remembered that grudge from a long time ago. Regardless of the reason, our user now has the wrong results in front of them. How can you prevent this terrible tragedy?

Back in the days of VanillaJS, a solution might have started off based on an event listener:

 let​ latestQuery;
 searchBar.addEventListener(​'keyup'​, event => {
 let​ searchVal = latestQuery = event.target.value;
  fetch(endpoint + searchVal)
  .then(results => {
 if​ (searchVal === latestQuery) {
  updatePage(results);
  }
  });
 });

Technically, this works though the exterior variable latestQuery might lead to some raised eyebrows in a code review. Look at this observable solution:

 fromEvent(searchBar, ​'keyup'​)
 .pipe(
  pluck(​'target'​, ​'value'​),
  switchMap(query => ajax(endpoint + searchVal))
 )
 .subscribe(results => updatePage(results));

As usual, a new operator has snuck in for you to learn. This time it’s switchMap—an operator that’s been stealing notes from mergeMap. switchMap works the same way as mergeMap: for every item, it runs the inner observable, waiting for it to complete before sending the results downstream. There’s one big exception: if a new value arrives before the inner observable initiated by the previous value completes, switchMap unsubscribes from the observable request (therefore cancelling it) and fires off a new one. This means that you can implement custom unsubscribe logic for your own observables (like the ones you built in Chapter 1, Creating Observables).

In the switchMap example above, abc would be passed to switchMap before the query for ab is finished, and therefore, ab’s result would be thrown away with nary a care. One way to think about this is that switchMap switches to the new request. The Rx version of the typeahead has each step wrapped up in its own functional package, leading to much more organized code. Let’s look at what happens now when the network requests get mixed up:

  1. user types a
  2. switchMap sees a, makes a note
  3. get/render response for a
  4. switchMap removes note for a
  5. user types ab
  6. switchMap sees ab, makes a note
  7. user types abc
  8. switchMap sees abc, sees that it has a note about ab
  9. switchMap replaces the ab note with one about abc
  10. get/render response for abc
  11. switchMap removes note for abc
  12. get response for ab
  13. switchMap sees response for ab and discards it because there’s no corresponding note

That’s a lot going on behind the scenes! Thankfully, the RxJS library handles all of these details.

Both the addEventListener and fromEvent snippets are missing part of the requirements—they don’t wait for the user to stop typing before making a request, leading to a lot of unneeded requests. This is a great way to make the backend engineers angry—let’s avoid that. Instead, how about you implement a debounce function?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.163.238