Skip to content

Commit

Permalink
Reject duplicate IDs
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Grondin committed Jun 9, 2018
1 parent 9fd798d commit b9d4a9b
Show file tree
Hide file tree
Showing 18 changed files with 72 additions and 49 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,37 @@ npm install --save bottleneck

### Quick Start

Most APIs have a rate limit. For example, the reddit.com API limits scripts to 1 request every 2 seconds.
Most APIs have a rate limit. For example, to execute 3 requests per second:

```js
import Bottleneck from "bottleneck"

// Never more than 1 request running at a time.
// Wait at least 2000ms between each request.
const limiter = new Bottleneck({
minTime: 333
});
```

If there's a chance some requests might take longer than 333ms and you want to prevent that, add `maxConcurrent: 1`.

```js
const limiter = new Bottleneck({
maxConcurrent: 1,
minTime: 2000
minTime: 333
});
```

Instead of this:

```js
someAsyncCall(arg1, arg2, callback);
```

Do this:

```js
limiter.submit(someAsyncCall, arg1, arg2, callback);
```

And now you can be assured that someAsyncCall will abide by your rate guidelines!

[More information about using Bottleneck with callbacks](#submit)
Expand Down Expand Up @@ -281,7 +291,7 @@ console.log(limiter.jobStatus("some-job-id"));
// Example: QUEUED
```

Returns the status of the job with the provided job id. See [Job Options](#job-options).
Returns the status of the job with the provided job id. See [Job Options](#job-options). Returns `null` if no job with that id exist.

**Note:** By default, Bottleneck does not keep track of DONE jobs, to save memory. You can enable that feature by passing `trackDoneStatus: true` as an option when creating a limiter.

Expand Down
33 changes: 17 additions & 16 deletions bottleneck.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}return e})()({1:[function(require,module,exports){
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
Expand All @@ -7,7 +7,7 @@ function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var Bottleneck,
DEFAULT_PRIORITY,
Expand Down Expand Up @@ -333,6 +333,10 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
if (options.id === this.jobDefaults.id) {
options.id = `${options.id}-${this._randomIndex()}`;
}
if (this.jobStatus(options.id) != null) {
job.cb(new Bottleneck.prototype.BottleneckError(`A job with the same id already exists (id=${options.id})`));
return false;
}
this._states.start(options.id); // RECEIVED
this.Events.trigger("debug", [`Queueing ${options.id}`, { args, options }]);
return this._submitLock.schedule(_asyncToGenerator(function* () {
Expand Down Expand Up @@ -517,7 +521,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
},{"../package.json":13,"./BottleneckError":2,"./DLList":3,"./Events":4,"./Group":5,"./Local":6,"./RedisStorage":7,"./States":8,"./Sync":9,"./parser":12}],2:[function(require,module,exports){
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError;

Expand All @@ -528,7 +532,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
},{}],3:[function(require,module,exports){
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var DLList;

Expand Down Expand Up @@ -590,7 +594,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
},{}],4:[function(require,module,exports){
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var Events;

Expand Down Expand Up @@ -665,7 +669,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var Events, Group, parser;

Expand Down Expand Up @@ -779,7 +783,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError, DLList, Local, parser;

Expand Down Expand Up @@ -975,7 +979,7 @@ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = [

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError, DLList, RedisStorage, libraries, lua, parser, scriptTemplates;

Expand Down Expand Up @@ -1350,7 +1354,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
},{"./BottleneckError":2,"./DLList":3,"./lua.json":11,"./parser":12}],8:[function(require,module,exports){
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError, States;

Expand Down Expand Up @@ -1380,9 +1384,6 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
}

start(id, initial = 0) {
if (this.jobs[id] != null) {
throw new BottleneckError(`A job with the same id already exists (id=${id})`);
}
this.jobs[id] = initial;
return this.counts[initial]++;
}
Expand Down Expand Up @@ -1419,7 +1420,7 @@ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = [

function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var DLList,
Sync,
Expand Down Expand Up @@ -1490,7 +1491,7 @@ function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }
},{"./DLList":3}],10:[function(require,module,exports){
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
module.exports = require("./Bottleneck");
}).call(undefined);
Expand All @@ -1516,7 +1517,7 @@ module.exports={
},{}],12:[function(require,module,exports){
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
exports.load = function (received, defaults, onto = {}) {
var k, ref, v;
Expand All @@ -1541,7 +1542,7 @@ module.exports={
},{}],13:[function(require,module,exports){
module.exports={
"name": "bottleneck",
"version": "2.3.1",
"version": "2.4.0",
"description": "Distributed task scheduler and rate limiter",
"main": "lib/index.js",
"typings": "bottleneck.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion bottleneck.min.js

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion lib/Bottleneck.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var Bottleneck,
DEFAULT_PRIORITY,
Expand Down Expand Up @@ -332,6 +332,10 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
if (options.id === this.jobDefaults.id) {
options.id = `${options.id}-${this._randomIndex()}`;
}
if (this.jobStatus(options.id) != null) {
job.cb(new Bottleneck.prototype.BottleneckError(`A job with the same id already exists (id=${options.id})`));
return false;
}
this._states.start(options.id); // RECEIVED
this.Events.trigger("debug", [`Queueing ${options.id}`, { args, options }]);
return this._submitLock.schedule(_asyncToGenerator(function* () {
Expand Down
2 changes: 1 addition & 1 deletion lib/BottleneckError.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError;

Expand Down
2 changes: 1 addition & 1 deletion lib/DLList.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var DLList;

Expand Down
2 changes: 1 addition & 1 deletion lib/Events.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var Events;

Expand Down
2 changes: 1 addition & 1 deletion lib/Group.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var Events, Group, parser;

Expand Down
2 changes: 1 addition & 1 deletion lib/Local.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError, DLList, Local, parser;

Expand Down
2 changes: 1 addition & 1 deletion lib/RedisStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = [

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError, DLList, RedisStorage, libraries, lua, parser, scriptTemplates;

Expand Down
5 changes: 1 addition & 4 deletions lib/States.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var BottleneckError, States;

Expand Down Expand Up @@ -30,9 +30,6 @@
}

start(id, initial = 0) {
if (this.jobs[id] != null) {
throw new BottleneckError(`A job with the same id already exists (id=${id})`);
}
this.jobs[id] = initial;
return this.counts[initial]++;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = [

function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
var DLList,
Sync,
Expand Down
2 changes: 1 addition & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
module.exports = require("./Bottleneck");
}).call(undefined);
2 changes: 1 addition & 1 deletion lib/parser.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

// Generated by CoffeeScript 2.2.2
// Generated by CoffeeScript 2.2.4
(function () {
exports.load = function (received, defaults, onto = {}) {
var k, ref, v;
Expand Down
5 changes: 5 additions & 0 deletions src/Bottleneck.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ class Bottleneck
job = { options, task, args, cb }
options.priority = @_sanitizePriority options.priority
if options.id == @jobDefaults.id then options.id = "#{options.id}-#{@_randomIndex()}"

if @jobStatus(options.id)?
job.cb new Bottleneck::BottleneckError "A job with the same id already exists (id=#{options.id})"
return false

@_states.start options.id # RECEIVED

@Events.trigger "debug", ["Queueing #{options.id}", { args, options }]
Expand Down
1 change: 0 additions & 1 deletion src/States.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class States
delete @jobs[id]

start: (id, initial=0) ->
if @jobs[id]? then throw new BottleneckError "A job with the same id already exists (id=#{id})"
@jobs[id] = initial
@counts[initial]++

Expand Down
19 changes: 19 additions & 0 deletions test/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,25 @@ describe('General', function () {
})
})

it('Should reject duplicate Job IDs', function (done) {
c = makeTest({maxConcurrent: 2, minTime: 100, trackDoneStatus: true})

c.limiter.ready()
.then(function () {
return c.limiter.schedule({ id: 'a' }, c.promise, null, 1)
})
.then(function () {
return c.limiter.schedule({ id: 'b' }, c.promise, null, 2)
})
.then(function () {
return c.limiter.schedule({ id: 'a' }, c.promise, null, 3)
})
.catch(function (e) {
c.mustEqual(e.message, 'A job with the same id already exists (id=a)')
done()
})
})

it('Should return job statuses', function () {
c = makeTest({maxConcurrent: 2, minTime: 100})

Expand Down
12 changes: 0 additions & 12 deletions test/states.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,6 @@ describe('States', function () {
c.mustEqual(states.statusCounts(), { A: 2, B: 0, C: 0 })
})

it('Should not accept duplicate ids', function (done) {
var states = new States(["A", "B", "C"])

states.start('x')
try {
states.start('x')
} catch (e) {
c.mustEqual(states.statusCounts(), { A: 1, B: 0, C: 0 })
done()
}
})

it('Should increment', function () {
var states = new States(["A", "B", "C"])

Expand Down

0 comments on commit b9d4a9b

Please sign in to comment.