index.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const lodash_1 = require("../utils/lodash");
  4. const util_1 = require("util");
  5. const events_1 = require("events");
  6. const Deque = require("denque");
  7. const command_1 = require("../command");
  8. const commander_1 = require("../commander");
  9. const utils_1 = require("../utils");
  10. const standard_as_callback_1 = require("standard-as-callback");
  11. const eventHandler = require("./event_handler");
  12. const connectors_1 = require("../connectors");
  13. const ScanStream_1 = require("../ScanStream");
  14. const commands = require("redis-commands");
  15. const PromiseContainer = require("../promiseContainer");
  16. const transaction_1 = require("../transaction");
  17. const RedisOptions_1 = require("./RedisOptions");
  18. const debug = utils_1.Debug("redis");
  19. /**
  20. * Creates a Redis instance
  21. *
  22. * @constructor
  23. * @param {(number|string|Object)} [port=6379] - Port of the Redis server,
  24. * or a URL string(see the examples below),
  25. * or the `options` object(see the third argument).
  26. * @param {string|Object} [host=localhost] - Host of the Redis server,
  27. * when the first argument is a URL string,
  28. * this argument is an object represents the options.
  29. * @param {Object} [options] - Other options.
  30. * @param {number} [options.port=6379] - Port of the Redis server.
  31. * @param {string} [options.host=localhost] - Host of the Redis server.
  32. * @param {string} [options.family=4] - Version of IP stack. Defaults to 4.
  33. * @param {string} [options.path=null] - Local domain socket path. If set the `port`,
  34. * `host` and `family` will be ignored.
  35. * @param {number} [options.keepAlive=0] - TCP KeepAlive on the socket with a X ms delay before start.
  36. * Set to a non-number value to disable keepAlive.
  37. * @param {boolean} [options.noDelay=true] - Whether to disable the Nagle's Algorithm. By default we disable
  38. * it to reduce the latency.
  39. * @param {string} [options.connectionName=null] - Connection name.
  40. * @param {number} [options.db=0] - Database index to use.
  41. * @param {string} [options.password=null] - If set, client will send AUTH command
  42. * with the value of this option when connected.
  43. * @param {string} [options.username=null] - Similar to `password`, Provide this for Redis ACL support.
  44. * @param {boolean} [options.dropBufferSupport=false] - Drop the buffer support for better performance.
  45. * This option is recommended to be enabled when
  46. * handling large array response and you don't need the buffer support.
  47. * @param {boolean} [options.enableReadyCheck=true] - When a connection is established to
  48. * the Redis server, the server might still be loading the database from disk.
  49. * While loading, the server not respond to any commands.
  50. * To work around this, when this option is `true`,
  51. * ioredis will check the status of the Redis server,
  52. * and when the Redis server is able to process commands,
  53. * a `ready` event will be emitted.
  54. * @param {boolean} [options.enableOfflineQueue=true] - By default,
  55. * if there is no active connection to the Redis server,
  56. * commands are added to a queue and are executed once the connection is "ready"
  57. * (when `enableReadyCheck` is `true`,
  58. * "ready" means the Redis server has loaded the database from disk, otherwise means the connection
  59. * to the Redis server has been established). If this option is false,
  60. * when execute the command when the connection isn't ready, an error will be returned.
  61. * @param {number} [options.connectTimeout=10000] - The milliseconds before a timeout occurs during the initial
  62. * connection to the Redis server.
  63. * @param {boolean} [options.autoResubscribe=true] - After reconnected, if the previous connection was in the
  64. * subscriber mode, client will auto re-subscribe these channels.
  65. * @param {boolean} [options.autoResendUnfulfilledCommands=true] - If true, client will resend unfulfilled
  66. * commands(e.g. block commands) in the previous connection when reconnected.
  67. * @param {boolean} [options.lazyConnect=false] - By default,
  68. * When a new `Redis` instance is created, it will connect to Redis server automatically.
  69. * If you want to keep the instance disconnected until a command is called, you can pass the `lazyConnect` option to
  70. * the constructor:
  71. *
  72. * ```javascript
  73. * var redis = new Redis({ lazyConnect: true });
  74. * // No attempting to connect to the Redis server here.
  75. * // Now let's connect to the Redis server
  76. * redis.get('foo', function () {
  77. * });
  78. * ```
  79. * @param {Object} [options.tls] - TLS connection support. See https://github.com/luin/ioredis#tls-options
  80. * @param {string} [options.keyPrefix=''] - The prefix to prepend to all keys in a command.
  81. * @param {function} [options.retryStrategy] - See "Quick Start" section
  82. * @param {number} [options.maxRetriesPerRequest] - See "Quick Start" section
  83. * @param {number} [options.maxLoadingRetryTime=10000] - when redis server is not ready, we will wait for
  84. * `loading_eta_seconds` from `info` command or maxLoadingRetryTime (milliseconds), whichever is smaller.
  85. * @param {function} [options.reconnectOnError] - See "Quick Start" section
  86. * @param {boolean} [options.readOnly=false] - Enable READONLY mode for the connection.
  87. * Only available for cluster mode.
  88. * @param {boolean} [options.stringNumbers=false] - Force numbers to be always returned as JavaScript
  89. * strings. This option is necessary when dealing with big numbers (exceed the [-2^53, +2^53] range).
  90. * @param {boolean} [options.enableTLSForSentinelMode=false] - Whether to support the `tls` option
  91. * when connecting to Redis via sentinel mode.
  92. * @param {NatMap} [options.natMap=null] NAT map for sentinel connector.
  93. * @param {boolean} [options.updateSentinels=true] - Update the given `sentinels` list with new IP
  94. * addresses when communicating with existing sentinels.
  95. * @param {boolean} [options.failoverDetector=false] - Detect failover actively by subscribing to the
  96. * related channels. With this option disabled, ioredis is still able to detect failovers because Redis
  97. * Sentinel will disconnect all clients whenever a failover happens, so ioredis will reconnect to the new
  98. * master. This option is useful when you want to detect failover quicker, but it will create more TCP
  99. * connections to Redis servers in order to subscribe to related channels.
  100. * @param {boolean} [options.enableAutoPipelining=false] - When enabled, all commands issued during an event loop
  101. * iteration are automatically wrapped in a pipeline and sent to the server at the same time.
  102. * This can dramatically improve performance.
  103. * @param {string[]} [options.autoPipeliningIgnoredCommands=[]] - The list of commands which must not be automatically wrapped in pipelines.
  104. * @param {number} [options.maxScriptsCachingTime=60000] Default script definition caching time.
  105. * @extends [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)
  106. * @extends Commander
  107. * @example
  108. * ```js
  109. * var Redis = require('ioredis');
  110. *
  111. * var redis = new Redis();
  112. *
  113. * var redisOnPort6380 = new Redis(6380);
  114. * var anotherRedis = new Redis(6380, '192.168.100.1');
  115. * var unixSocketRedis = new Redis({ path: '/tmp/echo.sock' });
  116. * var unixSocketRedis2 = new Redis('/tmp/echo.sock');
  117. * var urlRedis = new Redis('redis://user:password@redis-service.com:6379/');
  118. * var urlRedis2 = new Redis('//localhost:6379');
  119. * var urlRedisTls = new Redis('rediss://user:password@redis-service.com:6379/');
  120. * var authedRedis = new Redis(6380, '192.168.100.1', { password: 'password' });
  121. * ```
  122. */
  123. exports.default = Redis;
  124. function Redis() {
  125. if (!(this instanceof Redis)) {
  126. console.error(new Error("Calling `Redis()` like a function is deprecated. Using `new Redis()` instead.").stack.replace("Error", "Warning"));
  127. return new Redis(arguments[0], arguments[1], arguments[2]);
  128. }
  129. this.parseOptions(arguments[0], arguments[1], arguments[2]);
  130. events_1.EventEmitter.call(this);
  131. commander_1.default.call(this);
  132. this.resetCommandQueue();
  133. this.resetOfflineQueue();
  134. this.connectionEpoch = 0;
  135. if (this.options.Connector) {
  136. this.connector = new this.options.Connector(this.options);
  137. }
  138. else if (this.options.sentinels) {
  139. const sentinelConnector = new connectors_1.SentinelConnector(this.options);
  140. sentinelConnector.emitter = this;
  141. this.connector = sentinelConnector;
  142. }
  143. else {
  144. this.connector = new connectors_1.StandaloneConnector(this.options);
  145. }
  146. this.retryAttempts = 0;
  147. // Prepare a cache of scripts and setup a interval which regularly clears it
  148. this._addedScriptHashes = {};
  149. // Prepare autopipelines structures
  150. this._autoPipelines = new Map();
  151. this._runningAutoPipelines = new Set();
  152. Object.defineProperty(this, "autoPipelineQueueSize", {
  153. get() {
  154. let queued = 0;
  155. for (const pipeline of this._autoPipelines.values()) {
  156. queued += pipeline.length;
  157. }
  158. return queued;
  159. },
  160. });
  161. // end(or wait) -> connecting -> connect -> ready -> end
  162. if (this.options.lazyConnect) {
  163. this.setStatus("wait");
  164. }
  165. else {
  166. this.connect().catch(lodash_1.noop);
  167. }
  168. }
  169. util_1.inherits(Redis, events_1.EventEmitter);
  170. Object.assign(Redis.prototype, commander_1.default.prototype);
  171. /**
  172. * Create a Redis instance
  173. *
  174. * @deprecated
  175. */
  176. // @ts-ignore
  177. Redis.createClient = function (...args) {
  178. // @ts-ignore
  179. return new Redis(...args);
  180. };
  181. /**
  182. * Default options
  183. *
  184. * @var defaultOptions
  185. * @private
  186. */
  187. Redis.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
  188. Redis.prototype.resetCommandQueue = function () {
  189. this.commandQueue = new Deque();
  190. };
  191. Redis.prototype.resetOfflineQueue = function () {
  192. this.offlineQueue = new Deque();
  193. };
  194. Redis.prototype.parseOptions = function () {
  195. this.options = {};
  196. let isTls = false;
  197. for (let i = 0; i < arguments.length; ++i) {
  198. const arg = arguments[i];
  199. if (arg === null || typeof arg === "undefined") {
  200. continue;
  201. }
  202. if (typeof arg === "object") {
  203. lodash_1.defaults(this.options, arg);
  204. }
  205. else if (typeof arg === "string") {
  206. lodash_1.defaults(this.options, utils_1.parseURL(arg));
  207. if (arg.startsWith("rediss://")) {
  208. isTls = true;
  209. }
  210. }
  211. else if (typeof arg === "number") {
  212. this.options.port = arg;
  213. }
  214. else {
  215. throw new Error("Invalid argument " + arg);
  216. }
  217. }
  218. if (isTls) {
  219. lodash_1.defaults(this.options, { tls: true });
  220. }
  221. lodash_1.defaults(this.options, Redis.defaultOptions);
  222. if (typeof this.options.port === "string") {
  223. this.options.port = parseInt(this.options.port, 10);
  224. }
  225. if (typeof this.options.db === "string") {
  226. this.options.db = parseInt(this.options.db, 10);
  227. }
  228. if (this.options.parser === "hiredis") {
  229. console.warn("Hiredis parser is abandoned since ioredis v3.0, and JavaScript parser will be used");
  230. }
  231. this.options = utils_1.resolveTLSProfile(this.options);
  232. };
  233. /**
  234. * Change instance's status
  235. * @private
  236. */
  237. Redis.prototype.setStatus = function (status, arg) {
  238. // @ts-ignore
  239. if (debug.enabled) {
  240. debug("status[%s]: %s -> %s", this._getDescription(), this.status || "[empty]", status);
  241. }
  242. this.status = status;
  243. process.nextTick(this.emit.bind(this, status, arg));
  244. };
  245. Redis.prototype.clearAddedScriptHashesCleanInterval = function () {
  246. if (this._addedScriptHashesCleanInterval) {
  247. clearInterval(this._addedScriptHashesCleanInterval);
  248. this._addedScriptHashesCleanInterval = null;
  249. }
  250. };
  251. /**
  252. * Create a connection to Redis.
  253. * This method will be invoked automatically when creating a new Redis instance
  254. * unless `lazyConnect: true` is passed.
  255. *
  256. * When calling this method manually, a Promise is returned, which will
  257. * be resolved when the connection status is ready.
  258. * @param {function} [callback]
  259. * @return {Promise<void>}
  260. * @public
  261. */
  262. Redis.prototype.connect = function (callback) {
  263. const _Promise = PromiseContainer.get();
  264. const promise = new _Promise((resolve, reject) => {
  265. if (this.status === "connecting" ||
  266. this.status === "connect" ||
  267. this.status === "ready") {
  268. reject(new Error("Redis is already connecting/connected"));
  269. return;
  270. }
  271. // Make sure only one timer is active at a time
  272. this.clearAddedScriptHashesCleanInterval();
  273. // Scripts need to get reset on reconnect as redis
  274. // might have been restarted or some failover happened
  275. this._addedScriptHashes = {};
  276. // Start the script cache cleaning
  277. this._addedScriptHashesCleanInterval = setInterval(() => {
  278. this._addedScriptHashes = {};
  279. }, this.options.maxScriptsCachingTime);
  280. this.connectionEpoch += 1;
  281. this.setStatus("connecting");
  282. const { options } = this;
  283. this.condition = {
  284. select: options.db,
  285. auth: options.username
  286. ? [options.username, options.password]
  287. : options.password,
  288. subscriber: false,
  289. };
  290. const _this = this;
  291. standard_as_callback_1.default(this.connector.connect(function (type, err) {
  292. _this.silentEmit(type, err);
  293. }), function (err, stream) {
  294. if (err) {
  295. _this.flushQueue(err);
  296. _this.silentEmit("error", err);
  297. reject(err);
  298. _this.setStatus("end");
  299. return;
  300. }
  301. let CONNECT_EVENT = options.tls ? "secureConnect" : "connect";
  302. if (options.sentinels && !options.enableTLSForSentinelMode) {
  303. CONNECT_EVENT = "connect";
  304. }
  305. _this.stream = stream;
  306. if (typeof options.keepAlive === "number") {
  307. stream.setKeepAlive(true, options.keepAlive);
  308. }
  309. if (stream.connecting) {
  310. stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this));
  311. if (options.connectTimeout) {
  312. /*
  313. * Typically, Socket#setTimeout(0) will clear the timer
  314. * set before. However, in some platforms (Electron 3.x~4.x),
  315. * the timer will not be cleared. So we introduce a variable here.
  316. *
  317. * See https://github.com/electron/electron/issues/14915
  318. */
  319. let connectTimeoutCleared = false;
  320. stream.setTimeout(options.connectTimeout, function () {
  321. if (connectTimeoutCleared) {
  322. return;
  323. }
  324. stream.setTimeout(0);
  325. stream.destroy();
  326. const err = new Error("connect ETIMEDOUT");
  327. // @ts-ignore
  328. err.errorno = "ETIMEDOUT";
  329. // @ts-ignore
  330. err.code = "ETIMEDOUT";
  331. // @ts-ignore
  332. err.syscall = "connect";
  333. eventHandler.errorHandler(_this)(err);
  334. });
  335. stream.once(CONNECT_EVENT, function () {
  336. connectTimeoutCleared = true;
  337. stream.setTimeout(0);
  338. });
  339. }
  340. }
  341. else if (stream.destroyed) {
  342. const firstError = _this.connector.firstError;
  343. if (firstError) {
  344. process.nextTick(() => {
  345. eventHandler.errorHandler(_this)(firstError);
  346. });
  347. }
  348. process.nextTick(eventHandler.closeHandler(_this));
  349. }
  350. else {
  351. process.nextTick(eventHandler.connectHandler(_this));
  352. }
  353. if (!stream.destroyed) {
  354. stream.once("error", eventHandler.errorHandler(_this));
  355. stream.once("close", eventHandler.closeHandler(_this));
  356. }
  357. if (options.noDelay) {
  358. stream.setNoDelay(true);
  359. }
  360. const connectionReadyHandler = function () {
  361. _this.removeListener("close", connectionCloseHandler);
  362. resolve();
  363. };
  364. var connectionCloseHandler = function () {
  365. _this.removeListener("ready", connectionReadyHandler);
  366. reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
  367. };
  368. _this.once("ready", connectionReadyHandler);
  369. _this.once("close", connectionCloseHandler);
  370. });
  371. });
  372. return standard_as_callback_1.default(promise, callback);
  373. };
  374. /**
  375. * Disconnect from Redis.
  376. *
  377. * This method closes the connection immediately,
  378. * and may lose some pending replies that haven't written to client.
  379. * If you want to wait for the pending replies, use Redis#quit instead.
  380. * @public
  381. */
  382. Redis.prototype.disconnect = function (reconnect) {
  383. this.clearAddedScriptHashesCleanInterval();
  384. if (!reconnect) {
  385. this.manuallyClosing = true;
  386. }
  387. if (this.reconnectTimeout && !reconnect) {
  388. clearTimeout(this.reconnectTimeout);
  389. this.reconnectTimeout = null;
  390. }
  391. if (this.status === "wait") {
  392. eventHandler.closeHandler(this)();
  393. }
  394. else {
  395. this.connector.disconnect();
  396. }
  397. };
  398. /**
  399. * Disconnect from Redis.
  400. *
  401. * @deprecated
  402. */
  403. Redis.prototype.end = function () {
  404. this.disconnect();
  405. };
  406. /**
  407. * Create a new instance with the same options as the current one.
  408. *
  409. * @example
  410. * ```js
  411. * var redis = new Redis(6380);
  412. * var anotherRedis = redis.duplicate();
  413. * ```
  414. *
  415. * @public
  416. */
  417. Redis.prototype.duplicate = function (override) {
  418. return new Redis(Object.assign({}, this.options, override || {}));
  419. };
  420. Redis.prototype.recoverFromFatalError = function (commandError, err, options) {
  421. this.flushQueue(err, options);
  422. this.silentEmit("error", err);
  423. this.disconnect(true);
  424. };
  425. Redis.prototype.handleReconnection = function handleReconnection(err, item) {
  426. let needReconnect = false;
  427. if (this.options.reconnectOnError) {
  428. needReconnect = this.options.reconnectOnError(err);
  429. }
  430. switch (needReconnect) {
  431. case 1:
  432. case true:
  433. if (this.status !== "reconnecting") {
  434. this.disconnect(true);
  435. }
  436. item.command.reject(err);
  437. break;
  438. case 2:
  439. if (this.status !== "reconnecting") {
  440. this.disconnect(true);
  441. }
  442. if (this.condition.select !== item.select &&
  443. item.command.name !== "select") {
  444. this.select(item.select);
  445. }
  446. this.sendCommand(item.command);
  447. break;
  448. default:
  449. item.command.reject(err);
  450. }
  451. };
  452. /**
  453. * Flush offline queue and command queue with error.
  454. *
  455. * @param {Error} error - The error object to send to the commands
  456. * @param {object} options
  457. * @private
  458. */
  459. Redis.prototype.flushQueue = function (error, options) {
  460. options = lodash_1.defaults({}, options, {
  461. offlineQueue: true,
  462. commandQueue: true,
  463. });
  464. let item;
  465. if (options.offlineQueue) {
  466. while (this.offlineQueue.length > 0) {
  467. item = this.offlineQueue.shift();
  468. item.command.reject(error);
  469. }
  470. }
  471. if (options.commandQueue) {
  472. if (this.commandQueue.length > 0) {
  473. if (this.stream) {
  474. this.stream.removeAllListeners("data");
  475. }
  476. while (this.commandQueue.length > 0) {
  477. item = this.commandQueue.shift();
  478. item.command.reject(error);
  479. }
  480. }
  481. }
  482. };
  483. /**
  484. * Check whether Redis has finished loading the persistent data and is able to
  485. * process commands.
  486. *
  487. * @param {Function} callback
  488. * @private
  489. */
  490. Redis.prototype._readyCheck = function (callback) {
  491. const _this = this;
  492. this.info(function (err, res) {
  493. if (err) {
  494. return callback(err);
  495. }
  496. if (typeof res !== "string") {
  497. return callback(null, res);
  498. }
  499. const info = {};
  500. const lines = res.split("\r\n");
  501. for (let i = 0; i < lines.length; ++i) {
  502. const [fieldName, ...fieldValueParts] = lines[i].split(":");
  503. const fieldValue = fieldValueParts.join(":");
  504. if (fieldValue) {
  505. info[fieldName] = fieldValue;
  506. }
  507. }
  508. if (!info.loading || info.loading === "0") {
  509. callback(null, info);
  510. }
  511. else {
  512. const loadingEtaMs = (info.loading_eta_seconds || 1) * 1000;
  513. const retryTime = _this.options.maxLoadingRetryTime &&
  514. _this.options.maxLoadingRetryTime < loadingEtaMs
  515. ? _this.options.maxLoadingRetryTime
  516. : loadingEtaMs;
  517. debug("Redis server still loading, trying again in " + retryTime + "ms");
  518. setTimeout(function () {
  519. _this._readyCheck(callback);
  520. }, retryTime);
  521. }
  522. });
  523. };
  524. /**
  525. * Emit only when there's at least one listener.
  526. *
  527. * @param {string} eventName - Event to emit
  528. * @param {...*} arguments - Arguments
  529. * @return {boolean} Returns true if event had listeners, false otherwise.
  530. * @private
  531. */
  532. Redis.prototype.silentEmit = function (eventName) {
  533. let error;
  534. if (eventName === "error") {
  535. error = arguments[1];
  536. if (this.status === "end") {
  537. return;
  538. }
  539. if (this.manuallyClosing) {
  540. // ignore connection related errors when manually disconnecting
  541. if (error instanceof Error &&
  542. (error.message === utils_1.CONNECTION_CLOSED_ERROR_MSG ||
  543. // @ts-ignore
  544. error.syscall === "connect" ||
  545. // @ts-ignore
  546. error.syscall === "read")) {
  547. return;
  548. }
  549. }
  550. }
  551. if (this.listeners(eventName).length > 0) {
  552. return this.emit.apply(this, arguments);
  553. }
  554. if (error && error instanceof Error) {
  555. console.error("[ioredis] Unhandled error event:", error.stack);
  556. }
  557. return false;
  558. };
  559. /**
  560. * Listen for all requests received by the server in real time.
  561. *
  562. * This command will create a new connection to Redis and send a
  563. * MONITOR command via the new connection in order to avoid disturbing
  564. * the current connection.
  565. *
  566. * @param {function} [callback] The callback function. If omit, a promise will be returned.
  567. * @example
  568. * ```js
  569. * var redis = new Redis();
  570. * redis.monitor(function (err, monitor) {
  571. * // Entering monitoring mode.
  572. * monitor.on('monitor', function (time, args, source, database) {
  573. * console.log(time + ": " + util.inspect(args));
  574. * });
  575. * });
  576. *
  577. * // supports promise as well as other commands
  578. * redis.monitor().then(function (monitor) {
  579. * monitor.on('monitor', function (time, args, source, database) {
  580. * console.log(time + ": " + util.inspect(args));
  581. * });
  582. * });
  583. * ```
  584. * @public
  585. */
  586. Redis.prototype.monitor = function (callback) {
  587. const monitorInstance = this.duplicate({
  588. monitor: true,
  589. lazyConnect: false,
  590. });
  591. const Promise = PromiseContainer.get();
  592. return standard_as_callback_1.default(new Promise(function (resolve) {
  593. monitorInstance.once("monitoring", function () {
  594. resolve(monitorInstance);
  595. });
  596. }), callback);
  597. };
  598. transaction_1.addTransactionSupport(Redis.prototype);
  599. /**
  600. * Send a command to Redis
  601. *
  602. * This method is used internally by the `Redis#set`, `Redis#lpush` etc.
  603. * Most of the time you won't invoke this method directly.
  604. * However when you want to send a command that is not supported by ioredis yet,
  605. * this command will be useful.
  606. *
  607. * @method sendCommand
  608. * @memberOf Redis#
  609. * @param {Command} command - The Command instance to send.
  610. * @see {@link Command}
  611. * @example
  612. * ```js
  613. * var redis = new Redis();
  614. *
  615. * // Use callback
  616. * var get = new Command('get', ['foo'], 'utf8', function (err, result) {
  617. * console.log(result);
  618. * });
  619. * redis.sendCommand(get);
  620. *
  621. * // Use promise
  622. * var set = new Command('set', ['foo', 'bar'], 'utf8');
  623. * set.promise.then(function (result) {
  624. * console.log(result);
  625. * });
  626. * redis.sendCommand(set);
  627. * ```
  628. * @private
  629. */
  630. Redis.prototype.sendCommand = function (command, stream) {
  631. if (this.status === "wait") {
  632. this.connect().catch(lodash_1.noop);
  633. }
  634. if (this.status === "end") {
  635. command.reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
  636. return command.promise;
  637. }
  638. if (this.condition.subscriber &&
  639. !command_1.default.checkFlag("VALID_IN_SUBSCRIBER_MODE", command.name)) {
  640. command.reject(new Error("Connection in subscriber mode, only subscriber commands may be used"));
  641. return command.promise;
  642. }
  643. if (typeof this.options.commandTimeout === "number") {
  644. command.setTimeout(this.options.commandTimeout);
  645. }
  646. if (command.name === "quit") {
  647. this.clearAddedScriptHashesCleanInterval();
  648. }
  649. let writable = this.status === "ready" ||
  650. (!stream &&
  651. this.status === "connect" &&
  652. commands.exists(command.name) &&
  653. commands.hasFlag(command.name, "loading"));
  654. if (!this.stream) {
  655. writable = false;
  656. }
  657. else if (!this.stream.writable) {
  658. writable = false;
  659. }
  660. else if (this.stream._writableState && this.stream._writableState.ended) {
  661. // https://github.com/iojs/io.js/pull/1217
  662. writable = false;
  663. }
  664. if (!writable && !this.options.enableOfflineQueue) {
  665. command.reject(new Error("Stream isn't writeable and enableOfflineQueue options is false"));
  666. return command.promise;
  667. }
  668. if (!writable && command.name === "quit" && this.offlineQueue.length === 0) {
  669. this.disconnect();
  670. command.resolve(Buffer.from("OK"));
  671. return command.promise;
  672. }
  673. if (writable) {
  674. // @ts-ignore
  675. if (debug.enabled) {
  676. debug("write command[%s]: %d -> %s(%o)", this._getDescription(), this.condition.select, command.name, command.args);
  677. }
  678. (stream || this.stream).write(command.toWritable());
  679. this.commandQueue.push({
  680. command: command,
  681. stream: stream,
  682. select: this.condition.select,
  683. });
  684. if (command_1.default.checkFlag("WILL_DISCONNECT", command.name)) {
  685. this.manuallyClosing = true;
  686. }
  687. }
  688. else if (this.options.enableOfflineQueue) {
  689. // @ts-ignore
  690. if (debug.enabled) {
  691. debug("queue command[%s]: %d -> %s(%o)", this._getDescription(), this.condition.select, command.name, command.args);
  692. }
  693. this.offlineQueue.push({
  694. command: command,
  695. stream: stream,
  696. select: this.condition.select,
  697. });
  698. }
  699. if (command.name === "select" && utils_1.isInt(command.args[0])) {
  700. const db = parseInt(command.args[0], 10);
  701. if (this.condition.select !== db) {
  702. this.condition.select = db;
  703. this.emit("select", db);
  704. debug("switch to db [%d]", this.condition.select);
  705. }
  706. }
  707. return command.promise;
  708. };
  709. /**
  710. * Get description of the connection. Used for debugging.
  711. * @private
  712. */
  713. Redis.prototype._getDescription = function () {
  714. let description;
  715. if (this.options.path) {
  716. description = this.options.path;
  717. }
  718. else if (this.stream &&
  719. this.stream.remoteAddress &&
  720. this.stream.remotePort) {
  721. description = this.stream.remoteAddress + ":" + this.stream.remotePort;
  722. }
  723. else {
  724. description = this.options.host + ":" + this.options.port;
  725. }
  726. if (this.options.connectionName) {
  727. description += ` (${this.options.connectionName})`;
  728. }
  729. return description;
  730. };
  731. [
  732. "scan",
  733. "sscan",
  734. "hscan",
  735. "zscan",
  736. "scanBuffer",
  737. "sscanBuffer",
  738. "hscanBuffer",
  739. "zscanBuffer",
  740. ].forEach(function (command) {
  741. Redis.prototype[command + "Stream"] = function (key, options) {
  742. if (command === "scan" || command === "scanBuffer") {
  743. options = key;
  744. key = null;
  745. }
  746. return new ScanStream_1.default(lodash_1.defaults({
  747. objectMode: true,
  748. key: key,
  749. redis: this,
  750. command: command,
  751. }, options));
  752. };
  753. });