For loop to retry an async function (sequlize.authenticate) - javascript

I would like to have my microservice waiting until the database becomes available.
(I have a sidecar Cloud SQL proxy that needs some time for the database connection).
So I was thinking of writing a for loop that attempts a connect and retries after a defined time.
Currently, the code looks as follows, but it doesn't seem to wait before reconnecting.
private class Database {
static async connectDatabase() {
try {
const retries = 20;
const tryTimeout = async (currentTry, retries) => {
return new Promise((resolve) =>
setTimeout(function () {
logger.info(`Try: ${currentTry}, ${retries} retries left.`);
}, 1000));
};
for (let i = 1; i <= retries; i++) {
try {
// Establish database connection
await SequelizeConnection.authenticate()
.then(() => {
logger.info(
"*** Database connection has been established successfully."
);
})
.catch(async (err) => {
logger.info("Error in connect database function: ", err);
throw err;
});
await SeqzelizeConnectionHealthcheck.authenticate()
.then(() => {
logger.info(
"*** Database connection for healthcheck has been established successfully."
);
})
.catch(async (err) => {
logger.error(
"Error in connect database function for healthcheck: ",
err
);
throw err;
});
} catch (error) {
logger.error("Error in connectDB retry function");
await tryTimeout(i, retries - i);
}
}
} catch (error) {
logger.error("Error in connect database function: ", error);
}
}
}
I was thinking of creating a retry wrapper function and tried some retry libraries but without success.

As mentioned in comments, there are a few issues in the code. Mainly that you're mixing await with .then()/.catch(), not resolving the promise from the wait and not breaking out of the for loop when the connections are successful.
To illustrate this, I've reformatted your method a bit and extracted some functions so it's hopefully clearer. In the snippet below I'm simulating the database connection succeeding after 3 tries and the healthcheck database connection succeeding after 5 tries.
I've also changed the logic a bit so that it makes the main database and healthcheck database connections concurrently and retries them independently - you don't want to be retrying the main database connection again just because the healthcheck one failed, so for this I created a retryUntilResolved function that will retry the given function until it resolves, or until the maximum number of retries has been reached.
// Mocks purely 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();

You should call resolve() in setTimeout().
Please see below example. It is the code that assumes that authenticate() always fails.
async function connectDatabase() {
try {
const retries = 20;
const tryTimeout = async (currentTry, retries) => {
return new Promise((resolve) =>
setTimeout(function () {
console.log(`Try: ${currentTry}, ${retries} retries left.`);
resolve();
}, 1000));
};
for (let i = 1; i <= retries; i++) {
try {
// Establish database connection
await sequelizeConnection_authenticate()
.then(() => {
console.log(
"*** Database connection has been established successfully."
);
})
.catch(async (err) => {
console.log("Error in connect database function: ", err);
throw err;
});
await seqzelizeConnectionHealthcheck_authenticate()
.then(() => {
console.log(
"*** Database connection for healthcheck has been established successfully."
);
})
.catch(async (err) => {
console.log(
"Error in connect database function for healthcheck: ",
err
);
throw err;
});
} catch (error) {
console.log("Error in connectDB retry function");
await tryTimeout(i, retries - i);
}
}
} catch (error) {
console.log("Error in connect database function: ", error);
}
}
async function sequelizeConnection_authenticate() {
return new Promise((_, reject) => {
setTimeout(function () {
reject();
}, 1000);
});
}
async function seqzelizeConnectionHealthcheck_authenticate() {
return new Promise((_, reject) => {
setTimeout(function () {
reject();
}, 1000);
});
}
connectDatabase();

Related

Async Funtion Return recursive call with setTimeout

