Comparing version 1.0.10 to 1.0.11
@@ -36,10 +36,19 @@ var _ = require('lodash'); | ||
*/ | ||
function Command(name, args, replyEncoding, callback) { | ||
function Command(name, args, options, callback) { | ||
this.name = name; | ||
this.replyEncoding = options && options.replyEncoding; | ||
this.errorStack = options && options.errorStack; | ||
this.args = args ? _.flatten(args) : []; | ||
var _this = this; | ||
this.promise = new Promise(function (resolve, reject) { | ||
_this.resolve = _this._convertValue(resolve); | ||
_this.reject = reject; | ||
if (_this.errorStack) { | ||
_this.reject = function (err) { | ||
reject(utils.optimizeErrorStack(err, _this.errorStack, __dirname)); | ||
}; | ||
} else { | ||
_this.reject = reject; | ||
} | ||
_this.name = name; | ||
_this.args = args ? _.flatten(args) : []; | ||
var transformer = Command._transformer.argument[_this.name]; | ||
@@ -49,3 +58,2 @@ if (transformer) { | ||
} | ||
_this.replyEncoding = replyEncoding; | ||
}).nodeify(callback); | ||
@@ -52,0 +60,0 @@ } |
var _ = require('lodash'); | ||
var Command = require('./command'); | ||
function Commander () {} | ||
function Commander() { | ||
this.options = _.defaults(this.options || {}, { | ||
showFriendlyErrorStack: false | ||
}); | ||
} | ||
@@ -19,3 +23,10 @@ var commands = _.difference(_.keys(require('ioredis-commands'), ['monitor'])); | ||
return this.sendCommand(new Command(commandName, args, 'utf8', callback)); | ||
var options = { replyEncoding: 'utf8' }; | ||
if (this.options.showFriendlyErrorStack) { | ||
options.errorStack = new Error().stack; | ||
} | ||
var command = new Command(commandName, args, options, callback); | ||
return this.sendCommand(command); | ||
}; | ||
@@ -31,3 +42,9 @@ | ||
return this.sendCommand(new Command(commandName, args, null, callback)); | ||
var options = { replyEncoding: null }; | ||
if (this.options.showFriendlyErrorStack) { | ||
options.errorStack = new Error().stack; | ||
} | ||
var command = new Command(commandName, args, options, callback); | ||
return this.sendCommand(command); | ||
}; | ||
@@ -45,3 +62,9 @@ }); | ||
return this.sendCommand(new Command(commandName, args, 'utf8', callback)); | ||
var options = { replyEncoding: 'utf8' }; | ||
if (this.options && this.options.showFriendlyErrorStack) { | ||
options.errorStack = new Error().stack; | ||
} | ||
var command = new Command(commandName, args, options, callback); | ||
return this.sendCommand(command); | ||
}; | ||
@@ -58,3 +81,9 @@ | ||
return this.sendCommand(new Command(commandName, args, null, callback)); | ||
var options = { replyEncoding: null }; | ||
if (this.options && this.options.showFriendlyErrorStack) { | ||
options.errorStack = new Error().stack; | ||
} | ||
var command = new Command(commandName, args, options, callback); | ||
return this.sendCommand(command); | ||
}; | ||
@@ -61,0 +90,0 @@ |
@@ -7,2 +7,4 @@ var _ = require('lodash'); | ||
function Pipeline(redis) { | ||
Commander.call(this); | ||
this.redis = redis; | ||
@@ -82,6 +84,5 @@ this._queue = []; | ||
if (scripts.length) { | ||
var checkExists = new Command('script', ['exists'].concat(scripts.map(function (item) { | ||
promise = this.redis.script('exists', scripts.map(function (item) { | ||
return item.sha; | ||
})), 'utf8'); | ||
promise = this.redis.sendCommand(checkExists).then(function (results) { | ||
})).then(function (results) { | ||
var pending = []; | ||
@@ -94,3 +95,3 @@ for (var i = 0; i < results.length; ++i) { | ||
return Promise.all(pending.map(function (script) { | ||
return _this.redis.sendCommand(new Command('script', ['load', script.lua], 'utf8')); | ||
return _this.redis.script('load', script.lua); | ||
})); | ||
@@ -97,0 +98,0 @@ }); |
@@ -32,3 +32,3 @@ var Redis = require('./redis'); | ||
this.fails = 0; | ||
this.options = _.defaults(options || {}, RedisCluster._defaultOptions); | ||
this.options = _.defaults(options || {}, this.options || {}, RedisCluster._defaultOptions); | ||
@@ -35,0 +35,0 @@ this.startupNodes = startupNodes.map(function (options) { |
@@ -89,5 +89,6 @@ var _ = require('lodash'); | ||
var resultOptions; | ||
if (typeof port === 'object') { | ||
// Redis(options) | ||
this.options = _.cloneDeep(port); | ||
resultOptions = port; | ||
} else if (typeof port === 'string' && !utils.isInt(port)) { | ||
@@ -109,17 +110,17 @@ // Redis(url, options) | ||
} | ||
this.options = _.defaults(host ? _.cloneDeep(host) : {}, parsedOptions); | ||
resultOptions = _.defaults(host || {}, parsedOptions); | ||
} else { | ||
// Redis(port, host, options) or Redis(port, options) | ||
if (host && typeof host === 'object') { | ||
this.options = _.defaults(_.cloneDeep(host), { port: port }); | ||
resultOptions = _.defaults(host, { port: port }); | ||
} else { | ||
this.options = _.defaults(options ? _.cloneDeep(options) : {}, { port: port, host: host }); | ||
resultOptions = _.defaults(options || {}, { port: port, host: host }); | ||
} | ||
} | ||
_.defaults(this.options, Redis._defaultOptions); | ||
if (typeof this.options.port === 'string') { | ||
this.options.port = parseInt(this.options.port, 10); | ||
if (resultOptions && typeof resultOptions.port === 'string') { | ||
resultOptions.port = parseInt(resultOptions.port, 10); | ||
} | ||
this.options = _.defaults(resultOptions || {}, this.options || {}, Redis._defaultOptions); | ||
if (this.options.parser === 'javascript') { | ||
@@ -329,3 +330,3 @@ this.Parser = require('./parsers/javascript'); | ||
var _this = this; | ||
this.sendCommand(new Command('info', null, 'utf8', function (err, res) { | ||
this.info(function (err, res) { | ||
if (err) { | ||
@@ -357,3 +358,3 @@ return callback(err); | ||
} | ||
})); | ||
}); | ||
}; | ||
@@ -387,3 +388,8 @@ | ||
return script.execute(this, args, 'utf8', callback); | ||
var options = { replyEncoding: 'utf8' }; | ||
if (this.options.showFriendlyErrorStack) { | ||
options.errorStack = new Error().stack; | ||
} | ||
return script.execute(this, args, options, callback); | ||
}; | ||
@@ -399,3 +405,8 @@ | ||
return script.execute(this, args, null, callback); | ||
var options = { replyEncoding: null }; | ||
if (this.options.showFriendlyErrorStack) { | ||
options.errorStack = new Error().stack; | ||
} | ||
return script.execute(this, args, options, callback); | ||
}; | ||
@@ -614,2 +625,4 @@ }; | ||
Redis.Promise = Promise; | ||
var Sentinel = require('./sentinel'); |
@@ -15,3 +15,3 @@ var Queue = require('fastqueue'); | ||
if (self.condition.auth) { | ||
self.sendCommand(new Command('auth', [self.condition.auth], 'utf8', function (err, res) { | ||
self.auth(self.condition.auth, function (err, res) { | ||
if (res === 'OK') { | ||
@@ -27,3 +27,3 @@ return; | ||
} | ||
})); | ||
}); | ||
} | ||
@@ -101,11 +101,3 @@ | ||
return function (data) { | ||
try { | ||
self.replyParser.execute(data); | ||
} catch (error) { | ||
// This is an unexpected parser problem, an exception that came from the parser code itself. | ||
// Parser should emit "error" events if it notices things are out of whack. | ||
// Callbacks that throw exceptions will land in return_reply(), below. | ||
// TODO - it might be nice to have a different "error" event for different types of errors | ||
self.silentEmit('error', error); | ||
} | ||
self.replyParser.execute(data); | ||
}; | ||
@@ -136,3 +128,3 @@ }; | ||
if (item.select !== self.condition.select && item.command.name !== 'select') { | ||
self.sendCommand(new Command('select', [item.select])); | ||
self.select(item.select); | ||
} | ||
@@ -146,3 +138,3 @@ self.sendCommand(item.command, item.stream); | ||
debug('connect to db [%d]', finalSelect); | ||
self.sendCommand(new Command('select', [finalSelect])); | ||
self.selectBuffer(finalSelect); | ||
} | ||
@@ -153,3 +145,3 @@ | ||
if (condition.mode.monitoring) { | ||
self.sendCommand(new Command('monitor', null, 'utf8', function () { | ||
self.call('monitor', function () { | ||
self.sendCommand = function (command) { | ||
@@ -160,3 +152,3 @@ command.reject(new Error('Connection is in monitoring mode, can\'t process commands.')); | ||
self.emit('monitoring'); | ||
})); | ||
}); | ||
} else { | ||
@@ -163,0 +155,0 @@ if (condition.mode.subscriber && self.options.autoResubscribe) { |
@@ -41,2 +41,7 @@ var _ = require('lodash'); | ||
err.command = { | ||
name: command.name, | ||
args: command.args | ||
}; | ||
command.reject(err); | ||
@@ -43,0 +48,0 @@ }; |
@@ -11,3 +11,3 @@ var Command = require('./command'); | ||
Script.prototype.execute = function (container, args, replyEncoding, callback) { | ||
Script.prototype.execute = function (container, args, options, callback) { | ||
if (this.numberOfKeys) { | ||
@@ -17,3 +17,3 @@ args.unshift(this.numberOfKeys); | ||
var result = container.sendCommand(new Command('evalsha', [this.sha].concat(args), replyEncoding)); | ||
var result = container.sendCommand(new Command('evalsha', [this.sha].concat(args), options)); | ||
if (result instanceof Promise) { | ||
@@ -25,3 +25,3 @@ var _this = this; | ||
} | ||
return container.sendCommand(new Command('eval', [_this.lua].concat(args), replyEncoding)); | ||
return container.sendCommand(new Command('eval', [_this.lua].concat(args), options)); | ||
}).nodeify(callback); | ||
@@ -28,0 +28,0 @@ } |
@@ -224,1 +224,25 @@ /** | ||
}; | ||
/** | ||
* Optimize error stack | ||
* | ||
* @param {Error} error - actually error | ||
* @param {string} friendlyStack - the stack that more meaningful | ||
* @param {string} filterPath - only show stacks with the specified path | ||
*/ | ||
exports.optimizeErrorStack = function (error, friendlyStack, filterPath) { | ||
var stacks = friendlyStack.split('\n'); | ||
var lines = ''; | ||
var i; | ||
for (i = 1; i < stacks.length; ++i) { | ||
if (stacks[i].indexOf(filterPath) === -1) { | ||
break; | ||
} | ||
} | ||
for (var j = i; j < stacks.length; ++j) { | ||
lines += '\n' + stacks[j]; | ||
} | ||
var pos = error.stack.indexOf('\n'); | ||
error.stack = error.stack.slice(0, pos) + lines; | ||
return error; | ||
}; |
{ | ||
"name": "ioredis", | ||
"version": "1.0.10", | ||
"version": "1.0.11", | ||
"description": "A delightful, performance-focused Redis client for Node and io.js", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
116
README.md
@@ -10,3 +10,3 @@ # ioredis | ||
Support Redis >= 2.6.12 and (Node.js >= 0.11.13 or io.js). | ||
Support Redis >= 2.6.12 and (Node.js >= 0.11.16 or io.js). | ||
@@ -34,2 +34,4 @@ # Feature | ||
* [Migrating from node_redis](https://github.com/luin/ioredis/wiki/Migrating-from-node_redis) | ||
* [Error Handling](#error-handling) | ||
* [Benchmark](#benchmark) | ||
@@ -222,3 +224,3 @@ # Quick Start | ||
```javascript | ||
redis.pipeline().get('foo').mulit().set('foo', 'bar').get('foo').exec().get('foo').exec(); | ||
redis.pipeline().get('foo').multi().set('foo', 'bar').get('foo').exec().get('foo').exec(); | ||
``` | ||
@@ -436,3 +438,3 @@ | ||
but a few so that if one is down the client will try the next one, and the client will discover other nodes automatically when at least one node is connnected. | ||
0. Some comands can't be used in the cluster mode, e.g. `info` and `pipeline`, custom commands also don't work(currently). | ||
0. Some features are not available in the cluster mode, e.g. `pipeline` and custom commands(currently). | ||
@@ -446,2 +448,78 @@ ## hiredis | ||
# Error Handling | ||
All the errors returned by the Redis server are instances of `ReplyError`, which can be accessed via `Redis`: | ||
```javascript | ||
var Redis = require('ioredis'); | ||
var redis = new Redis(); | ||
// This command causes an reply error since SET command requires two arguments. | ||
redis.set('foo', function (err) { | ||
// err instanceof Redis.ReplyError | ||
}); | ||
``` | ||
When a reply error is not handled(no callback is specified and no `catch` method is chained), | ||
the error will be logged to the stderr. For instance: | ||
```javascript | ||
var Redis = require('ioredis'); | ||
var redis = new Redis(); | ||
redis.set('foo'); | ||
``` | ||
The following error will be printed: | ||
``` | ||
Unhandled rejection ReplyError: ERR wrong number of arguments for 'set' command | ||
at ReplyParser._parseResult (/app/node_modules/ioredis/lib/parsers/javascript.js:60:14) | ||
at ReplyParser.execute (/app/node_modules/ioredis/lib/parsers/javascript.js:178:20) | ||
at Socket.<anonymous> (/app/node_modules/ioredis/lib/redis/event_handler.js:99:22) | ||
at Socket.emit (events.js:97:17) | ||
at readableAddChunk (_stream_readable.js:143:16) | ||
at Socket.Readable.push (_stream_readable.js:106:10) | ||
at TCP.onread (net.js:509:20) | ||
``` | ||
But the error stack doesn't make any sense because the whole stack happens in the ioreids | ||
module itself, not in your code. So it's not easy to find out where the error happens in your code. | ||
ioredis provides an option `showFriendlyErrorStack` to solve the problem. When you enable | ||
`showFriendlyErrorStack`, ioredis will optimize the error stack for you: | ||
```javascript | ||
var Redis = require('ioredis'); | ||
var redis = new Redis({ showFriendlyErrorStack: true }); | ||
redis.set('foo'); | ||
``` | ||
And the output will be: | ||
``` | ||
Unhandled rejection ReplyError: ERR wrong number of arguments for 'set' command | ||
at Object.<anonymous> (/app/index.js:3:7) | ||
at Module._compile (module.js:446:26) | ||
at Object.Module._extensions..js (module.js:464:10) | ||
at Module.load (module.js:341:32) | ||
at Function.Module._load (module.js:296:12) | ||
at Function.Module.runMain (module.js:487:10) | ||
at startup (node.js:111:16) | ||
at node.js:799:3 | ||
``` | ||
This time the stack tells you that the error happens on the third line in your code, pretty sweet! | ||
However, it would decrease the performance significantly to optimize the error stack. So by | ||
default this option is disabled and can be only used for debug purpose. You **shouldn't** use this feature in production environment. | ||
If you want to catch all unhandled errors without decrease performance, there's another way: | ||
```javascript | ||
var Redis = require('ioredis'); | ||
Redis.Promise.onPossiblyUnhandledRejection(function (error) { | ||
// you can log the error here. | ||
// error.command.name is the command name, here is 'set' | ||
// error.command.args is the command arguments, here is ['foo'] | ||
}); | ||
var redis = new Redis(); | ||
redis.set('foo'); | ||
``` | ||
# Benchmark | ||
@@ -453,25 +531,25 @@ | ||
> npm run bench | ||
simple set | ||
65,438 op/s » ioredis | ||
36,954 op/s » node_redis | ||
simple set | ||
81,026 op/s » ioredis | ||
43,487 op/s » node_redis | ||
simple get | ||
71,109 op/s » ioredis | ||
36,825 op/s » node_redis | ||
simple get | ||
79,565 op/s » ioredis | ||
41,808 op/s » node_redis | ||
simple get with pipeline | ||
11,123 op/s » ioredis | ||
3,820 op/s » node_redis | ||
simple get with pipeline | ||
12,711 op/s » ioredis | ||
4,714 op/s » node_redis | ||
lrange 100 | ||
58,812 op/s » ioredis | ||
46,703 op/s » node_redis | ||
lrange 100 | ||
61,149 op/s » ioredis | ||
48,827 op/s » node_redis | ||
Suites: 4 | ||
Benches: 8 | ||
Elapsed: 61,715.11 ms | ||
Suites: 4 | ||
Benches: 8 | ||
Elapsed: 57,882.94 ms | ||
``` | ||
You can find the code at `benchmark.js`. | ||
You can find the code at `benchmark.js` and run it yourself using `npm run bench`. | ||
@@ -478,0 +556,0 @@ # Running tests |
30
test.js
@@ -7,5 +7,11 @@ // var noderedis = require('redis'); | ||
var Redis = require('./'); | ||
// var redis = new Redis({ connectTimeout: 10 }); | ||
// // var redis = new Redis(); | ||
var redis = new Redis({ showFriendlyErrorStack: 10 }); | ||
// var redis = new Redis(); | ||
Redis.Promise.onPossiblyUnhandledRejection(function(error){ | ||
console.log(error); | ||
}); | ||
redis.set('foo'); | ||
// var redis = new Redis(); | ||
// redis.blpop('que1', 0, function () { | ||
@@ -46,12 +52,12 @@ // console.log(arguments); | ||
var cluster = new Redis.Cluster([{ | ||
port: 6380, | ||
host: '127.0.0.1' | ||
}, { | ||
port: 6381, | ||
host: '127.0.0.1' | ||
}]); | ||
cluster.mget('foo13', 'foo', function (err, res) { | ||
console.log(err, res); | ||
}); | ||
// var cluster = new Redis.Cluster([{ | ||
// port: 6380, | ||
// host: '127.0.0.1' | ||
// }, { | ||
// port: 6381, | ||
// host: '127.0.0.1' | ||
// }]); | ||
// cluster.mget('foo13', 'foo', function (err, res) { | ||
// console.log(err, res); | ||
// }); | ||
// var redis = new Redis(6388, '177.22.22.2', { | ||
@@ -58,0 +64,0 @@ // enableReadyCheck: false |
@@ -27,3 +27,3 @@ var Command = require('../../lib/command'); | ||
it('should return buffer when replyEncoding is not set', function (done) { | ||
var command = new Command('get', ['foo'], null, function (err, result) { | ||
var command = new Command('get', ['foo'], { replyEncoding: null }, function (err, result) { | ||
expect(result).to.be.instanceof(Buffer); | ||
@@ -37,3 +37,3 @@ expect(result.toString()).to.eql('foo'); | ||
it('should covert result to string if replyEncoding is specified', function (done) { | ||
var command = new Command('get', ['foo'], 'utf8', function (err, result) { | ||
var command = new Command('get', ['foo'], { replyEncoding: 'utf8' }, function (err, result) { | ||
expect(result).to.eql('foo'); | ||
@@ -47,3 +47,3 @@ done(); | ||
var base64 = new Buffer('foo').toString('base64'); | ||
var command = new Command('get', ['foo'], 'base64', function (err, result) { | ||
var command = new Command('get', ['foo'], { replyEncoding: 'base64' }, function (err, result) { | ||
expect(result).to.eql(base64); | ||
@@ -50,0 +50,0 @@ done(); |
@@ -59,2 +59,7 @@ describe('Redis', function () { | ||
option = getOption({ | ||
showFriendlyErrorStack: true | ||
}); | ||
expect(option).to.have.property('showFriendlyErrorStack', true); | ||
option = getOption(6380, { | ||
@@ -61,0 +66,0 @@ host: '192.168.1.1' |
@@ -116,2 +116,10 @@ var utils = require('../../lib/utils'); | ||
}); | ||
describe('.optimizeErrorStack', function () { | ||
it('should return correctly', function () { | ||
var error = new Error(); | ||
var res = utils.optimizeErrorStack(error, new Error().stack + '\n@', __dirname); | ||
expect(res.stack.split('\n').pop()).to.eql('@'); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1508695
57
4015
599