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

江米小棗tonylua發表於2017-12-12

提起 “worker” 的話,你能想起什麼來呢 --

是“我們們工人有力量”?

image

還是“伐伐伐...伐木工”?

image

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

在 HTML5 規範中提出了工作執行緒(Web Worker)的概念,允許開發人員編寫能夠脫離主執行緒、長時間執行而不被使用者所中斷的後臺程式,去執行事務或者邏輯,並同時保證頁面對使用者的及時響應。

Web Worker 又分為 Dedicated WorkerSharedWorker

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

I. 程式和執行緒

先來複習一下基礎知識:

  • 程式(process)和執行緒(thread)是作業系統(OS) 裡面的兩個基本概念
  • 對於 OS 來說,一個任務就是一個程式;比如 Chrome 瀏覽器每開啟一個視窗就新建一個程式
  • 一個程式可以由多個執行緒組成,它們分別執行不同的任務;比如 Word 可以藉助不同執行緒同時進行打字、拼寫檢查、列印等
  • 區別在於:每個程式都需要 OS 為其分配獨立的記憶體地址空間,而同一程式中的所有執行緒共享同一塊地址空間
  • 多執行緒可以併發(時間上快速交替)執行,或在多核 CPU 上並行執行

image

傳統頁面中(HTML5 之前)的 JavaScript 的執行都是以單執行緒的方式工作的,雖然有多種方式實現了對多執行緒的模擬(例如:JavaScript 中的 setinterval 方法,setTimeout 方法等),但是在本質上程式的執行仍然是由 JavaScript 引擎以單執行緒排程的方式進行的。

image

為了避免多執行緒 UI 操作的衝突(如執行緒1要求瀏覽器刪除DOM節點,執行緒2卻希望修改這個節點的某些樣式風格),JS 將處理使用者互動、定時執行、操作DOM樹/CSS樣式樹等,都放在了 JS 引擎的一個執行緒中執行。

從 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 已經被支援的不錯了:

我們們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 才可以

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

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))
    );
  }
});
複製程式碼

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

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

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 中的最重要的組成部分,國內很多廠商已經在嘗試推進相關的支援,未來值得期待:

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

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 關注我們哦

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

相關文章