vuestorefront / vue-storefront

Alokai is a Frontend as a Service solution that simplifies composable commerce. It connects all the technologies needed to build and deploy fast & scalable ecommerce frontends. It guides merchants to deliver exceptional customer experiences quickly and easily.
https://www.alokai.com
MIT License
10.64k stars 2.08k forks source link

Cache not responding #1335

Closed nstgmk closed 6 years ago

nstgmk commented 6 years ago

When I clear the browser cache, enter my store's home page and then navigate to any category or product page I get the error message "Cache not responding". After this happens and I check Application > IndexedDB in Chrome I can see that nothing gets added to the database. For example, if I try to add something to the cart I can see that it's not added in the database. I can place an order in the checkout but it's not sent to the backend for processing at all. It's not found in syncTasks either.

Refreshing the page solves the problem. The problem appears in my dev store running v1.0.5 and the vue storefront demo page. I haven't checked if it's present in developer build.

I tried looking into the problem myself and found out that something seems to happen in lib/search.js in the function quickSearchByQuery. If I call ready() on global.$VS.db.elasticCacheCollection._localForageCollection I can see that the promise is pending and not resolved at the time getItem is called.

I used the promise from ready() and placed the whole cache.getItem block inside the then() part and this solved the problem. IndexedDB shows items as expected, no error message and no refresh needed.

I'm new to JS and Vue development so I'd appreciate if someone more experienced could look into this and maybe find a better solution or explain what happens. But it seems like elasticCacheCollection is just not ready when it's called? Is there a better way to wait for it?

Thanks!

pkarw commented 6 years ago

Thanks! This is great insight for solving this problem once and for all. We should consider how to deal with it; currently there is a fallback to the in-memory cache so the app is working in case of problesm with cache not responding - like normally. The downside is that if this problem will happen - then nothing is stored in the persistent storage (so after reloading the page shopping cart, user token - all will lost).

pkarw commented 6 years ago

The simplest way to work around it is just to modify the: https://github.com/DivanteLtd/vue-storefront/blob/6be64960d9dc5e728fcdda75068f80d8dbc8436a/core/store/lib/storage.js#L42

and the: https://github.com/DivanteLtd/vue-storefront/blob/6be64960d9dc5e728fcdda75068f80d8dbc8436a/core/store/lib/storage.js#L129

to add the ready() chain before setItem(), getItem(): https://localforage.github.io/localForage/#driver-api-ready

Could You test it, please?

nstgmk commented 6 years ago

Great suggestion! I've given it a try now. I changed getItem to the following:

getItem (key, callback) {
    const self = this
    const isCallbackCallable = (typeof callback !== 'undefined' && callback)
    let isResolved = false
    if (self._useLocalCacheByDefault && self._localCache[key]) {
      // console.debug('Local cache fallback for GET', key)
      return new Promise((resolve, reject) => {
        const value = typeof self._localCache[key] !== 'undefined' ? self._localCache[key] : null
        if (isCallbackCallable) callback(null, value)
        resolve(value)
      })
    }
    // console.debug('No local cache fallback for GET', key)
    const promise = this._localForageCollection.ready().then(function () {
      self._localForageCollection.getItem(key).then(result => {
        if (!isResolved) {
          if (isCallbackCallable) {
            callback(null, result)
          }
          isResolved = true
        } else {
          console.debug('Skipping return value as it was previously resolved')
        }
        return result
      })
    }).catch(err => {
      console.debug('UniversalStorage - GET - probably in SSR mode: ' + err)
      if (!isResolved) {
        if (isCallbackCallable) callback(null, typeof self._localCache[key] !== 'undefined' ? self._localCache[key] : null)
      }
      isResolved = true
    })

    setTimeout(function () {
      if (!isResolved) { // this is cache time out check
        console.error('Cache not responding within 1s')
        if (isCallbackCallable) callback(null, typeof self._localCache[key] !== 'undefined' ? self._localCache[key] : null)
      }
    }, 1000)
    return promise
  }

And it worked great! No more error messages and IndexedDB stores persistently. I tried to the same with setItem:

setItem (key, value, callback) {
    const self = this
    const isCallbackCallable = (typeof callback !== 'undefined' && callback)
    self._localCache[key] = value
    const promise = self._localForageCollection.ready().then(function () {
      self._localForageCollection.setItem(key, value).then(result => {
        if (isCallbackCallable) {
          callback(null, result)
        }
      })
    }).catch(err => {
      console.debug('UniversalStorage - SET - probably in SSR mode: ' + err)
    })

    return promise
  }

But this was not successful. I got the same error message that the cache was not responding and also a new error Uncaught (in promise) TypeError: Cannot read property 'db' of null. It looks like the db connection is lost. Maybe it's not possible to check if ready here?

However, when I didn't edit setItem and only changed getItem everything works for me.

pkarw commented 6 years ago

That's cool - in the meantime I've fixed it in parallel to Your fix: https://github.com/DivanteLtd/vue-storefront/commit/079a2f002999dc0b3ced64a4c06e9b5c64aaecea

:)

For me it works for setItem as well. The message You're getting is probably caused by some other reason ..

nstgmk commented 6 years ago

You're probably right :) I'll try your solution instead and see if it works. Thanks!

pkarw commented 6 years ago

Thanks! Without Your observation i wouldn't have fix it! :)