Looping to run an async process (sequilize.authenticate) multiple times until successful

I need my microservice to wait for the database to become available before proceeding. There is a sidecar Cloud SQL proxy involved that requires some time for the database connection.

My current approach involves a for loop that retries connecting after a defined interval, but it doesn't seem to wait properly before reconnecting.

class Database {
  static async connectDatabase() {
    try {
      const maxRetries = 20;

      const retryDelay = async (currentRetry, maxRetries) => {
        return new Promise((resolve) =>
          setTimeout(function () {
            logger.info(`Retry attempt: ${currentRetry}, ${maxRetries} retries left.`);
          }, 1000));
      };

      for (let i = 1; i <= maxRetries; i++) {
        try {
          // Establish database connection
          await SequelizeConnection.authenticate()
            .then(() => {
              logger.info("*** Database connection established successfully.");
            })
            .catch(async (err) => {
              logger.info("Error in connect database function: ", err);
              throw err;
            });
          await SeqzelizeConnectionHealthcheck.authenticate()
            .then(() => {
              logger.info("*** Healthcheck database connection established successfully.");
            })
            .catch(async (err) => {
              logger.error("Error in healthcheck connection function: ", err);
              throw err;
            });
        } catch (error) {
          logger.error("Error in connectDB retry function");
          await retryDelay(i, maxRetries - i);
        }
      }
    } catch (error) {
      logger.error("Error in connect database function: ", error);
    }
  }
}

I've attempted using retry libraries and creating a retry wrapper function, but haven't had success with either method.

Answer №1

After reviewing the comments, it's clear that there are several issues in the code. One major problem is the mixing of await with .then()/.catch(), failing to resolve the promise from the wait, and not properly handling successful connections within the for loop.

To address these issues, I have restructured your method and extracted some functions for better clarity. In the provided example below, I simulate a scenario where the main database connection succeeds after 3 attempts and the health check database connection succeeds after 5 attempts.

I've also adjusted the logic to establish both the main database and health check database connections concurrently while retrying them independently. This prevents unnecessary retries on the main database connection if the health check one fails. For this purpose, I introduced a retryUntilResolved function that will continue retrying the given function until it resolves or reaches the maximum number of retries.

// Mocks exclusively for snippet
const resolveNthTime = nth => {
  let callCount = 0;
  return () => ++callCount >= nth
    ? Promise.resolve()
    : Promise.reject('failed!');
}
const SequelizeConnection = { authenticate: resolveNthTime(3) };
const SequelizeConnectionHealthcheck = { authenticate: resolveNthTime(5) };


// Example
const pause = ms =>
  new Promise(resolve => setTimeout(resolve, ms));

const retryUntilResolved = (waitMs, maxRetries) => async func => {
  let tries = 0;

  while (true) {
    try {
      return await func();
    } catch(err) {
      if (tries++ < maxRetries) await pause(waitMs);
      else return err;
    }
  }
};


const authenticateDatabase = async () => {
  try {
    await SequelizeConnection.authenticate();
    console.info("Database connection established");
  } catch (err) {
    console.warn("Error connecting to database: ", err);
    throw err;
  }
};

const authenticateHealthcheck = async () => {
  try {
    await SequelizeConnectionHealthcheck.authenticate();
    console.info("Database connection for healthcheck established");
  } catch (err) {
    console.warn("Error connecting to healthcheck database: ", err);
    throw err;
  }
};

class Database {
  static async connectDatabase() {
    const maxRetries = 20;
    const msBeforeRetry = 1000;
    const retry = retryUntilResolved(msBeforeRetry, maxRetries);

    try {
      await Promise.all([
        retry(authenticateDatabase),
        retry(authenticateHealthcheck),
      ]);
      console.info('Both connections established');
    } catch (error) {
      console.error('Could not establish both connections');
    }
  }
}

Database.connectDatabase();

Answer №2

It is important to utilize resolve() within the context of a setTimeout() function. Below is an example code snippet that demonstrates this concept, assuming that the authenticate() function always fails.

async function establishConnectionWithDatabase() {
  try {
    const retryAttempts = 20;

    const attemptWithTimeout = async (currentAttempt, retriesLeft) => {
      return new Promise((resolve) =>
        setTimeout(function () {
          console.log(`Attempt: ${currentAttempt}, ${retriesLeft} attempts left.`);
          resolve();
        }, 1000));
    };

    for (let i = 1; i <= retryAttempts; i++) {
      try {
        // Establish the database connection
        await sequelizeAuthenticate()
          .then(() => {
            console.log(
              "*** Database connection has been successfully established."
            );
          })
          .catch(async (err) => {
            console.log("Error in establishing database connection: ", err);
            throw err;
          });
        await seqzelizeHealthcheckAuthenticate()
          .then(() => {
            console.log(
              "*** Database healthcheck connection has been successfully established."
            );
          })
          .catch(async (error) => {
            console.log(
              "Error in establishing database healthcheck connection: ",
              error
            );
            throw error;
          });
      } catch (exception) {
        console.log("Error in retrying database connection establishment");
        await attemptWithTimeout(i, retryAttempts - i);
      }
    }
  } catch (exception) {
    console.log("Error in establishing database connection: ", exception);
  }
}

async function sequelizeAuthenticate() {
  return new Promise((_, reject) => {
    setTimeout(function () {
      reject();
    }, 1000);
  });
}

async function seqzelizeHealthcheckAuthenticate() {
  return new Promise((_, reject) => {
    setTimeout(function () {
      reject();
    }, 1000);
  });
}

