Indroduction – The Use Case of Polling
One of the most common things done by a web application is to call a server endpoint to invoke some action. The action could mean buying a product, registering an account or adding an article to the database. Most of the time, such actions are short running. By short running I mean that they complete within the duration of a single HTTP request and the server can send a response indicating whether the action succeeded.
However, not all actions are short running. Sometimes, the action may require some significant computations or even manual input. To name a few examples:
- Image recognition on an uploaded photograph
- ID document verification by an actual human
- Heavy calculation on a large data set
In such cases, the action may not finish before the request times out. As a result, there is no way of informing the user whether the action succeeded or not (unless you notify them some other way, e.g. by e-mail).
Polling Explained
There are several solutions to this problem but in this post we’ll focus on polling. In order to implement polling, the API needs to be implemented in a special way. The endpoint to invoke the action should support at least two HTTP methods:
POSTfor initiating the actionGETfor checking the status of the action (and getting the results if available)- (optionally)
DELETEfor cancelling an already started action
Given such endpoint, polling can be described by the following steps:
- The frontend sends a
POSTrequest to the endpoint to initiate the action. The server replies with an identifier representing the action. - The frontend sends a
GETrequest to the endpoint, passing the identifier as a parameter. The server replies with a status of the action – either in progress or done. - The frontend keeps repeating the
GETrequest as long as the status is not done every X milliseconds. - The last response to the
GETrequest should contain the actual results.
Implementing Polling with RxJS
As you can see, the polling algorithm consists of several steps, all of them being asynchronous. It is exactly the kind of scenario where RxJS shows its usefulness. Let’s see how to implement polling with RxJS in just a few lines of code.
In this example we’re working with an API for analysing the sentiment of a long piece of text. On a high level, given a string the API returns a boolean value indicating whether the sentiment of the text is postive or negative. Since the analysis can take up to a few minutes, the API is asynchronous and requires polling. The exact shape of the API is as follows. You can find a very basic implementation of this API here.
POSTrequest to/analyzewith an example payload of{ message: "text to be analyzed" }starts an analysis job and returns its identifierGETrequest to/analyze/{jobId}returns the status of the job and, optionally, the resultPOSTrequest to/analyze/{jobId}/cancelcancels the job
Given such an API, let’s implement a simple polling flow with RxJS.
1 | const startAnalysis = ( ) => { |
Let’s analyze this code line by line:
- First, we create an
Observableusingajax.post. ThisObservablesends aPOSTrequest to${url}/analyzewhen subscribed. It emits a single item – the response – and immedietly completes. - Next, for each item emitted by that
Observable(and we know that there will be just one), we switch to a newObservable. This newObservablesends aGETrequest to${url}/analyze/${ajaxResponse.response.id}and emits the response (just once). - We apply the
repeatoperator thatObservable. This operator resubscribes to the sourceObservableonce it completes. Thanks to thedelayparameter, it will only resubscribe after waiting 1 second. Each new subscribtion will cause the sourceObservableto send a newGETrequest. - As of now,
GETrequests would keep happening forever and we need to tell ourObservablewhen to stop.takeWhileoperator will keep emitting for as long as the incoming items satisfy the passed condition. Once the condition is violated, it will emit one more item (thanks totruepassed as the second parameter) and complete. - Finally, we subscribe to the resulting
Observable. We expect thisObservableto emit a respone withinProgressstatus every second for some time, then emit a single response withfinishedstatus and then complete.
You may be wondering why repeat and takeWhile operators are in a “nested” pipe instead of right after switchMap. In other words, could we change this code to:
1 | .pipe( |
The way repeat operator works is that it resubscribes to the source Observable once it completes. The source, in this case, is the Observable returned by post function. Resubscribing it would result in a new POST request to ${url}/analyze, which is not what we want. Therefore, we need to apply repeat to the Observable returned by ajax.getJSON.
Once you hit the Analyze button, you’ll observe the following requests in the Network tab of Chrome Dev Tools. Don’t worry about double POST call – the first one is a preflight request.

Polling with Cancellation
This solution works well, but let’s see what happens when the user quickly clicks multiple times on the Analyze button.

It resulted in multiple polling sessions running concurrently. This is pretty bad, since each analysis is computationally costly and results in unnecessary load on the backend.
Let’s improve our solution to handle this scenario better. What’s needed here is cancellation. There are actually two aspets of cancellation:
- stop polling the endpoint to avoid unnecessary network traffic
- stop the actual analysis on the backend to avoid the computational cost
We’ll address the first aspect by introducing a cancelSubject. We’ll emit from this subject whenever polling should be cancelled. The nested polling stream will complete whenever cancelSubject emits. For the latter, we’ll need a dedicated endpoint for cancelling the operation. Unless it is exposed by the server, we won’t be able to address this concern.
1 | // Make sure to memoize it if you're using React |
We introduced a new function – cancelAnalysis. It does two things:
- emit on the
cancelSubjectwhich will cause the nested polling stream to complete thanks to thetakeUntiloperator - send a cancellation request to the backend
cancelAnalysis is called from startAnalysis but it can also be called explicitly, for example if want to allow the user to stop the analysis at any point of time.
Now you can observe the flow of server requests in the “fast-clicking” scenario. We can see that there is only a single polling session at a time and the the previous session always gets cancelled before starting a new session.

Summary
In this article I explained when to use polling and how to implement it in RxJS. This is a great example of a problem that can be easily implemented in RxJS but would require a complex solution if done imperatively.

