うまとま君の技術めも

2015年新卒入社した社畜の勉強内容などなど

Node.jsでリトライ処理を実装してみる

概要

HTTPリクエストが失敗した場合や、DBへの接続に失敗した場合など
何回かリトライ処理を含めたい場合ってありますよね?
そこで、Node.jsを使ったリトライ処理用モジュールを試しに作ってみようと思います。

環境

Node.js v4.4.7 OS Mac OS X 10.11.5

リトライ処理用モジュール

特に深い理由は無いですが、
Promiseベースのリトライ処理が出来るようなものを作ってみたいと思います。

作るもの

  • Promiseで結果が受け取れる
  • リトライ数が設定できる
  • リトライ間隔が設定できる
  • デバッグ用のログが出せる

promise-retry.js

'use strict';

class Retry {
  constructor() {
    this._maxTimes = 3;
    this._interval = 100; // ms
    this._debug = false;
  }

  /**
   * @public
   */
  maxTimes(count) {
    this._maxTimes = count;
    return this;
  }

  /**
   * @public
   */
  interval(ms) {
    this._interval = ms;
    return this;
  }

  /**
   * @public
   */
  debug(flag) {
    this._debug = flag;
    return this;
  }

  /**
   * @public
   */
  execute(fn) {
    let retryCount = 0;
    let maxTimes = this._maxTimes;
    return new Promise((resolve, reject) => {
      this.doRetry(fn, retryCount, resolve, reject);
    });
  }

  /**
   * @private
   */
  doRetry(fn, retryCount, resolve, reject) {
    let maxTimes = this._maxTimes;
    fn(retryCount)
      .then(function() {
        // Don't use arrow function.
        // In that case, `arguments` will be global scope variable.
        resolve.apply(Promise, arguments);
      })
      .catch((err) => {
        if (retryCount >= maxTimes) {
          reject(err);
        } else {
          retryCount++;
          setTimeout(() => {
            this.log(retryCount, err);
            this.doRetry(fn, retryCount, resolve, reject);
          }, this._interval);
        }
      });
  }

  /**
   * @private
   */
  log(retryCount, err) {
    if (this._debug) {
      let hrtime = process.hrtime();
      console.log(
        'retry cnt: %d, at: %d.%d, prev_err: %s',
        retryCount,
        hrtime[0],
        hrtime[1],
        err.message
      );
    }
  }
}

module.exports = () => new Retry();

リトライ処理をやってみる

では、作ったモジュールを使ってリトライ処理を実装してみたいと思います。

  • 最大リトライ数:5
  • リトライ間隔:20ms
  • デバッグモード:ON

index.js

'use strict';

const knex = require('knex')({ ... });
const retry = require('./promise-retry');

retry.debug(true)
  .maxTimes(5)
  .interval(20)
  .execute(retryCount => {
    return knex.first('*')
      .from('users')
      .where('id', 'umatoma');
  })
  .then(user => console.log(user))
  .catch(err => console.log(err));

地味にハマったポイント

ES6から導入された アロー関数 を使うと this と同様に arguments も束縛されてしまうんですね。
知りませんでした...

developer.mozilla.org

まとめ

単純な処理ではありますが、複数箇所で同じようなことをやる可能性も高いと思うので、
モジュール化して、使いまわすといいかもしれないですね。