我們worker有力量-在瀏覽器中玩轉多線程和離線應用

提起 「worker」 的話,你能想起什麼來呢 --javascript

是「我們工人有力量」?php

image

仍是「伐伐伐...伐木工」?css

image

固然,稍有經驗的開發者可能已經從標題猜出,今天真正要說的是 -- JavaScript 中的 worker 們html

在 HTML5 規範中提出了工做線程(Web Worker)的概念,容許開發人員編寫可以脫離主線程、長時間運行而不被用戶所中斷的後臺程序,去執行事務或者邏輯,並同時保證頁面對用戶的及時響應。java

Web Worker 又分爲 Dedicated WorkerSharedWorkernode

隨後 ServiceWorker 也加入進來,用於更好的控制緩存和處理請求,讓離線應用成爲可能。ios

I. 進程和線程

先來複習一下基礎知識:laravel

  • 進程(process)和線程(thread)是操做系統(OS) 裏面的兩個基本概念
  • 對於 OS 來講,一個任務就是一個進程;好比 Chrome 瀏覽器每打開一個窗口就新建一個進程
  • 一個進程能夠由多個線程組成,它們分別執行不一樣的任務;好比 Word 能夠藉助不一樣線程同時進行打字、拼寫檢查、打印等
  • 區別在於:每一個進程都須要 OS 爲其分配獨立的內存地址空間,而同一進程中的全部線程共享同一塊地址空間
  • 多線程能夠併發(時間上快速交替)執行,或在多核 CPU 上並行執行

image

傳統頁面中(HTML5 以前)的 JavaScript 的運行都是以單線程的方式工做的,雖然有多種方式實現了對多線程的模擬(例如:JavaScript 中的 setinterval 方法,setTimeout 方法等),可是在本質上程序的運行仍然是由 JavaScript 引擎以單線程調度的方式進行的。git

image

爲了不多線程 UI 操做的衝突(如線程1要求瀏覽器刪除DOM節點,線程2卻但願修改這個節點的某些樣式風格),JS 將處理用戶交互、定時執行、操做DOM樹/CSS樣式樹等,都放在了 JS 引擎的一個線程中執行。es6

從 2008 年 W3C 制定出第一個 HTML5 草案開始,HTML5 承載了愈來愈多嶄新的特性和功能。它不但強化了 Web 系統或網頁的表現性能,並且還增長了對本地數據庫等 Web 應用功能的支持。

隨之而來的,還有上面提到的幾種 worker,首先解決的就是多線程的問題。

II. Master-Worker 模式

那麼,來看看解決線程問題的東西爲何叫 worker,這來源於一種設計模式:

Master-Worker模式是經常使用的並行設計模式。其核心思想是:系統有兩個進程協同工做:Master進程和Worker進程。Master進程負責接收和分配任務,Worker進程負責處理子任務。當各個Worker進程將子任務處理完後,將結果返回給Master進程,由Master進行概括和彙總,從而獲得系統結果

image

image

實例:Node.js 中的 Master-Worker 模式

Node 的內置模塊 cluster,能夠經過一個主進程管理若干子進程的方式來實現集羣的功能

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) { //主進程

  console.log(`Master ${process.pid} is running`);

  //分配子任務
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
} else { //子進程

  //應用邏輯根本不須要知道本身是在集羣仍是單邊
  //每一個HTTP server都能監聽到同一個端口
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
  
}
複製代碼

運行 node server.js 後,輸出:

Master 3596 is running
Worker 4324 started
Worker 4520 started
Worker 6056 started
Worker 5644 started
複製代碼

III. Web Worker

在 HTML5 中,Web Worker 的出現使得在 Web 頁面中進行多線程編程成爲可能

HTML5 中的多線程是這樣一種機制:它容許在 Web 程序中併發執行多個 JavaScript 腳本,每一個腳本執行流都稱爲一個線程,彼此間上下文互相獨立,而且由瀏覽器中的 JavaScript 引擎負責管理

HTML5 規範列出了 Web Worker 的三大主要特徵:

  • 可以長時間運行(響應)
  • 理想的啓動性能
  • 理想的內存消耗

