<%@ Page Title="Custom retry policy" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" %>
<asp:Content ID="TitleContent" ContentPlaceHolderID="TitleContent" runat="server">Custom retry policy - AjaxUploader v5.2 Demos</asp:Content>
<asp:Content ID="HeaderContent" ContentPlaceHolderID="HeaderTitle" runat="server">Custom retry policy</asp:Content>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server"> 
<div class="demo-card"> <div class="demo-card-header">Pluggable retry decisions via <code>retryPolicy</code></div> <div class="demo-card-body"> <p class="demo-description"> The default retry strategy is <code>retryDelay x 2<sup>attempt</sup></code>. When you need smarter behavior - honoring <code>Retry-After</code> on 429/503, classifying permanent vs transient errors, jitter to avoid thundering herd, or capping at a wall-clock budget - pass a <code>retryPolicy</code> function that returns the desired delay (or <code>false</code> to abort).
 </p> <div class="d-flex gap-2 mb-2 mt-4">
 <button type="button" class="btn btn-primary" id="startBtn_retryUploader">Start upload</button>
 <button type="button" class="btn btn-outline-secondary" id="clearBtn_retryUploader">Clear queue</button>
 </div>
 <div id="retryUploader"></div> <script> (function () {
 function init() {
 if (!window.AjaxUploader || typeof AjaxUploader.create !== 'function') return;
 var uploader = AjaxUploader.create('#retryUploader', {
 uploadUrl: '<%= ResolveUrl("~/ajaxupload.axd/upload") %>',
 multiple: true,
 autoUpload: false,
 retries: 5,
 retryPolicy: function (attempt, error, ctx) {
 // Honor server's Retry-After hint on rate-limit responses.
 if (error && /\b(429|503)\b/.test(String(error.message || ''))) {
 var retryAfter = error.retryAfter || (1000 * attempt * 5);
 return Math.min(retryAfter, 30000);
 }
 // Treat 4xx other than 408/429 as permanent - abort immediately.
 if (error && /\bHTTP 4(?!08|29)\d{2}\b/.test(String(error.message || ''))) {
 return false;
 }
 // Add +/-25% jitter to default exponential backoff.
 var defaultMs = 1000 * Math.pow(2, attempt - 1);
 var jitter = (Math.random() - 0.5) * 0.5 * defaultMs;
 return defaultMs + jitter;
 }
 });
 var startButton = document.getElementById('startBtn_retryUploader');
 var clearButton = document.getElementById('clearBtn_retryUploader');
 if (startButton) {
 startButton.addEventListener('click', function () {
 if (uploader && typeof uploader.upload === 'function') uploader.upload();
 });
 }
 if (clearButton) {
 clearButton.addEventListener('click', function () {
 if (uploader && typeof uploader.clear === 'function') uploader.clear();
 });
 }
 }
 if (document.readyState !== 'loading') init();
 else document.addEventListener('DOMContentLoaded', init);
 })();
 </script> <div class="alert alert-info mt-3" role="alert"> <strong>Try it:</strong> point the uploader at a URL that returns a 503 with <code>Retry-After: 5</code> 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.
 </div> <h5 class="mt-4">Function signature</h5> <div class="code-panel">
<pre><code>retryPolicy: <span style="color:#94a3b8">function</span>(attempt, error, ctx) {
 <span style="color:#94a3b8">// attempt = 1-based count of the NEXT try (so 1 on first failure)</span> <span style="color:#94a3b8">// error = the Error that triggered the retry consideration</span> <span style="color:#94a3b8">// ctx = { task, kind: 'task'|'chunk'|'control', index? }</span> <span style="color:#94a3b8">// Return value:</span> <span style="color:#94a3b8">// number = milliseconds to wait before the next attempt</span> <span style="color:#94a3b8">// false = abort immediately (no retry)</span> <span style="color:#94a3b8">// true = use the default exponential backoff</span>
}</code></pre> </div> <h5 class="mt-4">Recipe: honor <code>Retry-After</code> on 429/503</h5> <div class="code-panel">
<pre><code>retryPolicy: <span style="color:#94a3b8">function</span>(attempt, err) {
 <span style="color:#94a3b8">// Server hint takes precedence over our backoff curve.</span> <span style="color:#94a3b8">if</span> (err.retryAfter) <span style="color:#94a3b8">return</span> Math.min(err.retryAfter * 1000, 60000);
 <span style="color:#94a3b8">return</span> <span style="color:#94a3b8">true</span>; <span style="color:#94a3b8">// fall back to default</span>
}</code></pre> </div> <h5 class="mt-4">Recipe: never retry a permanent error</h5> <div class="code-panel">
<pre><code>retryPolicy: <span style="color:#94a3b8">function</span>(attempt, err) {
 <span style="color:#94a3b8">// 401, 403, 404, 410, 422 are unrecoverable; don't waste time retrying.</span> <span style="color:#94a3b8">if</span> (/HTTP (401|403|404|410|422)/.test(err.message)) <span style="color:#94a3b8">return</span> <span style="color:#94a3b8">false</span>;
 <span style="color:#94a3b8">return</span> <span style="color:#94a3b8">true</span>;
}</code></pre> </div> <h5 class="mt-4">Recipe: cap by wall-clock budget</h5> <div class="code-panel">
<pre><code><span style="color:#94a3b8">var</span> uploadDeadline = Date.now() + 5 * 60 * 1000; <span style="color:#94a3b8">// 5-minute hard limit</span> 
retryPolicy: <span style="color:#94a3b8">function</span>(attempt, err) {
 <span style="color:#94a3b8">if</span> (Date.now() &gt; uploadDeadline) <span style="color:#94a3b8">return</span> <span style="color:#94a3b8">false</span>;
 <span style="color:#94a3b8">return</span> <span style="color:#94a3b8">true</span>;
}</code></pre> </div> <h5 class="mt-4">Recipe: jittered exponential</h5> <div class="code-panel">
<pre><code>retryPolicy: <span style="color:#94a3b8">function</span>(attempt) {
 <span style="color:#94a3b8">var</span> base = 1000 * Math.pow(2, attempt - 1);
 <span style="color:#94a3b8">return</span> base + Math.random() * base * 0.5; <span style="color:#94a3b8">// +/-25% jitter</span>
}</code></pre> </div> <h5 class="mt-4">Safety</h5> <ul class="mb-3"> <li>Returned delays are capped at <strong>10 minutes</strong> internally to prevent runaway sleeps.</li> <li>The total number of attempts still respects <code>options.retries</code>.</li> <li>If the policy throws, the default exponential backoff is used (your retries don't break upload).</li> </ul> </div>
</div> 
</asp:Content>
