My apologies beforehand, because I'm finding it a bit difficult to explain the question at hand, so I'll divide it in parts.
I'm trying to find the best way to trigger retries based on a failed final retry attempt from another function (retry of a series of retries). What I'm thinking would work is to put something like throw new Err('Forfeit') within the else statement of each retry function (see codes for 1 and 2), then have the original call retry based on that (since there's a condition when it reaches the max attempt). Will that work? How would you approach it?
I created my own retry function below (n=3 attempts):
function retryFetchAttempt(attempt, maxAttempts, myFunction, error) { if (attempt < maxAttempts) { setTimeout(function() { myFunction() .then(result => console.log('Retry: ', attempt)) .catch(err => { console.log(err); attempt += 1; retryFetchAttempt(attempt, maxAttempts, myFunction, error) } ) }, 2000 ** attempt); } else { console.log('Forfeited at retry: ', attempt, error); // throw new Err('Forfeit'); ???? } } module.exports = { retryFetchAttempt }
I created another retry for each email being sent (10 attempts).
const {google} = require('googleapis'); const nodemailer = require('nodemailer'); function retryEmail(attempt, sender, emailRecipients, bccRecipients, getSubject, htmlOutput, refreshToken, clientid, client_secret, REDIRECT_URI) { const maxRetries = 10; if (attempt < maxRetries) { setTimeout(function() { sendEmail( sender, emailRecipients, bccRecipients, getSubject, htmlOutput, refreshToken, clientid, client_secret, REDIRECT_URI ).then(result => console.log('Email sent after retry: ', attempt, result)) .catch(err => { console.log(err); attempt += 1; retryEmail( attempt, sender, emailRecipients, bccRecipients, getSubject, htmlOutput, refreshToken, clientid, client_secret, REDIRECT_URI) } ) }, 3000); } else { console.log('Forfeited at retry: ', attempt); // throw new Err('Forfeit'); ???? } } async function sendEmail(sender, emailRecipients, bccRecipients, getSubject, htmlOutput, refreshToken, clientid, client_secret, REDIRECT_URI) { try { const oAuth2Client = new google.auth.OAuth2(clientid, client_secret, REDIRECT_URI); oAuth2Client.setCredentials({refresh_token: refreshToken}); const accessToken = await oAuth2Client.getAccessToken(); let smtpConfigWithToken = { host: 'smtp.gmail.com', port: 465, secure: true, //pool: true, auth: { type: 'OAuth2', user: sender, clientId: clientid, clientSecret: client_secret, refreshToken: accessToken.res.data.refresh_token, accessToken: accessToken.res.data.accessToken } }; let transporter = nodemailer.createTransport(smtpConfigWithToken); let HelperOptions = { from: sender, to: emailRecipients, bcc: bccRecipients, subject: getSubject, html: htmlOutput }; const result = transporter.sendMail(HelperOptions); return result; } catch(err) { return err; } } module.exports = { sendEmail, retryEmail }
I have a node cron job that triggers Mon-Fri at 11:30am UTC - 300. This job uses node-fetch to call another route that queries the database. That fetch call uses the 3 attempts retry. See the code below:
// CRON schedule to run MON-FRI at 11:30am UTC - 300 // Reminder of returning equipment 7 days after being loaned // Also removes the 'snooze' status after 7 days (if snooze was applied)
cron.schedule('30 11 * * 1,2,3,4,5', () => { let retryAttempt = 0; const maxAttempts = 3; async function scheduler() { try { const fetchResult = await fetch(process.env.REDIRECT_URI + config.route1, { method: 'GET', headers: { 'Content-Type': 'application/json', 'auth-token': process.env.API_TOKEN }, }); const response = await fetchResult; const jsonData = await response.json(); console.log(jsonData) } catch(e) { console.log('') } } scheduler() .then(sch => console.log('Fetch Email reminder')) .catch(err => retryFetchAttempt(retryAttempt, maxAttempts, scheduler, '')) });
The following route is called by the 1st fetch call. This one generates a call forEach DB record that matches the parameters. Then, it'll trigger a reminder via email. See the code below:
// Route used by cron at 11:30am (Mon-Fri) to send email reminders about non-returned equipment
router.get(config.route1, (req, res) => { let retryAttempt = 0; const maxAttempts = 3; async function fetchEachScheduled() { try { await postgres('metsupply').select('*').then(data => { if (data[0] !== undefined) { data.forEach(async (record, index) => { try { setTimeout(async function() { try { const fetchResult = await fetch(process.env.REDIRECT_URI + config.route2, { method: 'POST', headers: { 'Content-Type': 'application/json', 'auth-token': process.env.API_TOKEN }, body: JSON.stringify({ record: record, index: index }) }); const response = await fetchResult; const jsonData = await response.json(); console.log(jsonData) } catch(e) { console.log(e) console.log(record) } }, 1000 * index) } catch(e) { console.log(e) } }) } else { console.log(`Job finished at ${new Date().toString().split(" GMT",1)} -- No data`) } res.json('Closed query'); }) } catch(e) { console.log(e) } } fetchEachScheduled() .then(sch => console.log('Email reminder started')) .catch(err => retryFetchAttempt(retryAttempt, maxAttempts, fetchEachScheduled, '')) })
Finally (yes... Finally!). The function below processes the forEach call.
router.post(config.route2, async (req, res) => { try { const { record, index } = req.body; const currentDate = new Date(); const currentDateString = currentDate.toString().split(" GMT",1); let retryAttempt = 0; const recordDate = new Date(record.jsdate.toString()); recordDate.setDate(recordDate.getDate() + 1); const output = emailTemplates.supplyReminder(record); const sender = process.env.SUPPLY_SENDER; const emailRecipients = config.emailRecipient; const bccRecipients = []; const getSubject = 'Return equipment reminder'; const refreshToken = process.env.SUPPLY_REFRESH_TOKEN; const clientid = process.env.SUPPLY_CLIENT_ID; const client_secret = process.env.SUPPLY_CLIENT_SECRET; const REDIRECT_URI = process.env.REDIRECT_URI; if ((currentDate >= recordDate) && (record.returned === 'No') && (record.needtoreplace === 'No') && (record.requesttype === 'Loan') && (record.snooze === 'No') && (record.notificationsoff === 'No')) { setTimeout(async function() { await sendEmail( sender, emailRecipients, bccRecipients, getSubject, output, refreshToken, clientid, client_secret, REDIRECT_URI ).then(async result => { //console.log('Email sent...', result); }) .catch(err => { retryAttempt += 1; retryEmail( retryAttempt, sender, emailRecipients, bccRecipients, getSubject, output, refreshToken, clientid, client_secret, REDIRECT_URI ); } ) }, 1000); postgres(config.maindb).update('emailcount', record.emailcount + 1).where('requestid', record.requestid).then(async counter => { res.json(`Job finished at ${new Date().toString().split(" GMT",1)} -- Sending Email`) }).catch(e => console.log(e)) } if ((currentDate < recordDate) && (record.returned === 'No') && (record.needtoreplace === 'No') && (record.requesttype === 'Loan') && (record.snooze === 'No') && (record.notificationsoff === 'No')) { res.json(`Job finished at ${new Date().toString().split(" GMT",1)} -- Nothing to send -- Time is not up`) } if ((record.snooze === 'Yes') && (new Date().getTime() - new Date(record.lastmodified).getTime() >= (1000 * 60 * 60 * 24 * 7))) { await postgres(config.maindb).update({ emailcount: record.emailcount + 1, snooze: 'No' }).where('requestid', record.requestid) .then(counter => { res.json(`Job finished at ${new Date().toString().split(" GMT",1)} -- Nothing to send -- Snooze: ${new Date().getTime() - new Date(record.lastmodified).getTime()}`) }) .catch(e => console.log(e)) } if ((record.returned === 'Yes') || (record.needtoreplace === 'Yes') || (record.notificationsoff === 'Yes') || (record.requesttype === 'Replace')) { res.json(`Job finished at ${new Date().toString().split(" GMT",1)} -- Nothing to send`) } } catch(e) { console.log(e) } })