Custom retry policy

Pluggable retry decisions via retryPolicy

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).

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).