Do while loops

Run the body once before testing the condition, with #do {# and #} while(…)#.

A retry schedule with exponential backoff: the first attempt always happens, and only the repeats depend on the condition. That asymmetry is the whole reason do…while exists.

<style>
    body { font: 14px/1.5 system-ui, sans-serif; margin: 0; color: #24292f; }
    ol { margin: 0; padding-left: 1.4em; }
    li { padding: 3px 0; }
    code { background: #f6f8fa; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
    .wait { color: #57606a; }
</style>

<script type="text/template" id="backoff">
    #{ let attempt = 0; let wait = firstDelay; let total = 0; }#
    <ol>
    #do {#
        #{ attempt++; }#
        <li>
            Attempt #attempt# to <code>#endpoint#</code>
            #{ total += wait; }#
            <span class="wait">&mdash; retry in #wait# ms</span>
            #{ wait = wait * factor; }#
        </li>
    #} while(attempt < maxAttempts)#
    </ol>
    <p>Gives up after #attempt# attempts and #total# ms.</p>
</script>

<div id="output"></div>

<script src="https://cdn.jsdelivr.net/gh/richdafunk/hashJS@v1.3.6/hashJS.js"></script>
<script>
    // A do-while runs its body before it tests the condition, which is exactly
    // the shape of a retry: the first attempt is not a retry at all.
    const data = {
        endpoint: 'POST /v1/payments',
        firstDelay: 250,
        factor: 2,
        maxAttempts: 5
    };

    new hashJS('backoff', data, 'output');
</script>
Output result:

The closing tag carries the condition: #} while(attempt < maxAttempts)#. It is recognised as a block continuation for the same reason #} else {# is — it begins with }.

Every number on screen is derived rather than supplied. The delay doubles through wait = wait * factor and the running total accumulates alongside it, so changing factor in the data changes the whole schedule without touching the template.