or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

caching.mdconfiguration.mdcore-fetch.mdindex.mdnetwork.mdretry.mdsecurity.md

retry.mddocs/

0

# Request Retry Logic

1

2

Configurable retry mechanism for handling transient network failures and server errors with exponential backoff and intelligent retry conditions.

3

4

## Capabilities

5

6

### Retry Configuration

7

8

Configure automatic request retries with detailed control over retry behavior and conditions.

9

10

```javascript { .api }

11

/**

12

* Retry configuration options

13

*/

14

interface RetryOptions {

15

retries?: number; // Number of retry attempts (default: 0)

16

factor?: number; // Exponential backoff factor (default: 2)

17

minTimeout?: number; // Minimum retry timeout in ms (default: 1000)

18

maxTimeout?: number; // Maximum retry timeout in ms (default: Infinity)

19

randomize?: boolean; // Whether to randomize retry intervals (default: false)

20

}

21

22

/**

23

* Callback function called on each retry attempt

24

* @param {Error|Response} cause - Error or response that caused the retry

25

*/

26

type OnRetryCallback = (cause: Error | Response) => void;

27

```

28

29

**Usage Examples:**

30

31

```javascript

32

const fetch = require('make-fetch-happen');

33

34

// Basic retry configuration

35

const response = await fetch('https://unreliable-api.example.com/data', {

36

retry: {

37

retries: 3,

38

factor: 2,

39

minTimeout: 1000,

40

maxTimeout: 10000,

41

randomize: true

42

}

43

});

44

45

// Simple retry count

46

const response2 = await fetch('https://api.example.com/data', {

47

retry: 5 // Equivalent to { retries: 5 }

48

});

49

50

// Disable retries

51

const response3 = await fetch('https://api.example.com/data', {

52

retry: false // Equivalent to { retries: 0 }

53

});

54

55

// With retry callback

56

const response4 = await fetch('https://flaky-api.example.com/data', {

57

retry: { retries: 3 },

58

onRetry: (cause) => {

59

if (cause instanceof Response) {

60

console.log(`Retrying due to ${cause.status} ${cause.statusText}`);

61

} else {

62

console.log(`Retrying due to error: ${cause.message}`);

63

}

64

}

65

});

66

```

67

68

### Retry Conditions

69

70

Requests are automatically retried under specific conditions:

71

72

```javascript { .api }

73

/**

74

* Retry conditions (all must be true):

75

* 1. Request method is NOT 'POST' (to avoid duplicate mutations)

76

* 2. Request does not have a streaming body

77

* 3. One of the following:

78

* a. Response status is 408, 420, 429, or >= 500

79

* b. Request failed with retriable error codes

80

* c. Request failed with retriable error types

81

*/

82

83

/**

84

* Retriable error codes:

85

*/

86

const RETRY_ERRORS = [

87

'ECONNRESET', // Remote socket closed

88

'ECONNREFUSED', // Connection refused

89

'EADDRINUSE', // Address in use

90

'ETIMEDOUT', // Operation timed out

91

'ECONNECTIONTIMEOUT', // Connection timeout (from @npmcli/agent)

92

'EIDLETIMEOUT', // Idle timeout (from @npmcli/agent)

93

'ERESPONSETIMEOUT', // Response timeout (from @npmcli/agent)

94

'ETRANSFERTIMEOUT' // Transfer timeout (from @npmcli/agent)

95

];

96

97

/**

98

* Retriable error types:

99

*/

100

const RETRY_TYPES = [

101

'request-timeout' // Request timeout from fetch

102

];

103

104

/**

105

* Non-retriable conditions:

106

* - ENOTFOUND (DNS resolution failure - likely offline or bad hostname)

107

* - EINVALIDPROXY (Invalid proxy configuration)

108

* - EINVALIDRESPONSE (Invalid response from server)

109

* - All 2xx and 3xx response codes

110

* - 4xx response codes except 408, 420, 429

111

*/

112

```

113

114

### Exponential Backoff

115

116

Retry timing follows exponential backoff with optional randomization:

117

118

```javascript { .api }

119

/**

120

* Backoff calculation:

121

* timeout = min(minTimeout * (factor ^ attempt), maxTimeout)

122

*

123

* If randomize is true:

124

* timeout = timeout * (1 + Math.random())

125

*

126

* Default values:

127

* - minTimeout: 1000ms

128

* - factor: 2

129

* - maxTimeout: Infinity

130

* - randomize: false

131

*/

132

```

133

134

**Usage Examples:**

135

136

```javascript

137

// Custom backoff strategy

138

const response = await fetch('https://api.example.com/data', {

139

retry: {

140

retries: 5,

141

factor: 1.5, // Slower exponential growth

142

minTimeout: 2000, // Start with 2 second delay

143

maxTimeout: 30000, // Cap at 30 seconds

144

randomize: true // Add jitter to prevent thundering herd

145

}

146

});

147

148

// Backoff progression with above config:

149

// Attempt 1: 2000-4000ms (randomized)

150

// Attempt 2: 3000-6000ms (randomized)

151

// Attempt 3: 4500-9000ms (randomized)

152

// Attempt 4: 6750-13500ms (randomized)

153

// Attempt 5: 10125-20250ms (randomized, but capped at 30000ms)

154

```