HTML5 中的 Web Worker 能夠分爲兩種不一樣線程類型,一個是專用線程 Dedicated Worker,一個是共享線程 Shared Worker

(3.1) 專用線程 Dedicated Worker

專用線程是指標準 worker,一個專用 worker 僅僅能被生成它的腳本所使用

也就是說,所謂的專用線程(dedicated worker)並無一個顯示的DedicatedWorker構造函數,其實指的就是普通的Worker構造函數。

🌰 在解釋概念前,先來看一個唄兒簡單的小栗子:

//myWorker.js

self.onmessage = function(event) {
    var info = event.data;
    self.postMessage(info + " from worker!");
};
複製代碼
//主頁面

<input type="text" name="wkInput1" />
<button id="btn1">test!</button>

<script> if (window.Worker) { const myWorker = new Worker("myWorker.js"); myWorker.onmessage = function (event) { alert(event.data); }; const btn = document.querySelector('#btn1'); btn.addEventListener('click', e=>{ const ipt = document.querySelector('[name=wkInput1]'); const info = "hello " + ipt.value; myWorker.postMessage(info); }); } </script>
複製代碼

很顯然,運行的效果無非是點擊按鈕後彈出包含文本框內容的字符串。

例子很簡單,但攜帶的關鍵信息還算豐富,那麼結合規範中的一些定義來看一看上面的代碼:

首先是專用 worker 在運行的過程當中,會隱式的使用一個MessagePort對象,其接口定義以下:

interface MessagePort {
  void postMessage(message, optional transfer = []);
  attribute onmessage;
  
  void start();
  void close();
  attribute onmessageerror;
};
複製代碼

咱們把最重要的兩個成員放在了前面,一個是postMessage()方法,另外一個是onmessage屬性。

  • postMessage()方法用來發送數據:第一個參數除了能夠發送字符串,還能夠發送 JS 對象(有的瀏覽器須要JSON.stringify());可選的第二個參數可用來發送 ArrayBuffer 對象數據(一種二進制數組,配合XHR、File API、Canvas等讀取字節流數據用)
  • onmessage屬性應被指定一個事件處理函數,用於接收傳遞過來的消息;也能夠選擇使用 addEventListener 方法,其實現方式和做用和 onmessage 相同

而後來看看簡化後的 Worker 的定義:

interface AbstractWorker {
  attribute onerror;
};
複製代碼
interface Worker {
  Constructor(scriptURL, optional workerOptions);
  
  void terminate();

  void postMessage(message, optional transfer = []);
  attribute onmessage;
  attribute onmessageerror;
};

Worker implements AbstractWorker;
複製代碼
  • 首先它實現了AbstractWorker接口,也就是說有一個onerror回調用來管理錯誤;
myWorker.onerror = function(event){
    console.log(event.message);
    console.log(event.filename);
    console.log(event.lineno);
}
複製代碼
  • 其次,明顯也實現了上面提到過的MessagePort接口,能夠 postMessage/onmessage
  • 實例化一個新的 worker 只須要調用 Worker() 的構造方法,指定一個腳本的 URI
  • 當建立完 worker 之後,能夠調用 terminate() 方法去終止該線程

經過workerOptions 中的選項能夠支持 es6 模塊化等,這裏不展開論述

至此,已經能夠理解「主頁面」中的各類定義和調用行爲了;而"myWorker.js"中的self又是怎樣的呢,繼續來看看相關定義:

interface WorkerGlobalScope {
  readonly attribute self; //WorkerGlobalScope
  readonly attribute location;
  readonly attribute navigator;
  void importScripts(urls);

  attribute onerror;
  attribute onlanguagechange;
  attribute onoffline;
  attribute ononline;
  attribute onrejectionhandled;
  attribute onunhandledrejection;
};
複製代碼
interface DedicatedWorkerGlobalScope {
  readonly attribute name;

  void postMessage(
  	message, 
  	optional transfer = []
  );

  void close();

  attribute onmessage;
  attribute onmessageerror;
};
複製代碼

專用 worker 實現了以上兩個接口,可知:

  • worker中的全局對象就是其自己
  • 可使用 WorkerGlobalScopeself 只讀屬性來得到這個對象自己的引用
  • 而且能夠調用相關的MessagePort接口方法。

