Skip to content

Commit 6ef0037

Browse files
committed
Add a retry combinator w/ exponential backoff
1 parent 7ac1f55 commit 6ef0037

File tree

3 files changed

+53
-2
lines changed

3 files changed

+53
-2
lines changed

bower.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"purescript-dom": "^0.1.2",
2929
"purescript-foreign": "^0.5.0",
3030
"purescript-integers": "^0.2.0",
31+
"purescript-math": "^0.2.0",
3132
"purescript-nullable": "^0.2.0",
3233
"purescript-unsafe-coerce": "^0.1.0"
3334
},

docs/Network.HTTP.Affjax.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ delete_ :: forall e. URL -> Affjax e Unit
144144

145145
Makes a `DELETE` request to the specified URL and ignores the response.
146146

147+
#### `retry`
148+
149+
``` purescript
150+
retry :: forall e a b. (Requestable a) => Maybe Int -> (AffjaxRequest a -> Affjax (avar :: AVAR | e) b) -> AffjaxRequest a -> Affjax (avar :: AVAR | e) b
151+
```
152+
153+
Retry a request with exponential backoff, timing out optionally after a specified number of milliseconds.
154+
147155
#### `affjax'`
148156

149157
``` purescript

src/Network/HTTP/Affjax.purs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,32 @@ module Network.HTTP.Affjax
1010
, post, post_, post', post_'
1111
, put, put_, put', put_'
1212
, delete, delete_
13+
, retry
1314
) where
1415

1516
import Prelude
17+
import Control.Alt ((<|>))
1618
import Control.Bind ((<=<))
17-
import Control.Monad.Aff (Aff(), makeAff, makeAff', Canceler(..))
19+
import Control.Monad.Aff (Aff(), makeAff, makeAff', Canceler(..), attempt, later', forkAff, cancel)
20+
import Control.Monad.Aff.Par (Par(..), runPar)
21+
import Control.Monad.Aff.AVar (AVAR(), makeVar, makeVar', takeVar, putVar)
1822
import Control.Monad.Eff (Eff())
1923
import Control.Monad.Eff.Exception (Error(), error)
24+
import Control.Monad.Error.Class (throwError)
2025
import Data.Either (Either(..))
2126
import Data.Foreign (Foreign(..), F(), parseJSON, readString)
2227
import Data.Function (Fn5(), runFn5, Fn4(), runFn4)
28+
import Data.Int (toNumber, round)
2329
import Data.Maybe (Maybe(..), maybe)
2430
import Data.Nullable (Nullable(), toNullable)
2531
import DOM.XHR (XMLHttpRequest())
32+
import Math (pow)
2633
import Network.HTTP.Affjax.Request
2734
import Network.HTTP.Affjax.Response
2835
import Network.HTTP.Method (Method(..), methodToString)
2936
import Network.HTTP.RequestHeader (RequestHeader(), requestHeaderName, requestHeaderValue)
3037
import Network.HTTP.ResponseHeader (ResponseHeader(), responseHeader)
31-
import Network.HTTP.StatusCode (StatusCode())
38+
import Network.HTTP.StatusCode (StatusCode(..))
3239

3340
-- | The effect type for AJAX requests made with Affjax.
3441
foreign import data AJAX :: !
@@ -117,6 +124,41 @@ delete u = affjax $ defaultRequest { method = DELETE, url = u }
117124
delete_ :: forall e. URL -> Affjax e Unit
118125
delete_ = delete
119126

127+
-- | Retry a request with exponential backoff, timing out optionally after a specified number of milliseconds.
128+
retry :: forall e a b. (Requestable a) => Maybe Int -> (AffjaxRequest a -> Affjax (avar :: AVAR | e) b) -> (AffjaxRequest a -> Affjax (avar :: AVAR | e) b)
129+
retry milliseconds run req = do
130+
failureVar <- makeVar
131+
let loop = go failureVar
132+
case milliseconds of
133+
Nothing -> loop 1
134+
Just milliseconds -> do
135+
respVar <- makeVar
136+
loopHandle <- forkAff $ loop 1 >>= putVar respVar <<< Just
137+
timeoutHandle <-
138+
forkAff <<< later' milliseconds $ do
139+
putVar respVar Nothing
140+
loopHandle `cancel` error "Cancel"
141+
result <- takeVar respVar
142+
case result of
143+
Nothing ->
144+
takeVar failureVar
145+
Just resp -> pure resp
146+
where
147+
assert200 resp =
148+
case resp.status of
149+
StatusCode 200 -> Right resp
150+
_ -> Left resp
151+
152+
go failureVar n = do
153+
result <- run req
154+
case assert200 result of
155+
Right b -> pure b
156+
Left resp -> do
157+
putVar failureVar resp
158+
-- TODO: is this too steep?
159+
let delay = round $ toNumber 1000 * pow (toNumber 2) (toNumber (n - 1))
160+
later' delay $ go failureVar (n + 1)
161+
120162
-- | Run a request directly without using `Aff`.
121163
affjax' :: forall e a b. (Requestable a, Respondable b) =>
122164
AffjaxRequest a ->

0 commit comments

Comments
 (0)