jojopuf
jojopuf

Reputation: 53

Retry promise with delay until resolved in NodeJS

I have a connect() function which returns a promise (it is actually a method in a class - not shown). I want this function to retry with a delay if the connection is not established (i.e. when an error occurs in the connection attempt), but so far I could not make it work.

I tried using the async js library and promise-retry library to no avail - the documentation is hard to understand. For clarity, socket.connect emits a 'connect' event if a connection is established.

This is my code:

this.socket = new net.Socket();
this.client = new Modbus.client.TCP(this.socket, this.unitID);
const net = require('net');
const Modbus = require('jsmodbus');

connect() {
    return new Promise((resolve, reject) => {
        this.socket.connect(options);
        this.socket.on('connect', () => {
            logger.info('*****CONNECTION MADE*****');
            //does something once connection made
            resolve();
        });
        this.socket.on('error', (error) => {
            logger.error('failed to connect');
            this.disconnect();
            reject();
        });
    })
}

Upvotes: 5

Views: 5183

Answers (3)

T.J. Crowder
T.J. Crowder

Reputation: 1074148

I'd do it by making the function that does a single connection attempt (basically, renaming your connect to tryConnect or similar, perhaps even as a private method if you're using a new enough version of Node.js), and then having a function that calls it with the repeat and delay, something like this (see comments):

Utility function:

function delay(ms, value) {
    return new Promise(resolve => setTimeout(resolve, ms, value);
}

The new connect:

async connect() {
    for (let attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
        if (attempt > 0) {
            // Last attempt failed, wait a moment
            await delay(RETRY_DELAY_IN_MS);
        }
        try {
            await tryConnect();
            return; // It worked
        } catch {
        }
    }
    // Out of retries
    throw new Error("Couldn't create connection");
}

(If you're using a slightly older Node.js, you may need to add (e) after the catch above. Leaving it off when you don't need it is a relatively new feature.)


Re your current implementation of what I'm calling tryConnect, here are a few notes as comments for how I'd change it:

tryConnect() {
    return new Promise((resolve, reject) => {
        // Keep these local for now
        const socket = new net.Socket();
        const client = new Modbus.client.TCP(socket, this.unitID);

        // Add handlers before calling `connect
        socket.on('connect', () => {

            logger.info('*****CONNECTION MADE*****');

            // NOW save these to the instance and resolve the promise
            this.socket = socket;
            this.client = client;
            resolve();
        });

        socket.on('error', (error) => {
            logger.error('failed to connect');
            // It's not connected, so no `disconnect` call here
            reject();
        });

        socket.connect(options);
    });
}

Upvotes: 0

myestery
myestery

Reputation: 125

In the function where connect is called, It can be called recursively, eg

const outerFunction = (times = 0) => {
  if (times < 4) {
    socket
      .connect(params)
      .then(() => {
        // do good stuff
      })
      .catch(e => {
        // increment the times so that it wont run forever
        times++;
          setTimeout(() => {
            // delay for two seconds then try conecting again
          outerFunction(times);
        }, 2000);
      });
  }
};

This way your connect function is tried three times with a spacing of 2 seconds, i hope this solves your issue

Upvotes: 0

trincot
trincot

Reputation: 350137

First define a utility function for having the delay:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

Then chain a .catch handler to the new Promise:

.catch(() => delay(1000).then(() => this.connect()));

Of course, you should avoid an infinite series of retries. So implement some logic to definitely give up: after a fixed number of attempts, or after a certain time has passed, ...etc.

For instance, give a parameter to connect how many attempts it should allow:

connect(attempts=3) {

Then the catch handler could be:

.catch((err) => {
     if (--attempts <= 0) throw err; // give up
     return delay(1000).then(() => this.connect(attempts));
});

Upvotes: 3

Related Questions