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.
- Get the request and respond with cthe old cached item
- 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