ghcjs / ghcjs-base

base library for GHCJS for JavaScript interaction and marshalling, used by higher level libraries like JSC
MIT License
45 stars 67 forks source link

Added ability to explicitly cancel XHR requests #66

Open EugeneN opened 8 years ago

EugeneN commented 8 years ago

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:

do
  handle <- xhrCreate
  forkIO $ xhr’ handle …
  ...
  when condition $ xhrAbort handle

The API could be implemented in a more advanced way, but it is a low-level API after all :-)

EugeneN commented 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: image

After - a request issued earlier is cancelled before issuing the next one: image

luite commented 8 years ago

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

EugeneN commented 8 years ago

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 :-)

EugeneN commented 8 years ago

Btw, another difference is that forkIO solution would require manual threads synchronisation using MVars 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?

EugeneN commented 8 years ago

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 xonExceptionxhrAbort x) fail to satisfy their demands.