那些年,我們一起讓瀏覽器當掉的日子
昨天提到了 Worker 就像是主從式架構一樣,有要求才有產出,而前後端的任務切割,就是在將商業邏輯與頁面呈現區隔開來。但隨著專案越來越複雜,前端越來越包山包海,我們好像一直不斷的在挑戰瀏覽器一樣,在前端做更多更複雜的事情。
有了 Worker 之後,或許我們會開始回憶起「那些年,我們一起讓瀏覽器當掉的日子」。
回到主題,那麼到底有哪些情況會用到 Worker 呢?我們先來看看許多專家的說法:
Web Worker的使用案例
* 影像辨識
* 影音資料處理
* 大量資料運算
* 頻繁或大量的AJAX request
* 較複雜的字串比對或分析
* Local 端資料庫的存取
* File IO (File Reader/Writer API)
以往這些較複雜的運算可能都要透過 server 端來處理,既然前端都可以處理這些事情了,那我們就可以直接在前端處理這些複雜運算,再度的節省 server 端的使用。而 Server 端最重要的 Database ,加上 HTML5 LocalStorage 與 IndexDB 可以在前端建立資料庫,拿來做一些簡單的單機系統也會更加容易。
但是這些很複雜的案例,並不是每個專案都有機會碰到,因此我想討論一些更common的案例。
前端可以處理的,就不在後端做
例如上傳圖片,以前都是直接整個檔案直接丟到server端做 resize, crop等等,現在流行前端直接做好再丟到後端。例如上傳/下載某種檔案支援格式(.txt, .xls, .blabla),server端只要統一好格式,由前端組好在上傳到server端即可。
使用者操作時的「偷偷」動作
使用者在頻繁的操作系統時,如果此時要做一些偷偷背景動作,例如偷偷背景存資料,偷偷背景上傳檔案,讓使用者感覺「Wow,好快速。」例如:「文章自動儲存機制」,「程式碼高亮化(syntax highlight)」等。因為使用者正在操作系統,任何一個動作中斷都會讓使用者覺得系統當掉。
反正重點就是:優化/節省 Server Side 的使用,搾乾 Client Side 的效能(偷笑)。當然,這只是討論,如果有更好的使用案例都歡迎分享。
實作
有了這些基礎概念之後,我們就可以開始實作了。然而在實作上,我很快就遇到第一個問題。通常會需要用到 Worker 來幫忙的,可能會是比較複雜的專案,但中大型專案可能會用到 CoffeeScript 來加速開發的速度,用 RequireJS 來管理模組間的相依性,如果將 Worker 的 script 拆出去,馬上就遇到 Dependency 的問題。舉例來說:
worker = new Worker('/worker_path/my_worker.js');
這樣的路徑就失去了用 requirejs 的優勢,破壞了整個專案的 dependency ,如果為了用 Web Worker 而搞砸專案架構,那可是得不償失的,不過幸好,山不轉路轉。
Inline Worker
在 new Worker 時,丟進 Worker 的參數是一個路徑,那麼我們就可以動態產生一個 blob url 路徑,傳給Worker ,動態產生 Woker。
content = """
//It is javascript content
var dosomething = function(){
return 'ok dosomething';
};
"""
blobWorker = new Blob([content], {type:'application/javascript'})
blobWorker_url = URL.createObjectURL(blobWorker)
inlineWorker = new Worker(blobWorker_url)
URL.revokeObjectURL(blobWorker_url)
如上,我們就可以動態用 content 來建立 inlineWorker 。只要確保 content 的產生是沒有問題的,用 string 的方式將它讀入就可以建立 Worker。
另外 requireJS 也支援在 worker 裡頭使用,當你的 Worker 要做的事情很多時,可以用 RequireJS 來進行管理,參考requireJS webworker
Debug
Worker 裡頭沒有好用的 console 可以用,不過有 error 的 event 可以註冊。例如:
worker.addEventListener('error', function(error){
console.error(error)
});
另外也可以在 developer tool 裡頭的「Source」開啟「Workers: Pause on start」
引用外部程式
Worker 是否可以引用外部程式? 答案是可以的,但需要注意的是它的限制是無法存取瀏覽器資源,所以跟 browser 相關的 API 例如 DOM 都不能用,所以不能使用 jQuery ,Backbone 這種與 Browser 相依性很高的 Library (廢話)。
importScripts("http://underscorejs.org/underscore.js")
值得一提的是,importScripts可以載入不同Domain的javascript。但建議還是不要信任外部的程式,除了安全問題之外,如果這個路徑無法被 access 就會造成整個 Worker 直接死掉。
釋放Worker
如果確定Worker做完事了,需要結束 Woker 以釋放瀏覽器資源,可以呼叫terminate來終結 worker
worker.terminate();
或是直接在 Worker 裡頭自己主動結束
self.close();
其他
Worker 其實還定義了 Shared Worker ,可以在不同頁面共享 Worker 的資源,在此不介紹是因為我想不到有比較有意義的應用,有興趣深入瞭解的朋友可以參考 The Basics of Web Workers,裡面有基本的用法介紹。
Just do it
Web Worker 的 API 其實很簡單,難的是在實作上如何將這些動作拆開。建議可以慢慢的將一些動作移到 Worker 來處理,例如 AJAX request ,將資料處理與頁面 render 的程式分開。慢慢的就會更容易的掌握它。
不過不要以為 Web Worker 就這樣結束了,這篇文章其實只是個起點,實際應用在專案上,還會遇到很多問題(嘿嘿~),有任何問題都歡迎提出討論喔 ,我們 2013 JSDC 見。
廣告時間
「影分身好威喔!我也想要!」
HTC One 的 Camera 也可以快速建立多重影分身