看起來很簡單,兩邊均可以 postMessage/onmessage,就能夠愉快的通訊了。

除了上述這些,其餘的一些要點包括:

  • 爲了安全,在 worker 中不能訪問 DOM
  • 做爲參數傳遞給 worker 構造器的 URI 必須遵循同源策略
  • worker 不能訪問 window 對象,也不能調用 alert()
  • 能夠在只讀的 navigator 對象中訪問 appName、appVersion、platform、onLine 和 userAgent 等
  • 能夠在只讀的 location 對象中獲取 hostname 和 port 等
  • 在 worker 中也支持 XMLHttpRequest 和 fetch 等
  • 支持 importScripts() 方法(在同一個域上異步引入腳本文件),該函數接受0個或者多個URI做爲參數
  • 支持 JavaScript 對象,好比 Object、Array、Date、Math 和 String
  • 支持 setTimeout() 和 setInterval() 方法
  • 在主線程中使用時,onmessage 和 postMessage() 必須掛在worker對象上,而在worker中使用時不用這樣作。緣由是,在worker內部,worker是有效的全局做用域

專用 worker 相對理想的兼容狀況

在現代瀏覽器和移動端上,能夠說專用 worker 已經被支持的不錯了:

(3.2) 共享線程 Shared Worker

共享線程指的是一個能夠被多個頁面經過多個鏈接所使用的 worker

🌰 仍是先看一個栗子:

//wk.js

var arr = [];

self.onconnect = function(e) {
	var port = e.ports[0];
	port.postMessage('hello from worker!');

	port.onmessage = function(evt) {
		var val = evt.data;
		if (!~arr.indexOf(val)) {
			arr.push(val);
		}
		port.postMessage(arr.toString());
	}
}
複製代碼
<!DOCTYPE html>
<html><body>
page 1
<script> if (window.SharedWorker) { var wk = new SharedWorker('wk.js'); wk.port.onmessage = function(e) { console.log(e.data); } wk.port.postMessage(1); } //輸出 //hello from worker! //1 </script>
</body></html>
複製代碼
<!DOCTYPE html>
<html><body>
page 2
<script> if (window.SharedWorker) { var wk = new SharedWorker('wk.js'); wk.port.onmessage = function(e) { console.log(e.data); } wk.port.postMessage(2); } //輸出 //hello from worker! //1,2 </script>
</body></html>
複製代碼

運行效果也不難理解,引用共享 worker 的兩個同域的頁面,共享了其中的 arr 數組。也就是說,專用 worker 一旦被某個頁面引用,該頁面就擁有了一個獨立的子線程上下文;與之不一樣的是,某個共享 worker 腳本文件若是被若干頁面(要求是同源的)引用,則這些頁面會共享該 worker 的上下文,擁有共同影響的變量等。

interface SharedWorker {
  Constructor(
  	scriptURL, 
  	optional (DOMString or WorkerOptions) options
  );
  
  readonly attribute port;
};

SharedWorker implements AbstractWorker;
複製代碼

另外一個很是大的區別在於,前面也提到過,與一個專用 worker 通訊,對MessagePort的實現是隱式進行的(直接在 worker 上進行postMessage/onmessage);而共享 worker 必須經過端口(MessagePort類型的worker.port)對象進行。

此外的幾個注意點:

var wk = new SharedWorker('wk.js', 'foo');
//or
var wk = new SharedWorker('wk.js', {name: 'foo'});
複製代碼
interface SharedWorkerGlobalScope {
  readonly attribute name;
  void close();
  attribute onconnect;
};
複製代碼
  • 實例化時添加的第二個參數,用於指定這個共享 worker 的名稱,必需要同名才能共享;這個 name 在 worker 中能夠藉由 self.name 得到
self.onconnect = function(e) {
	var port = e.ports[0];
	port.postMessage('hello from worker!');
    //...
}
複製代碼
  • 在共享 worker 的 onconnect 回調中直接發送了一個 postMessage,用於提示鏈接成功,這個動做在頁面刷新後並不會從新執行,而是從新打開頁面纔會執行。
var wk = new SharedWorker('wk.js');
wk.port.onmessage = function(e) {
	console.log(e.data);
}

