Promise not resolving after connection completes

I have a mongoose connect function in which I try to wait for reconnecting if the first attempt fails:

async connect() {
    const options = {...}
    try {
        console.log("starting")
        await this._connectWithRetry(options)
        console.log("finished")
    } catch (err) {
        winston.error(`Could not connect to Mongo with error: ${err}`)
    }
}

private async _connectWithRetry(options) {
    return new Promise( async (resolve, reject) => {
        try {
            winston.info("Connecting to mongo...")
            await mongoose.connect(this.dbURI, options)
            winston.info("Connection successful.")
            resolve()
        } catch (err) {
            winston.info("Failed to connect to mongo. Retrying in 5 seconds...")
            setTimeout( async () => {
                await this._connectWithRetry(options)
            }, 5000)
        }
    })
}

It successfully waits until I'm connected. But once I connect, the second console line is not hit ("finished"). so I figure that my promise resolution is buggy. What am I doing wrong?

1 answer

  • answered 2020-01-17 21:03 52d6c6af

    Your code "works" if the connection to the DB is established first time around.

    If the retry mechanism is used, you will see the error you describe.

    The Promise instantiated by the first call to mongoDBConnect is never resolved in the retry execution path.

    This is because subsequent invocations of mongoDBConnect are made in a totally separate execution context on a future tick of the event loop, controlled by the setTimeout - and each invocation instantiates a new Promise totally disconnected from your connect function.

    This refactoring should fix the issue:

    const delay = (interval) => new Promise(resolve => setTimeout(resolve, interval))
    
    async connect() {
        const options = {...}
        try {
            console.log("starting")
            await this._connectWithRetry(options)
            console.log("finished")
        } catch (err) {
            winston.error(`Could not connect to Mongo with error: ${err}`)
        }
    }
    
    private async _connectWithRetry(options) {
        try {
            winston.info("Connecting to mongo...")
            await mongoose.connect(this.dbURI, options)
            winston.info("Connection successful.")
        } catch (err) {
            winston.info("Failed to connect to mongo. Retrying in 5 seconds...")
            await delay(5000)
            await this._connectWithRetry(options)
        }
    }
    

    Test harness:

    let retryCount = 0
    
    const mongoose = {
        connect: ()=>retryCount++ === 2 ? Promise.resolve() : Promise.reject('fake error')
    }
    
    async function connect() {
        try {
            console.log("starting")
            await connectWithRetry()
            console.log("finished")
        } catch (err) {
            console.error(`connect error`, err)
        }
    }
    
    async function connectWithRetry() {
        try {
            console.log("Connecting to mongo...")
            await mongoose.connect()
            console.log("Connection successful.")
        } catch (err) {
            console.log("Retrying in 1 second...", err)
            await delay(1000)
            await connectWithRetry()
        }
    }
    
    const delay = (interval) => new Promise(resolve => setTimeout(resolve, interval))
    
    connect()