我在我的一个项目中遇到了同样的问题。我希望我的应用程序在CacheStorage 中存储一些资产。
我的代码看起来像这样:
window.caches.open(CACHE_NAME)
.then(cache => cache.addAll(resources))
.catch(err => console.log('error while caching', err));
我得到了同样的错误:
ERROR Error: Uncaught (in promise): TypeError: the given value is not a Promise
在搜索问题时,我发现 Angular 正在用它自己的 ZoneAwarePromise 替换浏览器的全局 Promise 实现以跟踪承诺。不幸的是,CacheStorage 以某种方式期望原生 Promise 而不是 Angular 的 ZoneAwarePromise 并引发错误。
我试图通过临时存储ZoneAwarePromise 并使用原始Promise 重置它来解决这个问题。事实证明这很棘手,导致我放弃交换实现。
我为此找到了两个合适的解决方法。
Service Worker 客户端通信
对于我的第二个(我认为更好)解决方法,我为 client service worker 通信打开一个通道,并让 service worker 进行动态缓存。
-
在 Service Worker 中实现 message 事件监听器。
Service Worker 将监听 message 事件以接收并缓存来自客户端的 url 列表。
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);
}
});
-
为客户端的新 url 添加缓存。
这会向 Service Worker 提交一条消息,其中包含 url 列表作为有效负载。
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]);
});
}
-
确保 Service Worker 处于活动状态以进行缓存。
如果有新的 service worker 控制,我会在客户端使用 controllerchange 事件来触发重新缓存。 (见: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))
);
隐形iframe
我针对这个问题的第一个解决方法实际上只是一个 hack,但它现在可以工作。
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));
我在这里做什么:
我创建了一个隐藏的iframe 并将其附加到我的文档中。在 iframe 内部,Angular 没有运行,所以存在原生的 Promise 实现。然后我从 iframe 的 contentWindow 对象访问缓存。成功缓存后,我删除了iframe。
我仍在寻找更好的解决方案。
我的下一个尝试是向 service worker 发送消息并让它触发资源的缓存。 Service Worker 也没有 Angular 运行。