Open EugeneN opened 8 years ago
Couple of screenshots illustrating the problem and xhr abort feature:
Before - a request issued earlier finishes much later than the next one:
After - a request issued earlier is cancelled before issuing the next one:
what's the advantage of this new api over the following:
f = do
tid <- forkIO (xhr ... >>= doSomethingWithResult)
...
when abortCondition (killThread tid)
this aborts the request if necessary
well, it has no advantage if the resources are released (XHRs cancelled), indeed. And this would allow to keep API surface smaller.
Theoretically, one could want to still have a way to operate on XHR-requests-level, if threads orchestration in complex business logic would be too hard or interfering with other logic, or produced some overhead.
For my case threads solution should be enough though. Feel free to close the pr then :-)
Btw, another difference is that forkIO
solution would require manual threads synchronisation using MVar
s or more advanced techniques, while xhr'
solution does all the async action under the hood while looking completely synchronous to the app's code (or is there a way of doing such a thing with threads (not counting foreign import interruptible
wrappers over Haskell functions, of course)?).
While the sync task is completely doable it could still be convenient to think about XHRs as a primitive synchronous operation which can be used with no extra work.
It would decrease API clear-ness probably. On the other hand, it's web programming, XHRs are omnipresent - maybe it's worth having richer low-level API for them?
Or maybe the best solution would be to just export doRequest
, xhrCreate
, xhrAbort
functions and XHR
type.
doRequest
is not a new function, but a lambda extracted from existing xhr
function: https://github.com/EugeneN/ghcjs-base/blob/c18c2702cd0e5a6b5ad2f8c997b56b126119398c/JavaScript/Web/XMLHttpRequest.hs#L134-L178
In this way no new code is added at all, just few existing primitives exported, so that users could implement any desirable API to XHRs should the default option (which could be easily reconstructed from exported primitives: xhr req = xhrCreate >>= \x -> doRequest req x
onExceptionxhrAbort x
) fail to satisfy their demands.
Ability to cancel pending [XHR] requests from the business logic is important in real-world applications.
The most basic example is an autocomplete functionality. It continuously issues XHR requests while a user is typing a search term, aggregating input into some chunks. It might happen that a request issued earlier will arrive later than a request with a more recent input, overwriting displayed results.
One could handle this by using some monotonic request ids, but this would complicate things a quite lot.
Also, freeing unneeded resource is a good practice, especially important in a browser. Browsers have a limit on number of simultaneous connections to the same origin as low as 6. Should a user type fast, and should requests stall for a bit, connections limit would be exhausted, which would cause kind of denial-of-service by the browser, degrading user experience, interrupting other functionality etc.
So for business logic to be able to cancel requests it would need a request handle. This PR adds a family of
xhr*'
functions (xhr'
,xhrByteString'
,xhrText'
,xhrString'
) which take an XHR object handle as a parameter, and exports 2 existing functions for creating and aborting XHRs (xhrCreate
,xhrAbort
).So the low-level API looks like this:
The API could be implemented in a more advanced way, but it is a low-level API after all :-)