LoginSignup
10
10

More than 5 years have passed since last update.

ES6でPromiseのレートリミット実装

Last updated at Posted at 2016-07-29

DynamoDBのようなスループットを気にする必要があるAPIにアクセスする場合、API実行に実行回数制限をかけたくなる場合があります。npmに沢山ライブラリが公開されていますが、Promise関数にレートリミットを付与したクロージャーを返す実装を書いて使うことにしました。

例えばあるPromiseを返す関数の実行を秒間20回に制限したい場合、次のように書くと設定値を超える回数の実行をsleepで調整します。
rateLimit関数では結果的に引数の関数を実行するクロージャーを返しているので、関数の使い方は変わりません。

const testFunctionRateLimit = rateLimit(testFunction, 20, 1000);

実装

ソースコード:

// ES2015以外のPromiseライブラリでも動作可
// import Promise from 'bluebird';

const delay = (ms) => {
  return new Promise((resolve) => {
      setTimeout(resolve, ms);
  });
};

const rateLimit = (func, rate, inMs) => {
  let executedHistories = [];

  const wrapped = (...args) => {
    const now = Date.now();
    executedHistories.push(now);
    executedHistories = executedHistories.filter((executedAt) => {
      return (now - executedAt) < inMs;
    });

    if (rate < executedHistories.length) {
      const sleepTime = inMs - (now - Math.min(...executedHistories));
      return delay(sleepTime).then(() => {
          executedHistories.pop();
          return wrapped(...args);
      });
    } else {
      return func(...args);
    }
  }
  return wrapped;
};

動作確認

ソースコード:

// 動作確認用の関数を定義
const startTime = Date.now();
const testFunction = (arg1, arg2, arg3) => {
  return Promise.resolve().then(() => {
      const elapsed = Date.now() - startTime;
      console.log('arg1:', arg1, ', arg2:', arg2, ', arg3:', arg3, 'elapsed:', elapsed);
  });
};

// testFunctionの実行を1000msに20回に制限
const testFunctionRateLimit = rateLimit(testFunction, 20, 1000);

// 直列に100回実行
let promise = Promise.resolve(null);
for (let i = 0; i < 100; i++) {
  promise = promise.then(() => {
    return testFunctionRateLimit(i, i + 1, i + 2);
  });
}

promise.then(() => {
  console.log('done');
});

出力:

arg1: 0 , arg2: 1 , arg3: 2 elapsed: 2
arg1: 1 , arg2: 2 , arg3: 3 elapsed: 3
...
arg1: 18 , arg2: 19 , arg3: 20 elapsed: 5
arg1: 19 , arg2: 20 , arg3: 21 elapsed: 5
arg1: 20 , arg2: 21 , arg3: 22 elapsed: 1004
arg1: 21 , arg2: 22 , arg3: 23 elapsed: 1005
...
arg1: 38 , arg2: 39 , arg3: 40 elapsed: 1006
arg1: 39 , arg2: 40 , arg3: 41 elapsed: 1006
arg1: 40 , arg2: 41 , arg3: 42 elapsed: 2006
arg1: 41 , arg2: 42 , arg3: 43 elapsed: 2006
...
arg1: 58 , arg2: 59 , arg3: 60 elapsed: 2007
arg1: 59 , arg2: 60 , arg3: 61 elapsed: 2007
arg1: 60 , arg2: 61 , arg3: 62 elapsed: 3003
arg1: 61 , arg2: 62 , arg3: 63 elapsed: 3003
...
arg1: 78 , arg2: 79 , arg3: 80 elapsed: 3006
arg1: 79 , arg2: 80 , arg3: 81 elapsed: 3006
arg1: 80 , arg2: 81 , arg3: 82 elapsed: 4002
arg1: 81 , arg2: 82 , arg3: 83 elapsed: 4002
...
done
10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10