Problem: as the user types text, asynchronously search using the entered text. Display the results from the latest search.
This problem is classic power showcase of the functional reactive programming. Here is sample implementation using RxJs
1 | var $input = $('#input'); |
Notice several interesting features reactive programming implements that are difficult to implement even using promises, and a major pain to implement using callbacks.
we throttle key events and only generate an event with entered text once the user stopped typing for 500ms, line
// 1
Because AJAX requests could return out of order, we need to make sure we only display results from the last search request. We are ignoring any search results from non-last search requests, line
// 2
Can we implement same features using promises?
Throttling
Ari Lerner in his ng-book gives good example how to throttle requests using AngularJs $timeout service.
1 | app.controller('ServiceController', function($scope, $timeout, githubService) { |
Any time user changes the username
value by typing into a text box, a callback function
will execute (line // 1
). Instead of searching immediately, we set a timeout of 350ms.
If there is already a timeout, we cancel it (line // 2
).
Using latest result
The above implementation still suffers from a huge issue. We still might execute several search requests in parallel.
at 0 ms user types 'john'
at 350ms we call the search service for 'john'
at 1000ms user gets tired of waiting and types 'mary'
at 1350ms we call the search service for `mary`
at 1400ms search returns results for `mary`
at 1600ms search returns results for `john`
Because the first search takes longer, user briefly sees second search results, then the screen flips to first results. How do we use only the results of the latest promise?
Promises are easy to either chain p1.then(p2).then ...
or execute in parallel
Q.all([p1, p2]).then ...
. It is much harder to control them when promises p1
and p2
can execute in parallel, but only the p2
should be used.
chaining
We could make sure the last promise is always used by chaining it to the current latest one
1 | var latest; |
This code always adds new search promise returned in // 1
to the current latest
promise. The searches execute in parallel, but they will be displayed in order.
Unfortunately this code has a huge problem. Every promise in the chain has to resolve, before latest results can be displayed, even if the last search request has returned.
at 350ms latest promise is search for `john`
at 1350ms we add second link to search for `mary`
search('john').then(search(`mary'))
at 1400ms the search for 'mary' gets resolved AND WAITS
at 1600ms the search for 'john' gets resolved,
then the search for `mary` starts (already resolved)
then `mary` results are displayed
We are correctly ignoring search results from non-latest promise, but we still wait for 200ms. This code also has another problem - if non-latest promise fails, the entire chain fails!
detecting latest promise
Instead of using chaining to get the latest results, we could just ignore results from non-latest promises. We can keep each promise separate, but add on a check
1 | var latest; |
We keep a reference to the latest promise, and after a promise has been resolved check if it is the same. If not, we throw an error we can detect in other parts of code. Our search example now executes correctly and efficiently
at 350ms latest promise is search for `john`
at 1350ms latest promise is search for `mary`
at 1400ms search for `mary` resolves
it checks if it is still the latest promise (line // 1) - true
prints results for `mary`
at 1600ms search for `john` resolves
it checks if it is still the latest promise (line // 1) - false
throws an error
Only the results from the latest executed search are displayed, just like the problem requires.
Update 1
I published the latest-promise as NPM package. It works should work with any promise and allows the rejected promise to check if it was cancelled because it became obsolete
1 | var pickLatest = require('latest-promise'); |