The default retry strategy is retryDelay × 2attempt. When you need smarter behavior — honoring Retry-After on 429/503, classifying permanent vs transient errors, jitter to avoid thundering herd, or capping at a wall-clock budget — pass a retryPolicy function that returns the desired delay (or false to abort).
Try it: point the uploader at a URL that returns a 503 with Retry-After: 5 and the policy waits 5 seconds (instead of jumping to 8 immediately). Point it at one returning a 404 and the upload aborts on the first failure with no retries.
Function signature
retryPolicy: function(attempt, error, ctx) {
// attempt = 1-based count of the NEXT try (so 1 on first failure)
// error = the Error that triggered the retry consideration
// ctx = { task, kind: 'task'|'chunk'|'control', index? }
// Return value:
// number = milliseconds to wait before the next attempt
// false = abort immediately (no retry)
// true = use the default exponential backoff
}
Recipe: honor Retry-After on 429/503
retryPolicy: function(attempt, err) {
// Server hint takes precedence over our backoff curve.
if (err.retryAfter) return Math.min(err.retryAfter * 1000, 60000);
return true; // fall back to default
}
Recipe: never retry a permanent error
retryPolicy: function(attempt, err) {
// 401, 403, 404, 410, 422 are unrecoverable; don't waste time retrying.
if (/HTTP (401|403|404|410|422)/.test(err.message)) return false;
return true;
}
Recipe: cap by wall-clock budget
var uploadDeadline = Date.now() + 5 * 60 * 1000; // 5-minute hard limit
retryPolicy: function(attempt, err) {
if (Date.now() > uploadDeadline) return false;
return true;
}
Recipe: jittered exponential
retryPolicy: function(attempt) {
var base = 1000 * Math.pow(2, attempt - 1);
return base + Math.random() * base * 0.5; // ±25% jitter
}
Safety
- Returned delays are capped at 10 minutes internally to prevent runaway sleeps.
- The total number of attempts still respects
options.retries.
- If the policy throws, the default exponential backoff is used (your retries don't break upload).