javascript - 如何在初始化 ServiceWorker 时声明客户端以避免重新加载页面?

标签 javascript service-worker

我很难理解 Clients.claim ServiceWorker 的 API。据我了解(herehere),我可以在服务 worker activate 事件上调用 claim() 以防止必须刷新页面来初始化 ServiceWorker .我无法让它工作,但总是不得不刷新。这是我的代码:

在服务 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?我发现的所有链接都没有提到必须显式检查 controller,但我假设如果 ServiceWorker 处于事件状态,它将有一个可访问的 Controller 。

感谢您透露一些信息。

编辑:
在下面的帮助下弄清楚了。这使它对我有用:

// 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()Clients.claim() 确实通过单个请求安装和激活软件。但很自然地,您在重新加载后只会获得像 css 这样的静态 Assets 。

因此,即使配备了 skipWaiting()Clients.claim(),您也需要重新加载两次页面才能看到更新的 static 内容喜欢新的 html 或样式;

页面加载 #1

  • sw.js 的请求已发出,并且 since SW contents is changed install 事件被触发。
  • 同时 activate 事件被触发,因为您的 install 处理程序中有 self.skipWaiting()
  • 因此,您的 activate 处理程序运行并且有您的 self.clients.claim() 调用。这将命令 SW 接管其前任控制下的所有客户端的控制。
  • 此时,缓存中的 Assets 已更新,您的页面全部由新的 service worker 控制。例如,service worker 范围内的任何 Ajax 请求都将返回新缓存的响应。

页面加载#2

您的应用程序加载,您的软件通过像往常一样劫持请求从缓存中响应。但现在缓存是最新的,用户可以完全使用应用程序和新 Assets 。

您遇到的错误

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.
    })
  );

如果您修复该错误,该错误应该会消失。

关于javascript - 如何在初始化 ServiceWorker 时声明客户端以避免重新加载页面?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41912166/

相关文章:

service-worker - 推送web api,notificationclick,如何打开桌面客户端而不是打开新的浏览器客户端

javascript - 数据表 Highcharts

javascript - 如何有重叠的药丸进度条

javascript - 刷新/定时器功能仅适用于 2 个提要中的 1 个 -- $q.all() 合并

javascript - 当蜂窝网络不可用时,geolocation.getCurrentPosition 不起作用

reactjs - 如何激活 react 路由并传递来自 Service Worker 的数据?

angular - Angular Service Worker和index.html缓存

javascript - 是否有充分的理由为 JavaScript 使用短文件名?

javascript - React Native 组件不能使用 super

javascript - 如何在 Service Worker 中持久化数据