MutableFetchEvent

I find myself often wanting to change the Request (maybe add a header or adjust the path). Since Requests are immutable, I need to replace the object:

myRequest = new Request('/new/path', fetchEvent.request)

But as soon as I do that, it becomes disconnected from the fetchEvent. Unfortunately, that, too, is immutable. So at the beginning of my worker, I wrap the FetchEvent in a MutableFetch event that makes it easy to manipulate the request during parsing:

class MutableFetchEvent {
  constructor(immutableFetchEvent) {
    this.immutableFetchEvent = immutableFetchEvent
    this.request = new Request(immutableFetchEvent.request)
  }

  waitUntil(promise) {
    this.immutableFetchEvent.waitUntil(promise)
  }
}

addEventListener('fetch', event => {
  event.respondWith(handle(new MutableFetchEvent(event))
})

async function handle(fetchEvent) {
  // this is now fine:
  fetchEvent.request = new Request(…)

  return fetch(fetchEvent.request)
}

So you basically wrote a wrapper class for “FetchEvent”?

Though, I am afraid I am not quite sure if I understand the purpose of it. What is wrong with simply using the approach you mentioned in the first line? Yes, you obviously cannot use event.request in that case but that shouldnt be an issue. Furthermore, even with this wrapper class, you still cannot modify the URL afterwards, without still creating a new request object.

I am just a bit baffled by what the point here is - no offence, maybe you can elaborate :slight_smile:

I’d be happy to elaborate.

The benefit to me is that I can change the request and pass it around attached to the event. I’ve broken my handler up into a bunch of different functions, each of which does one thing to the request and then passes it upstream.

Without MutableFetchEvent, you can certainly create a new request and pass the two objects around:

function removeAPIPrefix(request, event) {
  const adjustedRequest = new Request(request.url.replace(/^\/api\/, ''), request)
  return [adjustedRequest, event]
}

function logRequest(request, event) {
  event.waitUntil(() => {
    fetch('https://example.com/logging', {
      method: 'POST',
      body: {
        timestamp: new Date().toISOString()
        url: request.url,
      }
    })
  })
}

async function handle(event) {
  let request = request.event
  [request, event] = removeAPIPrefix(request, event)
  [request, event] = logRequest(request, event)
  return fetch(request)
}

addEventListener('fetch', event => event.respondWith(handle(event)))

It’s just a little awkward to pass those two around separately. If I saw this code, I would wonder whether to use the request argument or the event.request.

By wrapping them up in MutableFetchEvent, I pass around a thing that’s API-compatible with the original FetchEvent, but that I can change.

Alright, fair enough. Thanks for elaborating. I guess if someone actually needs the event object on a regular basis, that might be a fair approach.

Personally, I try to write all my code as immutable as possible, but this might actually be useful for quick and dirty request manipulations.

Thanks for sharing!

I’m a big fan of immutable programming as well! Unfortunately, there’s no way to clone or create a FetchEvent, so mutability is our only option. But if you prefer a more immutable style, you could move some of the “mutators” up to the FetchEvent wrapper:

class FetchEventWrapper {
  constructor(fromEvent) {
    this.fromEvent = fromEvent
  }

  url(url) {
    const e = new FetchEventWrapper(this.fromEvent)
    e.request = new Request(url, this.request)
    return e
  }

  // and likewise for headers and body
}