I am calling a smart contract function to get some status which is the same as calling an API. And, I need to check if status.fulfilled===true before returning it to front-end. To do this I need to call the API every second to return the result as soon as possible. It usually takes 5-20 seconds for it to be fulfilled.
Here is how i tried to do it:
async function getStatus(requestId) {
try {
await Moralis.enableWeb3({ provider: 'metamask' });
const options = {
contractAddress: coinFlipAddress,
functionName: 'getStatus',
abi: coinFlipABI,
params: { requestId },
};
var status = await Moralis.executeFunction(options);
console.log(status);
if (status.fulfilled) {
console.log('fulfilled');
return status;
} else {
setTimeout(async () => {
return await getStatus(requestId);
}, 1000);
}
} catch (err) {
console.log(err);
return { error: err };
}
}
This keeps calling the getStatus function recursively until status.fulfilled===trueand console.log('fulfilled'); also logs when it is fulfilled, but it doesn't return it to where It is first initialized.
const handleFlip = async (choice) => {
setCurrentChoice(null);
setMetamaskInProgress(true);
const transaction = await flip(choice, amount);
setMetamaskInProgress(false);
setCurrentChoice(choices[choice]);
setFlipping(true);
setResult('Spinning');
const requestId = waitForConfirmation(transaction);
const result = await getStatus(requestId); //This is the initial call to getStatus()
console.log('RESULT ' + result);
if (result) {
setFlipping(false);
setSide(result.hasWon ? (choice === '0' ? 'Heads' : 'Tails') : choice === '0' ? 'Tails' : 'Heads');
setResult(result.hasWon ? 'You have won!' : 'You have lost :(');
}
};
What am I doing wrong? Also, could this recursive calls create any problems with memory? If yes, do you have any suggestions to handle this case differently?
You cannot return from a setTimeout callback. You'll need to promisify that, wait, and return afterwards:
async function getStatus(requestId) {
try {
await Moralis.enableWeb3({ provider: 'metamask' });
const options = {
contractAddress: coinFlipAddress,
functionName: 'getStatus',
abi: coinFlipABI,
params: { requestId },
};
var status = await Moralis.executeFunction(options);
console.log(status);
if (status.fulfilled) {
console.log('fulfilled');
return status;
} else {
await new Promise(resolve => {
setTimeout(resolve, 1000);
});
return getStatus(requestId);
}
} catch (err) {
console.log(err);
return { error: err };
}
}
I would have done something like this :
const MAX_RETRIES = 10; //maximum retries
const REQUEST_DELAY = 1000; //delay between requests (milliseconds)
// JS Implementation of the wide known `sleep` function
const sleep = (time) => new Promise(res => setTimeout(res, time, "done."));
/**
* Retrieve the status
* #param {string} requestId
*/
const getStatus = async (requestId) => {
try {
await Moralis.enableWeb3({ provider: 'metamask' }); //Guessing you need to call this only once
const options = {
contractAddress: coinFlipAddress,
functionName: 'getStatus',
abi: coinFlipABI,
params: { requestId },
};
let retries = 0;
while(retries < MAX_RETRIES) {
let status = await Moralis.executeFunction(options); // check status
console.log('attemtp %d | status %s', retries, status);
if (status.fulfilled) {
return status
}
await sleep(REQUEST_DELAY);
retries++;
}
throw new Error('Unable to retrieve status in time');
} catch (error) {
console.error('Error while fetching status', error);
throw error;
}
}
Few notes here :
I took the constants out of the function for more clarity,
use the while to loop for the number of retries.
Used a widely known 'sleep' method to create a delay between requests, and throw an error whenever something's not supposed to happen, happens (up to you to edit according to your needs).
Finally I used arrow functions for simplicity of use, know it's up to you ;)
You could try this change maxRetry and spaceBetweenRetry as you wish
async function getStatus(requestId) {
return new Promise(async (resolve, reject) => {
try {
let maxRetry = 10; //how many times you want to retry
let spaceBetweenRetry = 1000; // sleep between retries in ms
await Moralis.enableWeb3({ provider: 'metamask' }); //Guessing you need to call this only once
const options = {
contractAddress: coinFlipAddress,
functionName: 'getStatus',
abi: coinFlipABI,
params: { requestId },
};
for (let index = 0; index < maxRetry; index++) {
var status = await Moralis.executeFunction(options); // check status
console.log(status);
if (status.fulfilled) {
resolve(status) //return the promise if fullfilled.
}
await new Promise((r) => setTimeout(r, spaceBetweenRetry)); // sleep for spaceBetweenRetry ms
}
} catch (err) {
reject(err)
}
})}

How can I abort a call to redis after a specific time?

