Promiseとリトライ

HTML

<div>
  タスク成功率: <input type="number" id="successRate" max="100" min="0" value="5">%
</div>
<div>
  リトライ回数: <input type="number" id="retryCount" max="100000" min="0" value="10">回
</div>
<div>
  リトライ実装: <select id="retryImpl">
    <option value="none">なし</option>
    <option value="forloop1">for文1(ディレイなし)</option>
    <option value="forloop2">for文2(ディレイ・キャンセル実装、不具合あり)</option>
    <option value="forloop3">for文3(ディレイ・キャンセル実装)</option>
    <option value="recursive1">再帰1(無限リトライ、ディレイ・キャンセル実装)</option>
    <option value="recursive2">再帰2(有限リトライ、ディレイ・キャンセル実装)</option>
  </select>
</div>
<div>
  ディレイ: <input type="number" id="baseSleepMillis" max="1000" min="0" value="100">ms * リトライ回数
</div>
<div>
  最大ディレイ: <input type="number" id="maxSleepMillis" max="60000" min="0" value="1000">ms
</div>
<div>
 <button id="start">実行</button>
 <button id="cancel" disabled="disabled">リトライキャンセル</button>
</div>
<div>
  <textarea id="console" rows="20" cols="80" placeholder="ここに処理結果が出力されます" readonly></textarea>
</div>

JavaScript

const stopWatch = (() => {
  let startTime = new Date().getTime();
  return {
    getTimeStamp() {
      const current = new Date().getTime();
      const time = current - startTime;
      startTime = current;
      return "+" + Math.floor(time / 1000) + "." + (("" + (time % 1000)).padStart(3, '0'));
    },
    clear() {
      startTime = new Date().getTime()
    }
  }
})();

const clearLog = function() {
  document.getElementById('console').innerHTML = "";
  stopWatch.clear();
};

const printLog = function(...args) {
  document.getElementById('console').innerHTML += `${stopWatch.getTimeStamp()}: ${args.join(' ')}\n`;
};


function sleepAsync(milliseconds) {
  return new Promise((r) => setTimeout(r, milliseconds))
}

function doTaskAsync(successRate) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      printLog("[Task]", "processing...")
      if (Math.random() < successRate / 100.0) {
        resolve("SUCCESS");
      } else {
        reject("FAILED");
      }
    }, 0);
  })
}

const retryImplements = {
  "none": function(func) {
    return func();
  },
  "forloop1": function(func, retryCount = 5) {
    let promise = func();
    for (let i = 1; i <= retryCount; ++i) {
      promise = promise.catch(func)
    }
    return promise;
  },
  "forloop2": function(func, retryCount = 5, retryActionFunction = () => Promise.resolve()) {
    let promise = func();
    for (let i = 1; i <= retryCount; ++i) {
      promise = promise.catch((...rejectArgs) => retryActionFunction(i, rejectArgs).then(func)) // NG!!
    }
    return promise;
  },
  "forloop3": function(func, retryCount = 5, retryActionFunction = () => Promise.resolve()) {
    return new Promise((resolve, reject) => {
      let promise = func();
      for (let i = 1; i <= retryCount; ++i) {
        promise = promise.catch((...rejectArgs) => retryActionFunction(i, rejectArgs).then(func, reject))
      }
      promise.then(resolve, reject);
    })
  },
  "recursive1": function(func, dummy =...