Angular 5 with CacheStorage

2020-04-30 02:23发布

im trying to use CacheStorage's promises in Angular 5 like in the docs :

let test = caches.open('test');
    test.then((result : Cache) => {
      result.add('/test.png')
    })

but i got :

ERROR Error: Uncaught (in promise): TypeError: the given value is not a Promise

I tryed switching from es5 to es6 in the ts file. But same problem

thanks

2条回答
一纸荒年 Trace。
2楼-- · 2020-04-30 02:35

I had the same problem in one of my projects. I wanted my application to store some assets in the CacheStorage.

My code looked something like this:

window.caches.open(CACHE_NAME)
  .then(cache => cache.addAll(resources))
  .catch(err => console.log('error while caching', err));

I got the same Error:

ERROR Error: Uncaught (in promise): TypeError: the given value is not a Promise

While searching for the problem I found out that Angular is replacing the browser's global Promise implementation by it's own ZoneAwarePromise to keep track of promises. Unfortunately the CacheStorage somehow expects a native Promise instead of Angular's ZoneAwarePromise and throws an error.

I tried to get around this problem by storing the ZoneAwarePromise temporarily and reset it with the original Promise. This turned out to be tricky, resulting in me giving up to exchange back the implementation.

I found two suitable workarounds for this.

Service Worker <=> Client Communication

For my second (and I think better) workaround I open a channel for client <=> service worker communication and let the service worker do the dynamic caching.

  1. Implement the message event listener in the service worker.

    The service worker will listen to the message event to receive and cache a list of urls from the client.

    self.addEventListener('message', event => {
      const urls = event.data;
    
      if (!urls) {
        event.waitUntil(Promise.reject('nothing to cache'));
        event.ports[0].postMessage(false);
      }
      else {
        event.waitUntil(
          caches.open(version)
            .then(cache => cache.addAll(urls))
            .then(self.skipWaiting())
            .then(() => console.log('dynamic urls cached'))
        );
        event.ports[0].postMessage(true);
      }
    });
    
  2. Add caching for new urls to the client.

    This submits a message to the service worker with the list of urls as payload.

    function addToCache(urls: string[]): Promise<any> {
      return new Promise((resolve, reject) => {
        let messageChannel = new MessageChannel();
        messageChannel.port1.onmessage = function(event) {
          if (event.data.error) reject(event.data.error);
          else resolve(event.data);
        };
    
        if (!navigator.serviceWorker.controller) reject('no service worker contoller found')
        else navigator.serviceWorker.controller.postMessage(urls, [messageChannel.port2]);
      });
    }
    
  3. Make sure the service worker is active for caching.

    I use the controllerchange event in the client to trigger re-caching if a new service worker is taking control. (see: service worker lifecycle documentation).

    const resources = ['some/url/to/cache.png', 'another/url/to/cache.css']
    
    navigator.serviceWorker.addEventListener('controllerchange', () => 
      addToCache(resources)
        .then(data => console.log('urls cached', data))
        .catch(err => console.log('error while caching', err))
    );
    

Invisible iframe

My first workaround for this problem is really just a hack but it works for now.

const hiddenFrame = document.createElement('iframe');
hiddenFrame.style.display = 'none';
document.documentElement.appendChild(hiddenFrame);

hiddenFrame.contentWindow.caches.open(CACHE_NAME)
  .then(cache => cache.addAll(resources))
  .then(() => document.documentElement.removeChild(hiddenFrame))
  .catch(err => console.log('error while caching', err));

What I'm doing here:

I create a hidden iframe and append it to my document. Inside the iframe Angular is not running and so there is the native Promise implementation present. Then I access the cache from the iframe's contentWindow object. After successful caching I remove the iframe.

I'm still looking for a better solution.

My next try is to send a message to a service worker and let it trigger the caching of the resources. The service worker has no angular running either.

查看更多
叼着烟拽天下
3楼-- · 2020-04-30 02:51

This is easy to miss -

1) CacheStorage API (or Cache API) are only available on localhost and HTTP/2.
2) For purists reason, prefer to use window.caches.open(CACHE_NAME) as caches is a singleton instance present in window object.

查看更多
登录 后发表回答