Client side API consumption may be stuck with old code due to caching

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the web-development category.

Last Updated: 2024-04-15

Background: My software uses JavaScript to update the flash messages of pages that use public caching rather using than HTML (so as to avoid caching the message and showing it to the wrong user).

In a refactor, I changed from sending an array of flash_messages...

def update_flash
  payload = {
    flash_messages: [flash_messages]
  }.to_json

  render json: payload
end

...to sending null if no messages are present:

def update_flash
  payload = {
    flash_messages: @flash ? nil : [flash_messages]
  }.to_json
  # if null, this would give the following json:
  # => {"flash_messaages":null}

  render json: payload
end

My old JavaScript client side had the following:

if (Object.keys(data.flash_messages).length > 0) {
  updateFlash()
}

When a something passed to Object.keys has a null value, as it could have after the rewrite, Object.keys throws an error. This did not happen in the old code because I was guaranteed a non-null object.

Therefore I changed the client code to handle this.

if (data.flash_messages) {
  data.flash_messages(displayMessage)
 }

Yet - and here's the point of this piece - I still got a deluge of exceptions. Why? My guess is that I had publicly cached some webpages referencing the old JavaScript file. This old JavaScript made a request to the modified backend and got the new response, but couldn't handle it.

Lesson

Client-side updates that are supposed to be in lock-step with backend changes may lead to issues caused by out-of-data code that is cached.

This problem is worse with SPAs where the code may not refresh at the browser level for quite some time.

Proactive solutions: - support both old and new way for some time (but leave a deprecation notice) - reduce cache time for assets generally (or just in the time before deploying)