//-->
wk.port.addEventListener('message', function(e) {
	console.log(e.data);
});
wk.port.start();
複製代碼
  • 若是用addEventListener代替onmessge,則須要額外調用 start() 方法才能創建鏈接

共享大法雖好,兼容仍需謹慎

移動端尚不支持、IE11/Edge也沒戲;測試時 Mac 端的 chrome/firefox 也是情況頻頻沒法成功,最後在 chrome@win10 以及 opera@mac 才能夠

IV. Service Worker

image

Service Worker 基於 Web Worker 的事件驅動,提供了用來管理安裝、版本、升級的一整套系統。

專用 worker 或共享 worker 專一於解決 「耗時的 JS 執行影響 UI 響應」 的問題, -- 一是後臺運行 JS,不影響主線程;二是使用postMessage()/onmessage消息機制實現了並行。

而 service worker 則是爲解決 「由於依賴並容易丟失網絡鏈接,從而形成 Web App 的用戶體驗不如 Native App」 的問題而提供的一系列技術集合;它比 web worker 獨立得更完全,能夠在頁面沒有打開的時候就運行。

而且相比於已經被廢棄的 Application Cache 緩存技術:

<html manifest="appcache.manifest">
  ...
</html>
複製代碼
CACHE MANIFEST
# appcache.manifest text file, version: 0.517

NETWORK:
#CACHE:
assets/loading.gif
assets/wei_shop_bk1.jpg
assets/wei_shop_bk2.jpg
assets/wei_ios/icons.png
assets/wei_ios/icon_addr.png
assets/wei_ios/icon_tel.png

NETWORK:
scripts/wei_webapp.js
styles/meishi_wei.css
複製代碼

service worker 擁有更精細、更完整的控制;做爲一個頁面與服務器之間代理中間層,service worker 能夠捕獲它所負責的頁面的請求,並返回相應資源,這使離線 web 應用成爲了可能。

🌰 一如既往的先看一個直觀的小栗子:

<!--http://localhost:8000/service.html-->

<h1>hello service!</h1>

<img src="deer.png" />

<script> if (navigator.serviceWorker) { window.onload = function() { navigator.serviceWorker.register( 'myService.js', {scope: '/'} ).then(registration=>{ console.log('SW register OK with scope: ', registration.scope); registration.onmessage = function(e) { console.log(e.data) } }).catch(err=>{ console.log('SW register failed: ', err); }); } // SW register OK with scope: http://localhost:8000/ } </script>
複製代碼
//myService.js

var CACHE_NAME = 'my-site-cache-v1';

var urlsToCache = [
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        return cache.addAll(urlsToCache);
      })
  );
});

self.addEventListener('fetch', function(event) {
  const url = new URL(event.request.url);
  if (url.pathname == '/deer.png') {
    event.respondWith(
      fetch('/horse.jpg').catch(ex=>console.log(ex))
    );
  } else {
    event.respondWith(
      caches.match(event.request)
        .then(function(response) {
          if (response) {
            return response;
          }
          return fetch(event.request);
        }
      )
    );
  }
});
複製代碼

結合這個簡單的示例,來梳理一下其中反映出的信息:

(4.1) 基礎組成元素

Promise

和其餘兩種 worker 不一樣的是,service worker 中的各項技術普遍地利用了 Promise

Promises 是一種很是適用於異步操做的機制,一個操做依賴於另外一個操做的成功執行。這也成爲了 service worker 的通用工做機制

Response 對象

Response 的構造函數容許建立一個自定義的響應對象:

new Response('<p>Hello from service worker!</p>', {
  headers: { 'Content-Type': 'text/html' }
})
複製代碼

但更常見的是:經過其餘的 API 操做返回了一個 Response 對象,例如一個 service worker 的 event.respondWith ,或者一個簡單的 fetch()

在 service worker 中使用 Response 對象時,一般還要經過 response.clone() 來取得一個克隆使用;這樣作的緣由是,一個 response 是一個流,只用被消費一次,而咱們想讓瀏覽器、緩存等屢次操做這個響應,就須要 clone 出不一樣的對象來;對於 Request 請求對象的使用也是相似的道理

Fetch

