diff --git a/lib/redis/index.ts b/lib/redis/index.ts index 1cf12325..0775c81b 100644 --- a/lib/redis/index.ts +++ b/lib/redis/index.ts @@ -304,41 +304,45 @@ Redis.prototype.connect = function (callback) { stream.setKeepAlive(true, options.keepAlive); } - stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this)); + if (stream.connecting) { + stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this)); + + if (options.connectTimeout) { + /* + * Typically, Socket#setTimeout(0) will clear the timer + * set before. However, in some platforms (Electron 3.x~4.x), + * the timer will not be cleared. So we introduce a variable here. + * + * See https://github.com/electron/electron/issues/14915 + */ + let connectTimeoutCleared = false; + stream.setTimeout(options.connectTimeout, function () { + if (connectTimeoutCleared) { + return; + } + stream.setTimeout(0); + stream.destroy(); + + const err = new Error("connect ETIMEDOUT"); + // @ts-ignore + err.errorno = "ETIMEDOUT"; + // @ts-ignore + err.code = "ETIMEDOUT"; + // @ts-ignore + err.syscall = "connect"; + eventHandler.errorHandler(_this)(err); + }); + stream.once(CONNECT_EVENT, function () { + connectTimeoutCleared = true; + stream.setTimeout(0); + }); + } + } else { + process.nextTick(eventHandler.connectHandler(_this)); + } stream.once("error", eventHandler.errorHandler(_this)); stream.once("close", eventHandler.closeHandler(_this)); - if (options.connectTimeout) { - /* - * Typically, Socket#setTimeout(0) will clear the timer - * set before. However, in some platforms (Electron 3.x~4.x), - * the timer will not be cleared. So we introduce a variable here. - * - * See https://github.com/electron/electron/issues/14915 - */ - let connectTimeoutCleared = false; - stream.setTimeout(options.connectTimeout, function () { - if (connectTimeoutCleared) { - return; - } - stream.setTimeout(0); - stream.destroy(); - - const err = new Error("connect ETIMEDOUT"); - // @ts-ignore - err.errorno = "ETIMEDOUT"; - // @ts-ignore - err.code = "ETIMEDOUT"; - // @ts-ignore - err.syscall = "connect"; - eventHandler.errorHandler(_this)(err); - }); - stream.once(CONNECT_EVENT, function () { - connectTimeoutCleared = true; - stream.setTimeout(0); - }); - } - if (options.noDelay) { stream.setNoDelay(true); } diff --git a/test/functional/connection.ts b/test/functional/connection.ts index c8edf79f..78f48893 100644 --- a/test/functional/connection.ts +++ b/test/functional/connection.ts @@ -4,6 +4,7 @@ import * as sinon from "sinon"; import { expect } from "chai"; import MockServer from "../helpers/mock_server"; import * as Bluebird from "bluebird"; +import { StandaloneConnector } from "../../lib/connectors"; describe("connection", function () { it('should emit "connect" when connected', function (done) { @@ -423,5 +424,31 @@ describe("connection", function () { done(); }); }); + + it("works when connection established before promise is resolved", (done) => { + const socket = new net.Socket(); + sinon.stub(StandaloneConnector.prototype, "connect").resolves(socket); + socket.connect(6379, "127.0.0.1").on("connect", () => { + new Redis().on("connect", () => done()); + }); + }); + + it("ignores connectTimeout when connection established before promise is resolved", (done) => { + const socketSetTimeoutSpy = sinon.spy(net.Socket.prototype, "setTimeout"); + const socket = new net.Socket(); + sinon.stub(StandaloneConnector.prototype, "connect").resolves(socket); + socket.connect(6379, "127.0.0.1").on("connect", () => { + const redis = new Redis({ + connectTimeout: 1, + }); + redis.on("error", () => + done(new Error("Connect timeout should not have been called")) + ); + redis.on("connect", () => { + expect(socketSetTimeoutSpy.callCount).to.eql(0); + done(); + }); + }); + }); }); });