diff --git a/lib/cluster/ClusterSubscriber.ts b/lib/cluster/ClusterSubscriber.ts index 4819bcd5..f8ecd216 100644 --- a/lib/cluster/ClusterSubscriber.ts +++ b/lib/cluster/ClusterSubscriber.ts @@ -1,7 +1,8 @@ import {EventEmitter} from 'events' import ConnectionPool from './ConnectionPool' -import {sample, noop} from '../utils/lodash' +import {noop} from '../utils/lodash' import {getNodeKey} from './util' +import {sample} from '../utils' const Redis = require('../redis') const debug = require('../utils/debug')('ioredis:cluster:subscriber') diff --git a/lib/cluster/ConnectionPool.ts b/lib/cluster/ConnectionPool.ts index e6b98e09..01f82080 100644 --- a/lib/cluster/ConnectionPool.ts +++ b/lib/cluster/ConnectionPool.ts @@ -1,6 +1,6 @@ import {parseURL} from '../utils' import {EventEmitter} from 'events' -import {noop, defaults, values} from '../utils/lodash' +import {noop, defaults} from '../utils/lodash' import {IRedisOptions, getNodeKey} from './util' const Redis = require('../redis') @@ -23,7 +23,8 @@ export default class ConnectionPool extends EventEmitter { } public getNodes(role: 'all' | 'master' | 'slave' = 'all'): any[] { - return values(this.nodes[role]) + const nodes = this.nodes[role] + return Object.keys(nodes).map((key) => nodes[key]) } /** diff --git a/lib/cluster/index.js b/lib/cluster/index.js index 8b4a471b..86a2116c 100644 --- a/lib/cluster/index.js +++ b/lib/cluster/index.js @@ -348,7 +348,7 @@ Cluster.prototype.refreshSlotsCache = function (callback) { } }; - var keys = _.shuffle(Object.keys(this.connectionPool.nodes.all)); + var keys = utils.shuffle(Object.keys(this.connectionPool.nodes.all)); var lastNodeError = null; @@ -426,7 +426,7 @@ Cluster.prototype.sendCommand = function (command, stream, node) { command.__is_reject_overwritten = true; var reject = command.reject; command.reject = function (err) { - var partialTry = _.partial(tryConnection, true); + var partialTry = tryConnection.bind(null, true) _this.handleError(err, ttl, { moved: function (slot, key) { debug('command %s is moved to %s', command.name, key); @@ -511,8 +511,8 @@ Cluster.prototype.sendCommand = function (command, stream, node) { } } if (!redis) { - redis = _.sample(_this.connectionPool.nodes[to]) || - _.sample(_this.connectionPool.nodes.all); + redis = utils.sample(_this.connectionPool.getNodes(to)) || + utils.sample(_this.connectionPool.getNodes('all')); } } if (node && !node.redis) { diff --git a/lib/commander.js b/lib/commander.js index 6b484055..69a9a520 100644 --- a/lib/commander.js +++ b/lib/commander.js @@ -26,7 +26,9 @@ function Commander() { this.scriptsSet = {}; } -var commands = _.difference(require('redis-commands').list, ['monitor']); +var commands = require('redis-commands').list.filter(function (command) { + return command !== 'monitor' +}) commands.push('sentinel'); /** @@ -53,7 +55,7 @@ Commander.prototype.createBuiltinCommand = function (commandName) { }; }; -_.forEach(commands, function (commandName) { +commands.forEach(function (commandName) { Commander.prototype[commandName] = generateFunction(commandName, 'utf8'); Commander.prototype[commandName + 'Buffer'] = generateFunction(commandName, null); }); diff --git a/lib/connectors/SentinelConnector/index.ts b/lib/connectors/SentinelConnector/index.ts index 24a0f166..9e3c4878 100644 --- a/lib/connectors/SentinelConnector/index.ts +++ b/lib/connectors/SentinelConnector/index.ts @@ -1,6 +1,5 @@ import {createConnection, Socket} from 'net' -import {sample} from '../../utils/lodash' -import {CONNECTION_CLOSED_ERROR_MSG, packObject} from '../../utils' +import {CONNECTION_CLOSED_ERROR_MSG, packObject, sample} from '../../utils' import {TLSSocket} from 'tls' import {ITcpConnectionOptions, isIIpcConnectionOptions} from '../StandaloneConnector' import SentinelIterator from './SentinelIterator' diff --git a/lib/pipeline.js b/lib/pipeline.js index f22f7ac2..5068eba8 100644 --- a/lib/pipeline.js +++ b/lib/pipeline.js @@ -217,7 +217,7 @@ Pipeline.prototype.exec = function (callback) { this.nodeifiedPromise = true; asCallback(this.promise, callback); } - if (_.isEmpty(this._queue)) { + if (!this._queue.length) { this.resolve([]); } var pipelineSlot, i; diff --git a/lib/redis/parser.js b/lib/redis/parser.js index f3bedd74..9f533fb2 100644 --- a/lib/redis/parser.js +++ b/lib/redis/parser.js @@ -70,7 +70,7 @@ exports.returnError = function (err) { }; var sharedBuffers = {}; -_.forEach(['message', 'pmessage', 'subscribe', 'psubscribe', 'unsubscribe', 'punsubscribe'], function (str) { +['message', 'pmessage', 'subscribe', 'psubscribe', 'unsubscribe', 'punsubscribe'].forEach(function (str) { sharedBuffers[str] = Buffer.from(str); }); exports.returnReply = function (reply) { diff --git a/lib/utils/index.ts b/lib/utils/index.ts index 92836cb9..f3f91486 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -293,6 +293,33 @@ export function sample (array: T[], from: number = 0): T { } return array[from + Math.floor(Math.random() * (length - from))] } +/** + * Shuffle the array using the Fisher-Yates Shuffle. + * This method will mutate the original array. + * + * @export + * @template T + * @param {T[]} array + * @returns {T[]} + */ +export function shuffle (array: T[]): T[] { + let counter = array.length + + // While there are elements in the array + while (counter > 0) { + // Pick a random index + const index = Math.floor(Math.random() * counter) + + // Decrease counter by 1 + counter-- + + // And swap the last element with it + [array[counter], array[index]] = [array[index], array[counter]] + } + + return array +} + /** * Error message for connection being disconnected diff --git a/lib/utils/lodash.js b/lib/utils/lodash.js index 07a84b65..12369779 100644 --- a/lib/utils/lodash.js +++ b/lib/utils/lodash.js @@ -1,13 +1,5 @@ 'use strict'; -exports.forEach = require('lodash.foreach'); -exports.pick = require('lodash.pick'); exports.defaults = require('lodash.defaults'); exports.noop = function () {}; -exports.difference = require('lodash.difference'); -exports.sample = require('lodash.sample'); exports.flatten = require('lodash.flatten'); -exports.isEmpty = require('lodash.isempty'); -exports.values = require('lodash.values'); -exports.shuffle = require('lodash.shuffle'); -exports.partial = require('lodash.partial'); diff --git a/package-lock.json b/package-lock.json index 014324dd..404c26e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -734,26 +734,11 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" - }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" - }, - "lodash.isempty": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" - }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -772,30 +757,11 @@ "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=", "dev": true }, - "lodash.partial": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.partial/-/lodash.partial-4.2.1.tgz", - "integrity": "sha1-SfPYz9qjv/izqR0SfpIyRUGJYdQ=" - }, "lodash.pick": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" - }, - "lodash.sample": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.sample/-/lodash.sample-4.2.1.tgz", - "integrity": "sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20=" - }, - "lodash.shuffle": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz", - "integrity": "sha1-FFtQU8+HX29cKjP0i26ZSMbse0s=" - }, - "lodash.values": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", - "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=" + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", + "dev": true }, "lolex": { "version": "1.3.2", diff --git a/package.json b/package.json index 4f6b7969..0fe99701 100644 --- a/package.json +++ b/package.json @@ -33,15 +33,7 @@ "denque": "^1.1.0", "flexbuffer": "0.0.6", "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", - "lodash.foreach": "^4.5.0", - "lodash.isempty": "^4.4.0", - "lodash.partial": "^4.2.1", - "lodash.pick": "^4.4.0", - "lodash.sample": "^4.2.1", - "lodash.shuffle": "^4.2.0", - "lodash.values": "^4.3.0", "redis-commands": "^1.3.5", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", diff --git a/test/unit/utils.js b/test/unit/utils.js index f5531cda..3b69b3d2 100644 --- a/test/unit/utils.js +++ b/test/unit/utils.js @@ -167,4 +167,53 @@ describe('utils', function () { Math.random.restore(); }); }); + + describe('.shuffle', function () { + function compareArray (arr1, arr2) { + if (arr1.length !== arr2.length) { + return false + } + arr1.sort() + arr2.sort() + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] !== arr2[i]) { + return false + } + } + return true + } + function testShuffle (arr) { + const origin = arr.slice(0) + expect(compareArray(origin, utils.shuffle(arr))).to.eql(true) + } + it('contains all items', () => { + testShuffle([1]) + testShuffle([1, 2]) + testShuffle([2, 1]) + testShuffle([1, 1, 1]) + testShuffle([1, 2, 3]) + testShuffle([3, -1, 0, 2, -1]) + testShuffle(['a', 'b', 'd', 'c']) + testShuffle(['c', 'b']) + }) + + it('mutates the original array', () => { + const arr = [3, 7] + const ret = utils.shuffle(arr) + expect(arr === ret).to.eql(true) + }) + + it('shuffles the array', () => { + const arr = [1, 2, 3, 4] + const copy = arr.slice(0) + while (true) { + utils.shuffle(copy) + for (let i = 0; i < copy.length; i++) { + if (arr[i] !== copy[i]) { + return + } + } + } + }) + }) });