I have a function where I would like to call specified redis methods which currently errors if it times out after a specified time in ms.
const redis = require('redis');
const client = redis.createClient({ url: process.env.REDIS_URL || 'http://localhost:6379'});
const callRedis = (fn, timeoutLimit, ...args) => new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(`Command timed out after ${timeoutLimit}ms`)), timeoutLimit);
client[fn](...args, (err, res) => {
clearTimeout(timer);
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
how can I adjust the above code to ensure the specified fn gets aborted once the timeoutLimit is reached?
I've found that I can create a new AbortController() which also gives me the signal property but I'm unsure how to use this with/in the callRedis() function?
EDIT: an example use of callRedis():
const { callRedis } = require('../redisService.js');
const ONE_SECOND = 1000;
try {
const someCachedValue = await callRedis('get', ONE_SECOND, 'someKey');
} catch (err) {
console.log('Redis call timed out!');
}

escape loop using an error from async function?

let me explain what I mean using an example
async function async_function(){
await new Promise(r=>setTimeout(r,3000));
throw 'task completed'
}
async function do_something_meanwhile() {
await new Promise(r => setTimeout(r, 500));
console.log(Math.floor(Math.random()*10));
}
(async ()=>{
try {
async_function(); //this returns an error after a while
while (...)
await do_something_meanwhile();
} catch (err) { console.log('exited with error:',err) }
console.log('moving on');
})();
I'm trying to run an async function and after it is complete immediately terminate the loop,
the best way I could think of (without any time delay) was to send an error
but it gives this error instead of moving on after it's done:
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing
inside of an async function without a catch block,
or by rejecting a promise which was not handled with
.catch(). The promise rejected with the reason "task
completed".] {
code: 'ERR_UNHANDLED_REJECTION'
}
is there a way around this or a better to achieve the desired effect?
You can handle rejection by setting an error variable that you can check in the loop:
try {
let error;
async_function()
.catch(err => error = err);
while (...) {
if (error) {
throw error;
}
await do_something_meanwhile();
}
} catch (err) {
console.log('exited with error:',err)
}
If you need to proactively tell do_something_meanwhile to terminate as well, you could use an AbortController and pass its signal to do_something_meanwhile.
try {
let error;
const controller = new AbortController();
const { signal } = controller;
async_function()
.catch(err => {
error = err;
controller.abort();
});
while (...) {
if (error) {
throw error;
}
await do_something_meanwhile(signal);
}
} catch (err) {
console.log('exited with error:',err)
}
I think if I were doing that, I might subclass AbortController so I can put the error in it:
class AbortContollerWithError extends AbortController {
abort(error) {
this.error = error;
super.abort();
}
}
then:
try {
const controller = new AbortController();
const { signal } = controller;
async_function()
.catch(err => {
controller.abort(err);
});
while (...) {
if (signal.aborted) {
throw controller.error;
}
await do_something_meanwhile(signal);
}
} catch (err) {
console.log('exited with error:',err)
}
...or something along those lines.
You asked how you'd use the signal in do_something_meanwhile, and suggested in a comment that you're really using a timer in it. That's where the signal's abort event comes in handy, you can use that to settle the promise early:
async function do_something_meanwhile(signal) {
let cancelError = {};
try {
await new Promise((resolve, reject) => {
const timer = setTimeout(resolve, 500);
signal.addEventListener("abort", () => {
clearTimeout(timer);
cancelError = new Error();
reject(cancelError);
});
});
console.log(Math.floor(Math.random() * 10));
} catch (error) {
if (error === cancelError) {
// Probably do nothing
} else {
// Something else went wrong, re-throw
throw error;
}
}
}
Promise.all can run async_function and do_something_meanwhile in parallel mode.
While Promise/A doesn't have a cancel method, you can define a stopFlag, and check it in do_something_meanwhile function and the while loop.
let stopFlag = false
async function async_function() {
await new Promise(r=>setTimeout(r, 3000));
throw 'task completed'
}
async function do_something_meanwhile() {
await new Promise(r => setTimeout(r, 500));
if (!stopFlag) {
console.log(Math.floor(Math.random() * 10));
}
}
(async()=>{
try {
await Promise.all([
async_function().catch((err) => {
stopFlag = true
throw err
}), // this returns an error after a while
(async () => {
while (!stopFlag)
await do_something_meanwhile();
})()
])
} catch (err) {
console.log('exited with error:', err)
}
console.log('moving on');
})();

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?
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()

Async await does not wait

I have the following in Typescript:
import sql = require("mssql");
const config: sql.config = {....
}
const connect = async() => {
return new Promise((resolve, reject) => {
new sql.ConnectionPool(config).connect((err) => {
if (err) {
reject(err);
} else {
console.log("CONNECTED");
resolve();
}
});
});
};
(async() => {
await connect().then(
() => {
console.log("Connection pool created successfully.");
}).catch((err) => {
console.error(err);
});
})();
console.log("Now proceeding to load...");
I always get the console output in the following order:
Now proceeding to load...
CONNECTED
Connection pool created successfully
What have I done wrong? How do I achieve executing the last line of code only after all the activities before it have completed execution?
You're calling the (async () => {... function, which is, well, asynchronous, and then directly proceed to print the loading message.
You're also mixing and matching .then().catch() and async/await/catch error handling – I'd refactor things like this:
import sql = require("mssql");
const connect: (config: sql.config) => Promise<sql.ConnectionPool> = async config =>
new Promise((resolve, reject) => {
const pool = new sql.ConnectionPool(config);
pool.connect(err => {
if (err) return reject(err);
console.log("CONNECTED");
resolve(pool);
});
});
const config: sql.config = {
/*...*/
};
(async () => {
console.log("Now proceeding to load...");
try {
const pool = await connect(config);
console.log("Connection pool created successfully.");
} catch (e) {
console.error(e);
return;
}
})();
Try this:
(async () => {
await connect();
console.log("Connection pool created successfully.");
})();
try something like below
import sql = require("mssql");
const config: sql.config = { /*....*/ };
const connect = () => {
return new Promise((resolve, reject) => {
try {
let Connection = await sql.ConnectionPool(config).connect();
console.log("Connected to mssql");
resolve("Successfully Connected");
} catch (error) {
reject(error);
}
});
};
(async function () {
try {
let Connection = await connect();
console.log("Connection pool created successfully.");
} catch (error) {
console.error(error);
}
}());

Categories