@@ -5,6 +5,7 @@ module Reflex.Dom.Xhr
5 , XMLHttpRequest
5 , XMLHttpRequest
6 , XhrResponseBody(..)
6 , XhrResponseBody(..)
7 , XhrResponseType(..)
7 , XhrResponseType(..)
8 , responseTextToText
9 , responseTextToText
9 , xmlHttpRequestGetReadyState
10 , xmlHttpRequestGetReadyState
10 , xmlHttpRequestGetResponseText
11 , xmlHttpRequestGetResponseText
@@ -20,6 +21,7 @@ module Reflex.Dom.Xhr
22import Control.Concurrent
23import Control.Concurrent
24+import Control.Exception (catch)
23import Control.Lens
25import Control.Lens
24import Control.Monad hiding (forM)
26import Control.Monad hiding (forM)
25import Control.Monad.IO.Class
27import Control.Monad.IO.Class
@@ -37,9 +39,11 @@ import Data.Text.Encoding
37import Data.Traversable
39import Data.Traversable
38import Reflex
40import Reflex
39import Reflex.Dom.Class
41import Reflex.Dom.Class
42+import Reflex.Dom.Xhr.Exception
40import Reflex.Dom.Xhr.Foreign
43import Reflex.Dom.Xhr.Foreign
41import Reflex.Dom.Xhr.ResponseType
44import Reflex.Dom.Xhr.ResponseType
42import Data.Typeable
45import Data.Typeable
46+-- import GHCJS.DOM.JSFFI.XMLHttpRequest (XHRError(..))
44data XhrRequest
48data XhrRequest
45 = XhrRequest { _xhrRequest_method :: String
49 = XhrRequest { _xhrRequest_method :: String
@@ -85,12 +89,24 @@ instance Default XhrRequestConfig where
85xhrRequest :: String -> String -> XhrRequestConfig -> XhrRequest
89xhrRequest :: String -> String -> XhrRequestConfig -> XhrRequest
86xhrRequest = XhrRequest
90xhrRequest = XhrRequest
92+-- | Make a new asyncronous XHR request. This does not block (it forks),
93+-- and returns an XHR object immediately (which you can use to abort
94+-- the XHR connection), and will pass an exception ('XhrException') to the
95+-- continuation if the connection cannot be made (or is aborted).
88−newXMLHttpRequest :: (HasWebView m, MonadIO m, HasPostGui t h m) => XhrRequest -> (XhrResponse -> h ()) -> m XMLHttpRequest
97+ :: (HasWebView m, MonadIO m, HasPostGui t h m)
99+ -- ^ The request to make.
100+ -> (Either XhrException XhrResponse -> h ())
101+ -- ^ A continuation to be called once a response comes back, or in
104+ -- ^ The XHR request, which could for example be aborted.
89newXMLHttpRequest req cb = do
105newXMLHttpRequest req cb = do
90 wv <- askWebView
106 wv <- askWebView
91 postGui <- askPostGui
107 postGui <- askPostGui
93− xhr <- xmlHttpRequestNew wv
108+ xhr <- liftIO $ xmlHttpRequestNew wv
109+ void $ liftIO $ forkIO $ flip catch (postGui . cb . Left) $ void $ do
94 let c = _xhrRequest_config req
110 let c = _xhrRequest_config req
95 rt = _xhrRequestConfig_responseType c
111 rt = _xhrRequestConfig_responseType c
96 xmlHttpRequestOpen
112 xmlHttpRequestOpen
@@ -112,24 +128,42 @@ newXMLHttpRequest req cb = do
112 then liftIO $ xmlHttpRequestGetResponseText xhr
128 then liftIO $ xmlHttpRequestGetResponseText xhr
113 else return Nothing
129 else return Nothing
114 r <- liftIO $ xmlHttpRequestGetResponse xhr
130 r <- liftIO $ xmlHttpRequestGetResponse xhr
131+ _ <- liftIO $ postGui $ cb $ Right $
115− _ <- liftIO $ postGui $ cb $ XhrResponse { _xhrResponse_status = status
132+ XhrResponse { _xhrResponse_status = status
116− , _xhrResponse_statusText = statusTextToText statusText
133+ , _xhrResponse_statusText = statusTextToText statusText
117− , _xhrResponse_response = r
134+ , _xhrResponse_response = r
118− , _xhrResponse_responseText = responseTextToText t
135+ , _xhrResponse_responseText = responseTextToText t
120 return ()
137 return ()
121 else return ()
138 else return ()
122 _ <- xmlHttpRequestSend xhr (_xhrRequestConfig_sendData c)
139 _ <- xmlHttpRequestSend xhr (_xhrRequestConfig_sendData c)
123− return xhr
140+ return ()
125−-- | Given Event of requests, issue them when the Event fires. Returns Event of corresponding responses.
143+-- | Given Event of requests, issue them when the Event fires.
144+-- Returns Event of corresponding responses.
146+-- The request is processed asynchronously, therefore handling does
147+-- not block or cause a delay while creating the connection.
149+ :: (MonadWidget t m)
126−performRequestAsync :: (MonadWidget t m) => Event t XhrRequest -> m (Event t XhrResponse)
150+ => Event t XhrRequest -> m (Event t (Either XhrException XhrResponse))
127performRequestAsync req = performEventAsync $ ffor req $ \r cb -> do
151performRequestAsync req = performEventAsync $ ffor req $ \r cb -> do
128 _ <- newXMLHttpRequest r $ liftIO . cb
152 _ <- newXMLHttpRequest r $ liftIO . cb
129 return ()
153 return ()
131−-- | Issues a collection of requests when the supplied Event fires. When ALL requests from a given firing complete, the results are collected and returned via the return Event.
155+-- | Issues a collection of requests when the supplied Event fires.
156+-- When ALL requests from a given firing complete, the results are
157+-- collected and returned via the return Event.
159+-- The requests are processed asynchronously, therefore handling does
160+-- not block or cause a delay while creating the connection.
162+-- Order of request execution and completion is not guaranteed, but
163+-- order of creation and the collection result is preserved.
165+ :: (Traversable f, MonadWidget t m)
132−performRequestsAsync :: (Traversable f, MonadWidget t m) => Event t (f XhrRequest) -> m (Event t (f XhrResponse))
166+ => Event t (f XhrRequest) -> m (Event t (f (Either XhrException XhrResponse)))
133performRequestsAsync req = performEventAsync $ ffor req $ \rs cb -> do
167performRequestsAsync req = performEventAsync $ ffor req $ \rs cb -> do
134 resps <- forM rs $ \r -> do
168 resps <- forM rs $ \r -> do
135 resp <- liftIO newEmptyMVar
169 resp <- liftIO newEmptyMVar
@@ -142,11 +176,16 @@ performRequestsAsync req = performEventAsync $ ffor req $ \rs cb -> do
142getAndDecode :: (FromJSON a, MonadWidget t m) => Event t String -> m (Event t (Maybe a))
176getAndDecode :: (FromJSON a, MonadWidget t m) => Event t String -> m (Event t (Maybe a))
143getAndDecode url = do
177getAndDecode url = do
144 r <- performRequestAsync $ fmap (\x -> XhrRequest "GET" x def) url
178 r <- performRequestAsync $ fmap (\x -> XhrRequest "GET" x def) url
145− return $ fmap decodeXhrResponse r
183+ Right xhr -> decodeXhrResponse xhr)
147-- | Create a "POST" request from an URL and thing with a JSON representation
186-- | Create a "POST" request from an URL and thing with a JSON representation
148postJson :: (ToJSON a) => String -> a -> XhrRequest
187postJson :: (ToJSON a) => String -> a -> XhrRequest
149−postJson url a =
188+postJson url a =
150 XhrRequest "POST" url $ def { _xhrRequestConfig_headers = headerUrlEnc
189 XhrRequest "POST" url $ def { _xhrRequestConfig_headers = headerUrlEnc
151 , _xhrRequestConfig_sendData = Just body
190 , _xhrRequestConfig_sendData = Just body
@@ -164,4 +203,3 @@ decodeText = decode . BL.fromStrict . encodeUtf8
164-- | Convenience function to decode JSON-encoded responses.
203-- | Convenience function to decode JSON-encoded responses.
165decodeXhrResponse :: FromJSON a => XhrResponse -> Maybe a
204decodeXhrResponse :: FromJSON a => XhrResponse -> Maybe a
166decodeXhrResponse = join . fmap decodeText . _xhrResponse_responseText
205decodeXhrResponse = join . fmap decodeText . _xhrResponse_responseText