155

156

### Retry Callbacks

157

158

Monitor retry attempts and implement custom logic:

159

160

```javascript { .api }

161

/**

162

* Retry callback receives the cause of the retry

163

* @param {Error|Response} cause - What triggered the retry

164

*/

165

type OnRetryCallback = (cause: Error | Response) => void;

166

```

167

168

**Usage Examples:**

169

170

```javascript

171

let retryCount = 0;

172

173

const response = await fetch('https://api.example.com/data', {

174

retry: { retries: 3 },

175

onRetry: (cause) => {

176

retryCount++;

177

178

if (cause instanceof Response) {

179

console.log(`Retry ${retryCount}: Server returned ${cause.status}`);

180

181

// Log response headers for debugging

182

const rateLimitReset = cause.headers.get('x-rate-limit-reset');

183

if (rateLimitReset) {

184

console.log(`Rate limit resets at: ${new Date(rateLimitReset * 1000)}`);

185

}

186

} else {

187

console.log(`Retry ${retryCount}: Network error - ${cause.message}`);

188

console.log(`Error code: ${cause.code}`);

189

}

190

}

191

});

192

```

193

194

### POST Request Handling

195

196

POST requests are handled specially to prevent accidental duplicate operations:

197

198

```javascript { .api }

199

/**

200

* POST request retry behavior:

201

* - POST requests are never retried for response status codes

202

* - POST requests are only retried for network-level errors

203

* - This prevents accidental duplicate mutations on the server

204

*/

205

```

206

207

**Usage Examples:**

208

209

```javascript

210

// GET request - will retry on 500 errors

211

const getData = await fetch('https://api.example.com/data', {

212

retry: { retries: 3 }

213

});

214

215

// POST request - will only retry on network errors, not 500 responses

216

const postData = await fetch('https://api.example.com/data', {

217

method: 'POST',

218

body: JSON.stringify({ action: 'create' }),

219

headers: { 'Content-Type': 'application/json' },

220

retry: { retries: 3 } // Only retries network errors for POST

221

});

222

223

// Use idempotent POST patterns for retries

224

const idempotentPost = await fetch('https://api.example.com/data', {

225

method: 'POST',

226

body: JSON.stringify({

227

idempotencyKey: 'unique-key-123',

228

action: 'create'

229

}),

230

headers: {

231

'Content-Type': 'application/json',

232

'Idempotency-Key': 'unique-key-123'

233

},

234

retry: { retries: 3 }

235

});

236

```

237

238

### Advanced Retry Patterns

239

240

Common patterns for robust retry handling:

241

242

```javascript

243

// Circuit breaker pattern

244

class CircuitBreaker {

245

constructor(threshold = 5, timeout = 60000) {

246

this.failureCount = 0;

247

this.lastFailTime = 0;

248

this.threshold = threshold;

249

this.timeout = timeout;

250

}

251

252

async call(fetchFn) {

253

if (this.isOpen()) {

254

throw new Error('Circuit breaker is open');

255

}

256

257

try {

258

const result = await fetchFn();

259

this.onSuccess();

260

return result;

261

} catch (error) {

262

this.onFailure();

263

throw error;

264

}

265

}

266

267

isOpen() {

268

return this.failureCount >= this.threshold &&

269

(Date.now() - this.lastFailTime) < this.timeout;

270

}

271

272

onSuccess() {

273

this.failureCount = 0;

274

}

275

276

onFailure() {

277

this.failureCount++;

278

this.lastFailTime = Date.now();

279

}

280

}

281

282

// Usage with circuit breaker

283

const breaker = new CircuitBreaker();

284

const response = await breaker.call(() =>

285

fetch('https://api.example.com/data', {

286

retry: { retries: 2 }

287

})

288

);

289

290

// Conditional retry based on error type

291

const smartRetry = {

292

retries: 3,

293

onRetry: (cause) => {

294

if (cause instanceof Response && cause.status === 429) {

295

// For rate limiting, wait longer

296

const retryAfter = cause.headers.get('retry-after');

297

if (retryAfter) {

298

console.log(`Rate limited, retry after ${retryAfter} seconds`);

299

}

300

}

301

}

302

};

303

304

// Metrics collection

305

const metrics = { attempts: 0, retries: 0, failures: 0 };

306

307

const response = await fetch('https://api.example.com/data', {

308

retry: { retries: 3 },

309

onRetry: () => {

310

metrics.retries++;

311

}

312

});

313

314

metrics.attempts = 1 + metrics.retries;

315

if (!response.ok) {

316

metrics.failures++;

317

}

318

```