fetchにタイムアウトとリトライの処理を付ける

axios(+ axios-retry)だとタイムアウトとリトライの処理があるけど、fetchには何もないらしい。まじか。

手続き型言語なら普通にタイマーでキャンセル処理を登録してリトライはforループとかで、、

for(1 .. retry_num){
  if (timer(fetch, timeout) == 0){
    break
  }
}

とこんな感じのコードで済むんだけど、javascriptは何かと面倒だな。
色々調べてこんな感じになった。

function fetch_retry(url, params={}) {
    const {retry=5, timeout_ms=1000, options={}} = params;

    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeout_ms);
    options["signal"] = controller.signal;

    return fetch(url, options)
    .catch((error) => {
        if (retry === 1) throw error;
        return fetch_retry(url, {retry : retry-1, timeout_ms : timeout_ms, options : options});
    })
    .finally(() => clearTimeout(timer));
}

こんな感じで使いたい。

fetch_retry(url)
fetch_retry(url, {timeout_ms:10000})

fetchを中断させるには AbortController().abort() を使うらしい。
おそらくこれを実行するとfetchのoptionに指定したsignalが送られてコネクションを閉じるんだと思う。

Promise.race()を使って、指定した時間でrejectを実行するPromiseと同時に動かすという方法もあるようだけど、これだと処理的にはタイムアウトするけどコネクションはそのままになってしまう。
まあ放っておけばkeep-aliveでcloseするだろうから(keepalive有効になってないことなんてないよね?)大きな問題ではなさそうだけど
fetch自体に備わってるabort処理を使う方が素直な実装でしょう。Promise.race()も使わずにすむし。

で、前のエントリーに合わせてStreams対応させれば。。もうちょっとだ。

参考: