Subversion Repositories munaweb

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
32 - 1
/* https://github.com/lpinca/stopcock/blob/master/index.js */
2
'use strict';
3
 
4
/**
5
 * Class representing a token bucket.
6
 */
7
class TokenBucket {
8
  /**
9
   * Create a new `TokenBucket`.
10
   *
11
   * @param {Object} options Options object
12
   * @param {Number} options.limit The tokens to add per `interval`
13
   * @param {Number} options.interval The interval at which tokens are added
14
   * @param {Number} options.bucketSize The capacity of the bucket
15
   */
16
  constructor(options) {
17
    this.tokens = this.capacity = options.bucketSize;
18
    this.interval = options.interval;
19
    this.limit = options.limit;
20
    this.last = Date.now();
21
  }
22
 
23
  /**
24
   * Refill the bucket with the proper amount of tokens.
25
   */
26
  refill() {
27
    const now = Date.now();
28
    const tokens = Math.floor((now - this.last) * this.limit / this.interval);
29
 
30
    this.tokens += tokens;
31
 
32
    //
33
    // `tokens` is rounded downward, so we only add the actual time required by
34
    // those tokens.
35
    //
36
    this.last += Math.ceil(tokens * this.interval / this.limit);
37
 
38
    if (this.tokens > this.capacity) {
39
      this.tokens = this.capacity;
40
      this.last = now;
41
    }
42
  }
43
 
44
  /**
45
   * Remove a token from the bucket.
46
   *
47
   * @return {Number} The amount of time to wait for a token to be available
48
   */
49
  consume() {
50
    this.refill();
51
 
52
    if (this.tokens) {
53
      this.tokens--;
54
      return 0;
55
    }
56
 
57
    return Math.ceil(this.interval / this.limit - (Date.now() - this.last));
58
  }
59
}
60
 
61
/**
62
 * Limit the execution rate of a function using a leaky bucket algorithm.
63
 *
64
 * @param {Function} fn The function to rate limit calls to
65
 * @param {Object} options Options object
66
 * @param {Number} options.limit The number of allowed calls per `interval`
67
 * @param {Number} options.interval The timespan where `limit` is calculated
68
 * @param {Number} options.queueSize The maximum size of the queue
69
 * @param {Number} options.bucketSize The capacity of the bucket
70
 * @return {Function}
71
 * @public
72
 */
73
function stopcock(fn, options) {
74
  options = Object.assign({
75
    queueSize: Math.pow(2, 32) - 1,
76
    bucketSize: 40,
77
    interval: 1000,
78
    limit: 2
79
  }, options);
80
 
81
  const bucket = new TokenBucket(options);
82
  const queue = [];
83
  let timer = null;
84
 
85
  function shift() {
86
    clearTimeout(timer);
87
    while (queue.length) {
88
      const delay = bucket.consume();
89
 
90
      if (delay > 0) {
91
        timer = setTimeout(shift, delay);
92
        break;
93
      }
94
 
95
      const data = queue.shift();
96
      data[2](fn.apply(data[0], data[1]));
97
    }
98
  }
99
 
100
  return function limiter() {
101
    const args = arguments;
102
 
103
    return new Promise((resolve, reject) => {
104
      if (queue.length === options.queueSize) {
105
        return reject(new Error('Queue is full'));
106
      }
107
 
108
      queue.push([this, args, resolve]);
109
      shift();
110
    });
111
  };
112
}