establishConnectionWithDatabase();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

implement a user input field within a div using JavaScript

I have created a lightbox in my HTML page with the following structure: <div id="container"> <input type="text" id="user_1" name="user_1" value="user_1"/><br> <input type="text" id="user_2" name="user_2" value="user_2"/>< ...

Displaying a horizontal scroll bar for legends in ECharts can be achieved by limiting the legend to three lines. If the legend items exceed this limit, they will scroll horizontally to accommodate all items

I am currently utilizing ECharts to display trend data in a line chart format. With 50 different series to showcase, each series comes with its own legend. My objective is to arrange the legends at the top of the chart, while limiting them to a maximum of ...

Updating lodash when it depends on jshint: A guide to NPM

After successfully passing an audit in npm, I received the following results: https://i.sstatic.net/ZueZ6.png Now, I am attempting to update my lodash package but I'm unsure of the correct method to do so. I attempted using npm -i --save lodash, how ...

Having trouble getting the innerHTML update to be triggered by onchange

Hey there, I am looking to tweak my file upload button so that it triggers a change in a specific span, signaling to the user that a file has indeed been selected. <script type="text/javascript"> function get_fileName(fileSelector, fileId) { var ...

Determine whether one class is a parent class of another class

I'm working with an array of classes (not objects) and I need to add new classes to the array only if a subclass is not already present. However, the current code is unable to achieve this since these are not initialized objects. import {A} from &apo ...

Issue with Ckeditor inside a bootstrap modal

I am encountering an issue while trying to integrate ckeditor into a bootstrap modal. Whenever I attempt to use it, the functionality does not work as expected. Clicking on any icons triggers an error in the console stating Uncaught TypeError: Cannot rea ...

Is there a way to point my Github URL to the index file within a specific folder?

The actual working location of my website: My desired working location for the site: Originally, I had my index.html file in the main repository, but later moved it to an html folder along with other html files for better organization. How can I ensure t ...

How can I retrieve x-data from an external file using Alpine.js?

I recently started exploring Alpine.js and grasped the fundamentals, but I'm facing challenges when trying to move functions outside of inline script tags. Consider this snippet from index.html: <div x-data="{ loading: false }"/> &l ...

Middleware designed for logging in React, akin to Multer in the realm of Express JS

Currently developing a React application that involves multiple API calls. I am exploring the possibility of integrating a middleware to intercept and log all these calls, similar to how Multer functions in an Express server. I am also curious if there is ...

Using React.js - Discover the best way to incorporate a function within a child component to unmount a different child component from the same parent, and then mount a new component in its place

Consider this scenario: import React, { Component } from 'react'; import BodyContent from './BodyContent'; import BottomOne from './BottomOne'; import BottomTwo from './BottomTwo'; class App extends Component { re ...

Connect data dynamically to the p-table based on columns

I'm facing a challenge in displaying JSON data in a table format, and I'm looking for a way to accomplish this using p-table. When looping through the data, I'm noticing duplicate records in the rows. Can anyone guide me on how to achieve th ...

Checking if a module is loaded through dynamic route code splitting

One issue with code splitting is that when the module is large, the initial loading time may result in a blank screen and delay for the user. function errorLoading(err) { console.error('Dynamic page loading failed', err); } fu ...

Extract the property value and save it as an array in Vue

Looking to extract all values of a specific property and save them as an array. I attempted the following method: data() { return { roomList: null } }, methods: { getRooms() { var that = this axios.get('http://local ...

Error in Next.js 11: Unable to access property 'canonicalBase' as it is undefined

Encountering an issue after upgrading from Next.js version 10.0.2 to 11.0.1. Since the update, I am unable to initiate a project due to the following error: Cannot read property 'canonicalBase' of undefined In my _app.tsx file, the Next imports ...

Incorporate a division based on the selection made from a jQuery dropdown menu

Is there a way to dynamically display a div to the right of a drop-down menu based on the user's selection using DOM manipulation? For reference, you can view an example of my current progress here: http://jsbin.com/#/afojid/1/edit The initial drop ...

Tips on integrating ActiveX controls in Angular

I built my project using Angular 6 and TypeScript in Visual Studio Code. The browser being used is IE11. Unfortunately, when I try to run the code written in app.component.html, it doesn't work as expected. The HTML code causing the issue is: <d ...

Using request-promise from npm, send a request with a self-generated certificate in JavaScript

When trying to make a simple request using the request-promise library, I encountered an error due to having a self-signed cert in my chain. This is a requirement that cannot be avoided. Fortunately, with NPM, I was able to specify the cert for installing ...

Exploring the differences between DDB.query and Raw Operation Object in CDK within AWS AppSync Resolver

Currently, I am in the midst of an AWS AppSync project utilizing CDK (Cloud Development Kit) to define my resolvers. While working on this project, I have stumbled upon two distinct approaches for writing DynamoDB query operations within my resolver functi ...

servlet failing to send a response to ajax call

I am facing an issue where the servlet is not sending back a response to my AJAX code. Can someone please help me with this? Below is the HTML code, the output should be printed here: This is the AJAX code written in JavaScript: <script language="jav ...

Automatically Populate Text Field with the URL of the Current Page

I am hoping to automatically fill a hidden text field with the URL of the current page so that I can keep track of where form submissions are coming from. This simple solution will help me streamline my inquiry responses and save time. To clarify, upon lo ...