A couple of years ago, I began using a Cloudflare worker to track my Googlebot traffic in Google Analytics. The initial code I used, and thought process to get there, is here, but here's what my code looks like today:

const analyticsId = 'UA-98289-3'

addEventListener('fetch', event => {
    event.passThroughOnException()
    event.respondWith(handleRequest(event))
})

/**
 * Check request object for Googlebot UA to send tracking data
 * @param {Event} event
 */
async function handleRequest(event) {

    let request = event.request
    let userAgent = request.headers.get('user-agent')
    let response = await fetch(request)

    if (userAgent)
    {
        // If Googlebot, then track hit in Analytics
        if (userAgent.includes('Google')) {

            // Search html
            let clone = await response.clone()
            let html = await clone.text()
            let indexableStatus = html.includes('<meta name="robots" content="noindex">') ? 'noindex' : 'indexable'

            event.waitUntil(analyticsHit(
                {
                    // uip: request.headers.get('CF-Connecting-IP'),
                    dl: request.url,
                    dt:
                        response.status + ' ' +
                        response.statusText + ' ' +
                        indexableStatus + ' ' +
                        userAgent
                }
            ))
        }
    }

    // Return the original content
    return response

}

/**
 * Send bot tracking data using Analytics Measurement Protocol
 * @param {Object} tracking
 */
function analyticsHit(tracking) {
    let payload = 'v=1&t=pageview&tid=' + analyticsId

    for(var key in tracking) {
        payload += '&' + key + '=' + encodeURIComponent(tracking[key])
    }

    payload += '&cid=' + [ Math.round(Math.random() * 2147483647), Math.round(new Date() / 1000.0) ].join('.')

    return fetch('https://www.google-analytics.com/collect', {
        method: 'POST',
        body: payload
    })
}

Note this is in a Cloudflare worker, so it executes at the edge. Now with Google Analytics being retired, and GA4 not supporting API usage in this way, I'm looking to freshen up this code a bit. However, I'm a javascript n00b. I pieced this code together from bits and snippets of using await async that I found on the web, and now I'm realizing that I'm not 100% sure that I'm doing it right. It's my goal to not delay my website's page load time while it sends off an API code to Google tracking. And yet I have event.waitUntil(analyticsHit() ... Is it correct that I am using waitUntil here? Is that telling Cloudflare to not load the page until a response has been received?

/*
##################################################
First of all Does waitUntil block your response?
##################################################
The waitUntil do not block the response.
It accepts a Promise-based task which the Workers runtime will execute before the handler terminates but without blocking the response.
For example, this is ideal for caching responses or handling logging or sending out events and data.

In the code example below i tested the waitUntil.
I set the waitUntil with setTimeout for 10 seconds.
As you can see, and you can test it yourself, the waitUntil do not block the response.
the event.respondWith will return a response and the waitUntil will be executed 10 seconds after without blocking the response.
in the example below i use KV to capture the before and after waitUntil event time.

##################################################
*Just for curious people:
##################################################
Does cloudflare workers setTimeout has a limit?
First, i checked on the cloudflare workers docs but could not find an answer.
so i checked it manually myself.
From my checking i found out the limit is 30 seconds.
setTimeout events with more then 30 seconds did not work for me.
But this was a fast test, so please don't take it as granted, check yourself.
any way even with longer setTimeout the waitUntil will not block the reponse.
it will simply not run.

##################################################
For your main question:
##################################################
Is it correct that I am using waitUntil here? Is that telling Cloudflare to not load the page until a response has been received?
Yes it's correct.
I checked it and it's not blocking your response.
It's first send the response to the viewer, after it sends an event to google.
HOWERVER, cloudflare workers and wrangler moved to newer syntax with modules
https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/
i would recomned to rewrite your function with the newer syntax.
Again, your current function works and it's not blocking your response.
** you can remove the await from line 24: let clone = await response.clone()
** no need to use the await keyword here

Do not use the code below as alternative to your current code.
The code below is only for testing the waitUntil.
Your current code is not blocking the response

*/

/**
 * wait until cloud flare workers with setTimeout
 * @param {number} timer
 * @param {string} uuid
 */
const testWaitUntil = (timer,uuid) => {
  return new Promise(function (resolve) {
    setTimeout(async function () {
      const date = new Date().toLocaleString();
      await TEST.put(`${uuid}-2-after`, `${date}`);
      resolve(true);
    }, timer);
  });
}

/**
 * generate randome id
 * @param {string} length
 */
const makeid = (length) => {
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  const charactersLength = characters.length;
  let result = '';
  for (let i = 0; i < length; i++ ) {
      result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
}

/**
 * testing cloudflare default addEventListener with waitUntil
 */
addEventListener('fetch', event => {
  event.respondWith((async (event) =>{

    // set timer
    const timer = 10000;

    // create uuid
    const uuid = makeid(7);

    // create before stamp
    // before the waitUntil run
    const date = new Date().toLocaleString();
    await TEST.put(`${uuid}-1-before`, `${date}`);

    // create after stamp 
    // testing wait until
    // must return a promise
    // Does NOT block / wait
    event.waitUntil(testWaitUntil(timer,uuid));

    // set some json response
    const json = JSON.stringify({
      message: `hi!`,
      uuid: uuid,
      date: date
    }, null, 2);

    // set init
    const init = {
      headers: {
        'content-type': 'application/json;charset=UTF-8'
      }
    }

    // return response
    return new Response(json, init);

  })(event));
});
commented: Thank you for your detailed explanation! +34
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.