提起 “worker” 的話,你能想起什麼來呢 --
是“我們們工人有力量”?
![image](https://i.iter01.com/images/df49ed48d19fc66b5f3b31c02c1c66b872e52ec441519e900d86edc5305564e3.jpg)
還是“伐伐伐...伐木工”?
![image](https://i.iter01.com/images/7fe7b8c2ef320268b88b340158d924259adace97663a0d875918b492b6f4dc91.jpg)
當然,稍有經驗的開發者可能已經從標題猜出,今天真正要說的是 -- JavaScript 中的 worker 們:
在 HTML5 規範中提出了工作執行緒(Web Worker
)的概念,允許開發人員編寫能夠脫離主執行緒、長時間執行而不被使用者所中斷的後臺程式,去執行事務或者邏輯,並同時保證頁面對使用者的及時響應。
Web Worker
又分為 Dedicated Worker
和 SharedWorker
。
隨後 ServiceWorker
也加入進來,用於更好的控制快取和處理請求,讓離線應用成為可能。
I. 程式和執行緒
先來複習一下基礎知識:
- 程式(process)和執行緒(thread)是作業系統(OS) 裡面的兩個基本概念
- 對於 OS 來說,一個任務就是一個程式;比如 Chrome 瀏覽器每開啟一個視窗就新建一個程式
- 一個程式可以由多個執行緒組成,它們分別執行不同的任務;比如 Word 可以藉助不同執行緒同時進行打字、拼寫檢查、列印等
- 區別在於:每個程式都需要 OS 為其分配獨立的記憶體地址空間,而同一程式中的所有執行緒共享同一塊地址空間
- 多執行緒可以併發(時間上快速交替)執行,或在多核 CPU 上並行執行
![image](https://i.iter01.com/images/14a7ecaa7b3bc43ffd7ce72711bd85cc55fed30b11df5fa3c4bc9d0e3c760631.jpg)
傳統頁面中(HTML5 之前)的 JavaScript 的執行都是以單執行緒的方式工作的,雖然有多種方式實現了對多執行緒的模擬(例如:JavaScript 中的 setinterval 方法,setTimeout 方法等),但是在本質上程式的執行仍然是由 JavaScript 引擎以單執行緒排程的方式進行的。
![image](https://i.iter01.com/images/b68afc29f3db7124647608caf1c944afee577eeb635670bbe7423768daab817f.jpg)
為了避免多執行緒 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](https://i.iter01.com/images/93f1b240bbf8c81ebe1ee4b938e74a3fce8d16fe9c0a05fa0e682a7bc4062e5c.png)
![image](https://i.iter01.com/images/89a6e0fb039433a9b4ca66b0c75659795bafd2f8ac104228613f2ee7eb6550b1.png)
例項: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
中的全域性物件就是其本身- 可以使用
WorkerGlobalScope
的self
只讀屬性來獲得這個物件本身的引用 - 並且可以呼叫相關的
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有力量-在瀏覽器中玩轉多執行緒和離線應用](https://i.iter01.com/images/6102951b48f84a58861ef6992683e66af580cd4e5c2b64e5f4e0e3144575838c.png)
(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有力量-在瀏覽器中玩轉多執行緒和離線應用](https://i.iter01.com/images/49b112ab4f65669950e7b5b19d3ee1231c96c4061434433d6d7df1d0a40af70e.png)
IV. Service Worker
![image](https://i.iter01.com/images/ef8ba804f449de94ca644739284a1023a7b4bc666a3746b4d62c351b664bdd65.png)
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
;而後者的優勢正在於,可以使用 Request
和 Response
物件
每次網路請求,都會觸發對應的 service worker 中的 fetch
事件
![image](https://i.iter01.com/images/5f38c5e1499387667b43016167daac55ec8628b08aecec000944fd69a0eccb24.png)
在我們的例子中,頁面上有一個指向 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有力量-在瀏覽器中玩轉多執行緒和離線應用](https://i.iter01.com/images/762e9e116522c8259de88f26712bec97b9974a0e4e7de21308e2b17f7ebbcf63.png)
![我們們worker有力量-在瀏覽器中玩轉多執行緒和離線應用](https://i.iter01.com/images/b490bae1e00e4a4ce17eadfa22e661a007dd26e0bcd31b92fb41444e315d2994.png)
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](https://i.iter01.com/images/011c7cecab90b3b90e3e94cc07a92f5ce3c3e43d38c37df0925e5f1e0bbd3b3b.png)
![image](https://i.iter01.com/images/7bf39033cef6f807930ee494ecd9641e9901d10ab9d33377f331f69f943b8a0c.png)
將 server worker 的生命週期設計成這樣,其目的在於:
- 實現離線優先
- 允許新服務工作執行緒自行做好執行準備,無需中斷當前的服務工作執行緒
- 確保整個過程中作用域頁面由同一個服務工作執行緒(或者沒有服務工作執行緒)控制
- 確保每次只執行網站的一個版本
對應的事件
![image](https://i.iter01.com/images/e87c01f3ca3a7e8ab4e7cfc0d9d76e66062b2c6867877c174920acde85e194fe.png)
重要的比如:
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
-
出於安全考慮,目前只能在 HTTPS 環境下才能使用 service worker;不符合則會丟擲錯誤
DOMException: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).
-
在測試時,是可以用
http://localhost
進行的
後臺同步
![image](https://i.iter01.com/images/2ab05d01f7d04d1f0d2c4c704fa1fc47ea18f7f73ea83ba8f3a35f89e8af349e.png)
後臺同步(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](https://i.iter01.com/images/2400f3fb0ec317fde208bc24bcb60549702cba22678a87189619e03bfbdf9681.png)
Push API 是基於 service worker 構建的另一個功能。該 API 允許喚醒 service worker 以響應來自作業系統訊息傳遞服務的訊息。
- 即使使用者沒有為您的網站開啟標籤,也會如此,僅喚醒 service worker
- 從頁面請求執行此操作的許可權,使用者將收到提示
- 適合於與通知相關的內容,如聊天訊息、突發新聞或電子郵件
- 同時可用於頻繁更改受益於立即同步的內容,如待辦事項更新或日曆更改
- 需要 google 的 FCM 通道服務,目前國內無法使用
chrome 離線小恐龍的遊戲
正是基於 service worker,chrome 在網路不可用時會顯示小恐龍冒險的離線遊戲,按下空格鍵,就可以開始了~
(4.4) Service Worker 的瀏覽器相容性
由於一些相關的 google 服務無法用,iOS 上對其的支援也有限並在試驗階段,所以尚不具備大規模應用的條件;
但作為漸進式網路應用技術 PWA 中的最重要的組成部分,國內很多廠商已經在嘗試推進相關的支援,未來值得期待:
![我們們worker有力量-在瀏覽器中玩轉多執行緒和離線應用](https://i.iter01.com/images/5fbb95f699fa2f9ff089322acb35c284b8e7ae56bf44fdc48f2e27e055118fb6.png)
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/
![我們們worker有力量-在瀏覽器中玩轉多執行緒和離線應用](https://i.iter01.com/images/0ff3044d56333745eab89fa37ce3689d12d2719220bf305741e41cdd64762c41.jpg)