99use Saloon \Http \PendingRequest ;
1010use Saloon \Contracts \MockClient ;
1111use GuzzleHttp \Promise \PromiseInterface ;
12+ use Saloon \Exceptions \Request \RequestException ;
13+ use Saloon \Exceptions \Request \FatalRequestException ;
1214use Saloon \Contracts \PendingRequest as PendingRequestContract ;
1315
1416trait SendsRequests
@@ -30,6 +32,70 @@ public function send(Request $request, MockClient $mockClient = null): Response
3032 return $ this ->createPendingRequest ($ request , $ mockClient )->send ();
3133 }
3234
35+ /**
36+ * Send a synchronous request and retry if it fails
37+ *
38+ * @param \Saloon\Contracts\Request $request
39+ * @param int $maxAttempts
40+ * @param int $interval
41+ * @param callable|null $handleRetry
42+ * @param bool $throw
43+ * @param \Saloon\Contracts\MockClient|null $mockClient
44+ * @return \Saloon\Contracts\Response
45+ * @throws \ReflectionException
46+ * @throws \Saloon\Exceptions\InvalidResponseClassException
47+ * @throws \Saloon\Exceptions\PendingRequestException
48+ * @throws \Saloon\Exceptions\Request\FatalRequestException
49+ * @throws \Saloon\Exceptions\Request\RequestException
50+ */
51+ public function sendAndRetry (Request $ request , int $ maxAttempts , int $ interval = 0 , callable $ handleRetry = null , bool $ throw = true , MockClient $ mockClient = null ): Response
52+ {
53+ $ currentAttempt = 0 ;
54+ $ pendingRequest = $ this ->createPendingRequest ($ request , $ mockClient );
55+
56+ do {
57+ $ currentAttempt ++;
58+
59+ // When the current attempt is greater than one, we will pause to wait
60+ // for the interval.
61+
62+ if ($ currentAttempt > 1 ) {
63+ usleep ($ interval * 1000 );
64+ }
65+
66+ try {
67+ // We'll attempt to send the PendingRequest. We'll also use the throw
68+ // method which will throw an exception if the request has failed.
69+
70+ return $ pendingRequest ->send ()->throw ();
71+ } catch (FatalRequestException |RequestException $ exception ) {
72+ // We won't create another pending request if our current attempt is
73+ // the max attempts we can make
74+
75+ if ($ currentAttempt === $ maxAttempts ) {
76+ return $ exception instanceof RequestException && $ throw === false ? $ exception ->getResponse () : throw $ exception ;
77+ }
78+
79+ $ pendingRequest = $ this ->createPendingRequest ($ request , $ mockClient );
80+
81+ // When either the FatalRequestException happens or the RequestException
82+ // happens, we should catch it and check if we should retry. If someone
83+ // has provided a callable into $handleRetry, we'll wait for the result
84+ // of the callable to retry.
85+
86+ if (is_null ($ handleRetry ) || $ handleRetry ($ exception , $ pendingRequest ) === true ) {
87+ continue ;
88+ }
89+
90+ // If we should not retry, we need to return the last response. If the
91+ // exception was a RequestException, we should return the response,
92+ // otherwise we'll throw the exception.
93+
94+ return $ exception instanceof RequestException && $ throw === false ? $ exception ->getResponse () : throw $ exception ;
95+ }
96+ } while ($ currentAttempt < $ maxAttempts );
97+ }
98+
3399 /**
34100 * Send a request asynchronously
35101 *
0 commit comments