Retrying async function for 15 seconds on AWS Lambda

Viewed 29

I'm fetching data from a website but the process sometimes fails so I make the function to retry it. But I'm using a 15 seconds timeout, so when the timeout is triggered it will stop retrying the function and it will return some error message from the website.

This works fine in my local machine but when I deploy my code to Vercel (running on AWS Lambda as far as I know), the setTimeout is being completely ignored, so the fetchData function keeps running until it gets the correct response or until the server default 60 seconds timeout triggers.

Here's the code:

router.get('/test', async (req, res) => {
    try {
        const browser = await playwright.launchChromium({ 
        headless: false });
        const context = await browser.newContext();
        const page = await context.newPage();

        let timeout = false;

        let fetchDataTimeout = setTimeout(() => {
        timeout = true;
        }, 15000);

        const fetchData = async () => {
            await page.type('#code', '6824498040');
            let data = await (
                await Promise.all([
                    page.waitForResponse(
                        (response) =>
                            response.url() === `${env.API_URL2}` && response.status() === 200,
                        { timeout: 10000 },
                    ),
                    page.click('#btn-check'),
                ])
            )[0].json();
            if (data.errors && !timeout) {
                await page.reload();
                return await fetchData();
            } else {
                clearTimeout(fetchDataTimeout);
                return data;
            }
        };
        let data = await fetchData();
        await browser.close();

        res.json({
            status: 200,
            message: data,
        });
    } catch (error) {
        console.error(error);
        return res.status(500).send({ 'Server Error': `${error}` });
    }
});

I read that you have to wrap the setTimeout into a Promise and return it as an async function. Like this:

  const timeOut = async (t) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Completed in ${t}`)
    }, t)
  })
}

await timeOut(15000).then((result) => console.log(result))

But this will trigger the 15 seconds wait always. I need to discard the waiting if I get the correct response from fetchData and trigger the timeout if I don't 15 seconds after I start trying.

Any ideas???

1 Answers

The solution was quite simple. Since AWS Lambda requires an async function to properly wait the timeouts, I just had to wrap the timeout in a Promise. But then what did the trick was wrapping both functions, the timeout and the fetchData, in a Promise.race() method. By doing this, the promise that resolves first will stop the other one from running.

The code is now like this:

router.get('/test', async (req, res) => {
    try {
        const browser = await playwright.launchChromium({headless: false });
        const context = await browser.newContext();
        const page = await context.newPage();

        const timeout = () =>
            new Promise((resolve, reject) => {
                setTimeout(() => {
                     reject('FUNCTION TIMEOUT');
            }, 12000);
        });

        const fetchData = async () => {
            await page.type('#code', '6824498040');
            let response = await (
                await Promise.all([
                    page.waitForResponse(
                        (res) =>
                            res.url() === `${env.API_URL2}` && res.status() === 200,
                        { timeout: 10000 },
                    ),
                    page.click('#btn-check'),
                ])
            )[0].json();
            if (data.error) {
                await page.reload();
                return await fetchData();
            } else return data;
            }
        };
        let data = await Promise.race([fetchData(), timeout()]);
        await browser.close();

        res.json({
            status: 200,
            message: data,
        });
    } catch (error) {
        console.error(error);
        return res.status(500).send({ 'Server Error': `${error}` });
    }
});

I implemented a 12 seconds timeout instand of 15. I tested this on Vercel (AWS Lambda) and it works fine.

Thanks everybody, especially dandavis, this implementation was his idea.

Anyway, I hope it helps.

Related