Is it possible to execute code after responding on a Cloudflare Worker?

I am starting workig with Cloudflare and I a m facing one of my first challenges.

I have some cached data, that I retrieve once I get it requested via a 'fetch' event. I would like after responding to this fetch, update the cache data that was requested.

I get this data from a third party API.

The workflow would be the following.

  1. Get the request and respond with cthe old cached item
  2. Refresh the cache for the requested item afterwards

I was thinking about using a cron timers and filling up a queue of items to update, but I think this list will not be persisted.

The ideal way would be having some sort of custom event I can register and fire just before I return the response, so while I am returning the old cached data the refresh cache job is already executing.

My current code is working (2 is not implemented yet), and I am not posting code, because I just would like to know some theories about how could this refresh be done.

Thanks in advance!


Solution 1:

If you had the typical handler of a worker deployed in service-worker mode:

addEventListener("fetch", (event) => {
    event.respondWith(
        handleRequest(event).catch(
            (err) => new Response(err.stack, { status: 500 })
        )
    );
});

And in that function handleRequest you checked if there was a cached response already, defaulting to a dummy response if there isn't

async function handleRequest(event) {
    let response = await caches.default.match(event.request)

    response = response ? new Response(response.body, response) : new Response('not found on cache ' + Date.now())
    response.headers.set('response-datetime', new Date().toISOString())
    return response
}

(For the sake of this example I'm creating a new, modifiable response from the cached one, if any)

FetchEvent.waitUntil is a method that expects a promise as an argument. You wouldn't need to await for event.waitUntil nor its argument to resolve. It's just as if you did

fetch(<url>).then(()=>console.log('fetch resolved'))
return response

or

setTimeout(()=>console.log('timeout callback'),500)
return response

Where the previous function invocation won't delay the response whatsoever. The difference is that calling

 event.waitUntil(getFreshInfoFromSomewhere(event.request))

Will extend the worker's lifetime beyond the response, up until its promise argument is resolved (but still subject to the runtime time limits, of course).

The function in charge of getting a fresh response it's up to you, I made one that puts a dummy response in the cache, 500ms after being requested:

/**
 * @param {Request} request 
 * @returns {<Promise<void>>}
 */
function getFreshInfoFromSomewhere(request) {
  return new Promise(resolve => {
    let response = new Response('response-cached-on ' + new Date().toLocaleTimeString())
    setTimeout(() => caches.default.put(request, response).then(resolve), 500)
  })
}

/**
 * @param {FetchEvent} event 
 * @returns <Promise<Response>>
 */
async function handleRequest(event) {
  let response = await caches.default.match(event.request),
    currentDatetime = new Date().toLocaleTimeString()
  event.waitUntil(getFreshInfoFromSomewhere(event.request))
  response = response ? new Response((await response.text()) + ', current datetime is ' + currentDatetime, response) : new Response('not found on cache at ' + currentDatetime)
  response.headers.set('response-datetime', currentDatetime)
  response.headers.set('cache-control', 'no-cache')
  return response
}


addEventListener("fetch", (event) => {
  event.respondWith(
    handleRequest(event).catch(
      (err) => new Response(err.stack, {
        status: 500
      })
    )
  );
});

You can see it working at https://examples.ffflabs.com/cached

(it's a very inneficient implementation, I needed to emphasize the underlying timeline)

Solution 2:

Put a promise in waitUntil and then return your response, for more information see: https://developers.cloudflare.com/workers/runtime-apis/fetch-event#waituntil