在 service worker 中沒法使用傳統的 XMLHttpRequest,只能使用 fetch;然後者的優點正在於,可使用 RequestResponse 對象

每次網絡請求,都會觸發對應的 service worker 中的 fetch 事件

image

在咱們的例子中,頁面上有一個指向 deer.png 的圖片元素,最後卻由 fetch 事件回調攔截並返回了 /horse.jpg,實現了混淆是非的自定義資源指向

self.addEventListener('fetch', function(event) {
  const url = new URL(event.request.url);
  if (url.pathname == '/deer.png') {
    event.respondWith(
      fetch('/horse.jpg').catch(ex=>console.log(ex))
    );
  }
});
複製代碼

Catch

在 service worker 規範中包含了原生的緩存能力,用以替代已被廢棄的 Application Cache 標準。

Cache API 提供了一個網絡請求的持久層,並可使用 match 操做查詢這些請求。

在 service worker 中最主要用到 Cache 的地方,仍是在上面提到的 fetch 事件回調中。

經過使用本地緩存中的資源,不但能省去對網絡的昂貴訪問,更有了在 離線、掉線、網絡不佳 等狀況下維持應用可用的能力。

相關的定義以下:

interface Cache {
  match(request, optional cacheQueryOptions);
  matchAll(optional request, optional cacheQueryOptions);
  add(request);
  addAll(requests);
  put(request, response);
  delete(request, optional cacheQueryOptions);
  keys(optional request, optional cacheQueryOptions);
};
複製代碼

同時 service worker 也能夠用 self.caches 來取得緩存:

interface WindowOrWorkerGlobalScope {
  readonly attribute caches; //CacheStorage
};

interface CacheStorage {
  match(request, optional options); //Promise
  has(cacheName); //Promise
  open(cacheName); //Promise
  delete(cacheName); //Promise
  keys(); //Promise
};
複製代碼

反映在例子中就是(版本的部分會在稍後提到):

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        return cache.addAll(urlsToCache);
      })
  );
});

self.addEventListener('fetch', function(event) {
    event.respondWith(
      caches.match(event.request)
        .then(function(response) {
          if (response) {
            return response;
          }
          return fetch(event.request);
        }
      )
    );
});
複製代碼

(4.2) 生命週期

完整的生命週期

image
image

將 server worker 的生命週期設計成這樣,其目的在於:

  • 實現離線優先
  • 容許新服務工做線程自行作好運行準備,無需中斷當前的服務工做線程
  • 確保整個過程當中做用域頁面由同一個服務工做線程(或者沒有服務工做線程)控制
  • 確保每次只運行網站的一個版本

對應的事件

image

重要的好比:

  • install事件:使用register() 註冊時會觸發
  • activate事件:register() 註冊時也會觸發activate事件

具體到各個事件的回調中,event 參數對應的類型以下:

事件名稱 接口
install ExtendableEvent
activate ExtendableEvent
fetch FetchEvent
message ExtendableMessageEvent
messageerror MessageEvent

其中有表明性的兩個事件的定義以下:

interface ExtendableEvent {
  void waitUntil(promiseFunc);
};

interface FetchEvent {
  readonly attribute request;
  readonly attribute clientId;
  readonly attribute reservedClientId;
  readonly attribute targetClientId;

  void respondWith(promiseFunc);
};
複製代碼

因此,才能夠在例子中調用 event.waitUntil()event.respondWith()

self.addEventListener('install', function(event) {
  event.waitUntil( //用一個 promise 檢查安裝是否成功
    //...
  );
});

