如何在初始化 ServiceWorker 时声明客户端以避免重新加载页面?
How can I claim a client when initializing a ServiceWorker to prevent having to reload the page?
我无法理解 Clients.claim API of the ServiceWorker. From what I understand (here and here) 我可以在 service worker activate 事件上调用 claim()
以避免不得不刷新页面以初始化 ServiceWorker。我无法让它工作,但总是不得不刷新。这是我的代码:
Service Worker 内部:
self.addEventListener('install', function (event) {
self.skipWaiting();
event.waitUntil(caches.open(CURRENT_CACHE_DICT.prefetch)
.then(function(cache) {
var cachePromises = PREFETCH_URL_LIST.map(function(prefetch_url) {
var url = new URL(prefetch_url, location.href),
request = new Request(url, {mode: 'no-cors'});
return fetch(request).then(function(response) {
if (response.status >= 400) {
throw new Error('request for ' + prefetch_url +
' failed with status ' + response.statusText);
}
return cache.put(prefetch_url, response);
}).catch(function(error) {
console.error('Not caching ' + prefetch_url + ' due to ' + error);
});
});
return Promise.all(cachePromises).then(function() {
console.log('Pre-fetching complete.');
});
}).catch(function(error) {
console.error('Pre-fetching failed:', error);
})
);
});
self.addEventListener('activate', function (event) {
// claim the scope immediately
// XXX does not work?
//self.clients.claim();
event.waitUntil(self.clients.claim()
.then(caches.keys)
.then(function(cache_name_list) {
return Promise.all(
cache_name_list.map(function() {...}
);
})
);
});
以上运行,但我最终不得不刷新并在 Chrome ServiceWorker 内部发现一个 Illegal invocation
错误。如果我从 waitUntil
处理程序中删除 clients.claim
并取消对前一个处理程序的注释,我不会收到任何错误,但我仍然需要刷新。调试器显示:
Console: {"lineNumber":128,"message":"Pre-fetching complete.","message_level":1,"sourceIdentifier":3,"sourceURL":""}
Console: {"lineNumber":0,"message":"Uncaught (in promise) TypeError: Illegal invocation","message_level":3,"sourceIdentifier":1,"sourceURL":""}
刷新是这样触发的:
function waitForInstallation(registration) {
return new RSVP.Promise(function(resolve, reject) {
if (registration.installing) {
registration.installing.addEventListener('statechange', function(e) {
if (e.target.state == 'installed') {
resolve();
} else if (e.target.state == 'redundant') {
reject(e);
}
});
} else {
resolve();
}
});
}
// refreshing should not be necessary if scope is claimed on activate
function claimScope(installation) {
return new RSVP.Promise(function (resolve, reject) {
if (navigator.serviceWorker.controller) {
resolve();
} else {
reject(new Error("Please refresh to initialize serviceworker."));
}
});
}
rJS(window)
.declareMethod('render', function (my_option_dict) {
var gadget = this;
if ('serviceWorker' in navigator) {
return new RSVP.Queue()
.push(function () {
return navigator.serviceWorker.register(
my_option_dict.serviceworker_url,
{scope: my_option_dict.scope}
);
})
.push(function (registration) {
return waitForInstallation(registration);
})
.push(function (installation) {
return claimScope(installation);
})
.push(null, function (my_error) {
console.log(my_error);
throw my_error;
});
} else {
throw new Error("Browser does not support serviceworker.");
}
});
问题:
如何正确地防止必须刷新页面才能使用 claim
激活 ServiceWorker? None 我发现的链接提到必须显式检查 controller
但我假设如果 ServiceWorker 处于活动状态,它将有一个可访问的控制器。
感谢您透露一些信息。
编辑:
在下面的帮助下弄清楚了。这使它对我有用:
// runs while an existing worker runs or nothing controls the page (update here)
self.addEventListener('install', function (event) {
event.waitUntil(caches.open(CURRENT_CACHE_DICT.dictionary)
.then(function(cache) {
var cache_promise_list = DICTIONARY_URL_LIST.map(function(prefetch_url) {...});
return Promise.all(cache_promise_list).then(function() {
console.log('Pre-fetching complete.');
});
})
.then(function () {
// force waiting worker to become active worker (claim)
self.skipWaiting();
}).catch(function(error) {
console.error('Pre-fetching failed:', error);
})
);
});
// runs active page, changes here (like deleting old cache) breaks page
self.addEventListener('activate', function (event) {
event.waitUntil(caches.keys()
.then(function(cache_name_list) {
return Promise.all(
cache_name_list.map(function(cache_name) { ... })
);
})
.then(function () {
return self.clients.claim();
})
);
});
触发脚本:
var SW = navigator.serviceWorker;
function installServiceWorker(my_option_dict) {
return new RSVP.Queue()
.push(function () {
return SW.getRegistration();
})
.push(function (is_registered_worker) {
// XXX What if this isn't mine?
if (!is_registered_worker) {
return SW.register(
my_option_dict.serviceworker_url, {
"scope": my_option_dict.scope
}
);
}
return is_registered_worker;
});
}
function waitForInstallation(registration) {
return new RSVP.Promise(function(resolve, reject) {
if (registration.installing) {
// If the current registration represents the "installing" service
// worker, then wait until the installation step completes (during
// which any defined resources are pre-fetched) to continue.
registration.installing.addEventListener('statechange', function(e) {
if (e.target.state == 'installed') {
resolve(registration);
} else if (e.target.state == 'redundant') {
reject(e);
}
});
} else {
// Otherwise, if this isn't the "installing" service worker, then
// installation must have beencompleted during a previous visit to this
// page, and the any resources will already have benn pre-fetched So
// we can proceed right away.
resolve(registration);
}
});
}
// refreshing should not be necessary if scope is claimed on activate
function claimScope(registration) {
return new RSVP.Promise(function (resolve, reject) {
if (registration.active.state === 'activated') {
resolve();
} else {
reject(new Error("Please refresh to initialize serviceworker."));
}
});
}
rJS(window)
.ready(function (my_gadget) {
my_gadget.property_dict = {};
})
.declareMethod('render', function (my_option_dict) {
var gadget = this;
if (!SW) {
throw new Error("Browser does not support serviceworker.");
}
return new RSVP.Queue()
.push(function () {
return installServiceWorker(my_option_dict),
})
.push(function (my_promise) {
return waitForInstallation(my_promise);
})
.push(function (my_installation) {
return claimScope(my_installation);
})
.push(function () {
return gadget;
})
.push(null, function (my_error) {
console.log(my_error);
throw my_error;
});
});
首先,您似乎是因为代码中的拼写错误而收到错误消息。请参阅底部的注释。
此外,skipWaiting()
and Clients.claim()
可以通过一次请求安装和激活 new 软件。但很自然地,你只会在重新加载后获得像 css 这样的静态资产。
因此,即使配备 skipWaiting()
和 Clients.claim()
,您也需要重新加载两次页面才能看到更新的 static
内容,例如新的 html 或样式;
页面加载 #1
- 向
sw.js
发出请求,since SW contents is changed install
事件被触发。
- 同时
activate
事件被触发,因为你的 install
处理程序中有 self.skipWaiting()
。
- 因此,您的
activate
处理程序 运行 和您的 self.clients.claim()
调用。这将命令 SW 接管其前任控制下的所有客户端的控制。
- 此时,缓存中的资产已更新,您的页面全部由新的 service worker 控制。例如,Service Worker 范围内的任何 Ajax 请求都会 return 新缓存的响应。
页面加载 #2
您的应用加载,并且您的 SW 像往常一样通过劫持请求从缓存中响应。但现在缓存 up-to-date,用户可以 完全 使用应用和新资产。
您遇到的错误
Uncaught (in promise) TypeError: Illegal invocation
错误必须是由于您的 activate
处理程序中缺少括号;
event.waitUntil(self.clients.claim()
.then(caches.keys)
.then(function(cache_name_list) {
return Promise.all(
cache_name_list.map(function() {...}
); <-- Here is our very lonely single parenthesis.
})
);
如果您修复该错误,该错误应该会消失。
我无法理解 Clients.claim API of the ServiceWorker. From what I understand (here and here) 我可以在 service worker activate 事件上调用 claim()
以避免不得不刷新页面以初始化 ServiceWorker。我无法让它工作,但总是不得不刷新。这是我的代码:
Service Worker 内部:
self.addEventListener('install', function (event) {
self.skipWaiting();
event.waitUntil(caches.open(CURRENT_CACHE_DICT.prefetch)
.then(function(cache) {
var cachePromises = PREFETCH_URL_LIST.map(function(prefetch_url) {
var url = new URL(prefetch_url, location.href),
request = new Request(url, {mode: 'no-cors'});
return fetch(request).then(function(response) {
if (response.status >= 400) {
throw new Error('request for ' + prefetch_url +
' failed with status ' + response.statusText);
}
return cache.put(prefetch_url, response);
}).catch(function(error) {
console.error('Not caching ' + prefetch_url + ' due to ' + error);
});
});
return Promise.all(cachePromises).then(function() {
console.log('Pre-fetching complete.');
});
}).catch(function(error) {
console.error('Pre-fetching failed:', error);
})
);
});
self.addEventListener('activate', function (event) {
// claim the scope immediately
// XXX does not work?
//self.clients.claim();
event.waitUntil(self.clients.claim()
.then(caches.keys)
.then(function(cache_name_list) {
return Promise.all(
cache_name_list.map(function() {...}
);
})
);
});
以上运行,但我最终不得不刷新并在 Chrome ServiceWorker 内部发现一个 Illegal invocation
错误。如果我从 waitUntil
处理程序中删除 clients.claim
并取消对前一个处理程序的注释,我不会收到任何错误,但我仍然需要刷新。调试器显示:
Console: {"lineNumber":128,"message":"Pre-fetching complete.","message_level":1,"sourceIdentifier":3,"sourceURL":""}
Console: {"lineNumber":0,"message":"Uncaught (in promise) TypeError: Illegal invocation","message_level":3,"sourceIdentifier":1,"sourceURL":""}
刷新是这样触发的:
function waitForInstallation(registration) {
return new RSVP.Promise(function(resolve, reject) {
if (registration.installing) {
registration.installing.addEventListener('statechange', function(e) {
if (e.target.state == 'installed') {
resolve();
} else if (e.target.state == 'redundant') {
reject(e);
}
});
} else {
resolve();
}
});
}
// refreshing should not be necessary if scope is claimed on activate
function claimScope(installation) {
return new RSVP.Promise(function (resolve, reject) {
if (navigator.serviceWorker.controller) {
resolve();
} else {
reject(new Error("Please refresh to initialize serviceworker."));
}
});
}
rJS(window)
.declareMethod('render', function (my_option_dict) {
var gadget = this;
if ('serviceWorker' in navigator) {
return new RSVP.Queue()
.push(function () {
return navigator.serviceWorker.register(
my_option_dict.serviceworker_url,
{scope: my_option_dict.scope}
);
})
.push(function (registration) {
return waitForInstallation(registration);
})
.push(function (installation) {
return claimScope(installation);
})
.push(null, function (my_error) {
console.log(my_error);
throw my_error;
});
} else {
throw new Error("Browser does not support serviceworker.");
}
});
问题:
如何正确地防止必须刷新页面才能使用 claim
激活 ServiceWorker? None 我发现的链接提到必须显式检查 controller
但我假设如果 ServiceWorker 处于活动状态,它将有一个可访问的控制器。
感谢您透露一些信息。
编辑:
在下面的帮助下弄清楚了。这使它对我有用:
// runs while an existing worker runs or nothing controls the page (update here)
self.addEventListener('install', function (event) {
event.waitUntil(caches.open(CURRENT_CACHE_DICT.dictionary)
.then(function(cache) {
var cache_promise_list = DICTIONARY_URL_LIST.map(function(prefetch_url) {...});
return Promise.all(cache_promise_list).then(function() {
console.log('Pre-fetching complete.');
});
})
.then(function () {
// force waiting worker to become active worker (claim)
self.skipWaiting();
}).catch(function(error) {
console.error('Pre-fetching failed:', error);
})
);
});
// runs active page, changes here (like deleting old cache) breaks page
self.addEventListener('activate', function (event) {
event.waitUntil(caches.keys()
.then(function(cache_name_list) {
return Promise.all(
cache_name_list.map(function(cache_name) { ... })
);
})
.then(function () {
return self.clients.claim();
})
);
});
触发脚本:
var SW = navigator.serviceWorker;
function installServiceWorker(my_option_dict) {
return new RSVP.Queue()
.push(function () {
return SW.getRegistration();
})
.push(function (is_registered_worker) {
// XXX What if this isn't mine?
if (!is_registered_worker) {
return SW.register(
my_option_dict.serviceworker_url, {
"scope": my_option_dict.scope
}
);
}
return is_registered_worker;
});
}
function waitForInstallation(registration) {
return new RSVP.Promise(function(resolve, reject) {
if (registration.installing) {
// If the current registration represents the "installing" service
// worker, then wait until the installation step completes (during
// which any defined resources are pre-fetched) to continue.
registration.installing.addEventListener('statechange', function(e) {
if (e.target.state == 'installed') {
resolve(registration);
} else if (e.target.state == 'redundant') {
reject(e);
}
});
} else {
// Otherwise, if this isn't the "installing" service worker, then
// installation must have beencompleted during a previous visit to this
// page, and the any resources will already have benn pre-fetched So
// we can proceed right away.
resolve(registration);
}
});
}
// refreshing should not be necessary if scope is claimed on activate
function claimScope(registration) {
return new RSVP.Promise(function (resolve, reject) {
if (registration.active.state === 'activated') {
resolve();
} else {
reject(new Error("Please refresh to initialize serviceworker."));
}
});
}
rJS(window)
.ready(function (my_gadget) {
my_gadget.property_dict = {};
})
.declareMethod('render', function (my_option_dict) {
var gadget = this;
if (!SW) {
throw new Error("Browser does not support serviceworker.");
}
return new RSVP.Queue()
.push(function () {
return installServiceWorker(my_option_dict),
})
.push(function (my_promise) {
return waitForInstallation(my_promise);
})
.push(function (my_installation) {
return claimScope(my_installation);
})
.push(function () {
return gadget;
})
.push(null, function (my_error) {
console.log(my_error);
throw my_error;
});
});
首先,您似乎是因为代码中的拼写错误而收到错误消息。请参阅底部的注释。
此外,skipWaiting()
and Clients.claim()
可以通过一次请求安装和激活 new 软件。但很自然地,你只会在重新加载后获得像 css 这样的静态资产。
因此,即使配备 skipWaiting()
和 Clients.claim()
,您也需要重新加载两次页面才能看到更新的 static
内容,例如新的 html 或样式;
页面加载 #1
- 向
sw.js
发出请求,since SW contents is changedinstall
事件被触发。 - 同时
activate
事件被触发,因为你的install
处理程序中有self.skipWaiting()
。 - 因此,您的
activate
处理程序 运行 和您的self.clients.claim()
调用。这将命令 SW 接管其前任控制下的所有客户端的控制。 - 此时,缓存中的资产已更新,您的页面全部由新的 service worker 控制。例如,Service Worker 范围内的任何 Ajax 请求都会 return 新缓存的响应。
页面加载 #2
您的应用加载,并且您的 SW 像往常一样通过劫持请求从缓存中响应。但现在缓存 up-to-date,用户可以 完全 使用应用和新资产。
您遇到的错误
Uncaught (in promise) TypeError: Illegal invocation
错误必须是由于您的 activate
处理程序中缺少括号;
event.waitUntil(self.clients.claim()
.then(caches.keys)
.then(function(cache_name_list) {
return Promise.all(
cache_name_list.map(function() {...}
); <-- Here is our very lonely single parenthesis.
})
);
如果您修复该错误,该错误应该会消失。