self.addEventListener('fetch', function(event) {
  event.respondWith( // 返回符合指望的 Response 對象
    //...
  );
複製代碼

註冊

不一樣於其餘兩種 worker 的是,service worker 再也不用 new 來實例化,而是直接經過 navigator.serviceWorker 取得

navigator.serviceWorker 實際上實現了 ServiceWorkerContainer 接口:

interface ServiceWorkerContainer {
  readonly attribute controller;
  readonly attribute ready; //promise

  register(scriptURL, optional registrationOptions);

  getRegistration(optional clientURL = "");
  
  getRegistrations();

  void startMessages();

  attribute oncontrollerchange;
  attribute onmessage; // event.source is a worker
  attribute onmessageerror;
};
複製代碼

好比咱們在例子中的主頁面所作的:

navigator.serviceWorker.register(
	'myService.js', 
	{scope: '/'}
).then().catch()
複製代碼

scope 參數是選填的,能夠被用來指定想讓 service worker 控制的內容的子目錄;service worker 能控制的最大權限層級就是其所在的目錄

運行 register() 方法成功的話,會在 navigator.serviceWorker 的 Promise 的 then 回調中獲得一個 ServiceWorkerRegistration 類型的對象;

正如例子中所示,主頁面中就能夠用這個實例化後的 'registration' 對象調用 onmessage

interface ServiceWorkerRegistration {
  readonly attribute installing;
  readonly attribute waiting;
  readonly attribute active;
  
  readonly attribute scope;
  
  readonly attribute updateViaCache;

  update(); //in promise
  unregister(); //in promise

  attribute onupdatefound;
};
複製代碼

同時若是 register() 成功,service worker 就在 ServiceWorkerGlobalScope 環境中運行;

也就是說,myService.js 中引用的 self 就是這個類型了,能夠調用 self.skipWaiting() 等方法;

這是一個特殊類型的 worker 上下文運行環境,與主運行線程相獨立,同時也沒有訪問 DOM 等能力

interface ServiceWorkerGlobalScope {
  readonly attribute clients;
  readonly attribute registration;

  skipWaiting();

  attribute oninstall;
  attribute onactivate;
  attribute onfetch;

  attribute onmessage; // event.source is a client
  attribute onmessageerror;
};
複製代碼

和 shared worker 相似,須要當心 service worker 腳本里的全局變量: 每一個頁面不會有本身獨有的worker

安裝

在 service worker 註冊以後,install 事件會被觸發

在 install 回調中,通常執行如下任務:

  • 打開制定版本的緩存
  • 緩存文件
  • 確認全部須要的資源是否被緩存
  • 若有指定的任何緩存文件沒法下載,則安裝步驟將失敗

更新

  • 更新 service worker 所在的 JavaScript 文件。用戶打開頁面時,瀏覽器會嘗試在後臺從新下載該 JS 文件;若是該文件與其當前所用文件存在字節差別,則將其視爲「新版本的 service worker」。
  • 新服務工做線程將會啓動,且將會觸發 install 事件
  • 若是 service worker 已經被安裝,可是刷新頁面時有一個新版本的可用 -- 那麼新版本雖會在後檯安裝,但還不會激活,且進入 waiting 狀態
  • 當再也不有任何已加載的頁面在使用舊版的 service worker 的時候,新版本纔會激活,並觸發其 activate 事件

出如今 activate 回調中的一個常見任務是緩存管理。在這個步驟進行緩存管理,而不是在以前的安裝階段進行,緣由在於:若是在 install 步驟中清除了任何舊緩存,則繼續控制全部當前頁面的任何舊 service worker 將忽然沒法從緩存中提供文件

self.addEventListener('activate', function(event) {

  var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1'];

  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});
複製代碼

(4.3) 其餘

須要 HTTPS

  1. 出於安全考慮,目前只能在 HTTPS 環境下才能使用 service worker;不符合則會拋出錯誤

    DOMException: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).

  2. 在測試時,是能夠用 http://localhost 進行的

後臺同步

image

後臺同步(Background Sync)是基於 service worker 構建的另外一個功能。容許用戶一次性或按間隔時間請求後臺數據同步。

  • 即便用戶沒有爲您的網站打開標籤,也會如此,僅喚醒 service worker
  • 從頁面請求執行此操做的權限,用戶將收到提示
  • 適用於非緊急更新,如社交時間表或新聞文章
navigator.serviceWorker.register('sw.js');

//...

navigator.serviceWorker.ready.then(registration=>{
  registration.sync.register('update-leaderboard').then(function() {
    // registration succeeded
  }, function() {
    // registration failed
  });
});
複製代碼
//sw.js
self.addEventListener('sync', function(event) {
  if (event.id == 'update-leaderboard') {
    event.waitUntil(
      caches.open('mygame-dynamic').then(function(cache) {
        return cache.add('/leaderboard.json');
      })
    );
  }
});
複製代碼

推送

image

Push API 是基於 service worker 構建的另外一個功能。該 API 容許喚醒 service worker 以響應來自操做系統消息傳遞服務的消息。

  • 即便用戶沒有爲您的網站打開標籤,也會如此,僅喚醒 service worker
  • 從頁面請求執行此操做的權限,用戶將收到提示
  • 適合於與通知相關的內容,如聊天消息、突發新聞或電子郵件
  • 同時可用於頻繁更改受益於當即同步的內容,如待辦事項更新或日曆更改
  • 須要 google 的 FCM 通道服務,目前國內沒法使用

chrome 離線小恐龍的遊戲

正是基於 service worker,chrome 在網絡不可用時會顯示小恐龍冒險的離線遊戲,按下空格鍵,就能夠開始了~

(4.4) Service Worker 的瀏覽器兼容性

因爲一些相關的 google 服務沒法用,iOS 上對其的支持也有限並在試驗階段,因此尚不具有大規模應用的條件;

但做爲漸進式網絡應用技術 PWA 中的最重要的組成部分,國內不少廠商已經在嘗試推動相關的支持,將來值得期待:

V. 總結

  • Master-Worker 是經常使用的並行設計模式,用worker表示線程相關的概念就來源於此
  • web worker 的出現使得在 Web 頁面中進行多線程編程成爲可能
  • 共享線程指的是一個能夠被多個頁面經過多個鏈接所使用的 worker,頁面會共享 worker 的上下文,擁有共同影響的變量等
  • service worker 做爲一個頁面與服務器之間的 proxy,能夠捕獲它所負責的頁面的請求,並返回相應資源,這使離線 web 應用成爲了可能
  • 兼容性方面,專用 worker 相對理想一些,共享 worker 不太理想,service worker 值得期待

VI. 參考資料:

  • https://superuser.com/questions/257406/can-a-multi-core-processor-run-multiple-processes-at-the-same-time
  • http://www.cnblogs.com/whitewolf/p/javascript-single-thread-and-browser-event-loop.html
  • http://woshixiguapi.blog.163.com/blog/static/19249969201010113479457/
  • http://www.cnblogs.com/Leo_wl/p/5319735.html
  • http://www.alloyteam.com/2015/08/nodejs-cluster-tutorial/
  • https://php.golaravel.com/function.pcntl-fork.html
  • https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers
  • https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers
  • https://www.smashingmagazine.com/2016/02/making-a-service-worker/
  • https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API
  • http://blog.88mph.io/2017/07/28/understanding-service-workers/
  • https://serviceworke.rs/
  • https://aarontgrogg.com/blog/2015/07/20/the-difference-between-service-workers-web-workers-and-websockets/
  • https://www.fxsitecompat.com/en-CA/docs/2015/application-cache-api-has-been-deprecated/
  • https://24ways.org/2016/http2-server-push-and-service-workers/
  • https://ponyfoo.com/articles/backgroundsync
  • https://www.ibm.com/developerworks/cn/web/1112_sunch_webworker/
  • https://www.ibm.com/developerworks/cn/web/wa-webworkers/index.html
  • https://www.quora.com/Whats-the-difference-between-service-workers-and-web-workers-in-JavaScript
  • http://jixianqianduan.com/frontend-javascript/2014/06/05/webworker-serviceworker.html
  • https://zhuanlan.zhihu.com/p/27264234
  • https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/
  • http://imweb.io/topic/56592b8a823633e31839fc01
  • https://w3c.github.io/ServiceWorker/#service-worker-concept
  • http://www.howtobuildsoftware.com/index.php/how-do/97C/service-worker-what-is-the-api-for-unregistering-service-workers-in-chrome-44
  • https://developers.google.com/web/fundamentals/primers/service-workers/
  • https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/
  • https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#_2
  • https://juejin.im/post/59d9b38ef265da064a0f72cc
  • https://nolanlawson.github.io/cascadia-2016/#/37
  • https://love2dev.com/blog/what-is-a-service-worker/


(end)



長按二維碼或搜索 fewelife 關注咱們哦

相關文章
相關標籤/搜索