1. 程式人生 > >前端黑魔法:webworker動態化,無需JS檔案建立worker

前端黑魔法:webworker動態化,無需JS檔案建立worker

前言

前幾天,我和一位知乎網友討論這個問題的時候,覺得這非常有意思,所以寫了這篇文章作為記錄 本文的思路和專案程式碼來源於知友 @simon3000,我加以修飾以更符合理解的需求。   本文所用程式碼已經得到當事人授權,請看: 非常感謝他的理解和鼓勵  

作者初始程式碼地址

 (進入專案頁面,裡面的original-version目錄下就是作者的最初的程式碼)

https://github.com/penghuwan/flex-webworker 

 


通過JS檔案和路徑建立webworker帶來的問題

Webworker,我其實一直覺得用法比較生硬,因為似乎需要建立額外的JS檔案才能執行,就像下面這樣
var worker =new Worker('work.js’)
  這意味著,你需要額外建立一個js檔案。這種方式讓我覺得有些“古板”。因為JS操縱檔案的能力很差,如果想要建立檔案,當然方法也有,參考:https://github.com/eligrey/FileSaver.js/   但是問題在於,如果想要建立檔案,JS的檔案建立往往離不開下載!我原本只是想“悄無聲息”地建立一個檔案,但結果JS在建立的時候突然彈出一個下載框,這可讓人受不了。啊,難受。(此處應有[我太難了]表情包)。   也就是,這時候的webWorker是“靜態”的,是需要額外JS檔案的,是受約束的。  

四次轉換,將一個普通函式強行變成WebWorker

但是 @simon3000 的建議讓我眼前一亮!他告訴我,根據他使用webworker-loader(webpack技術棧)的經驗,有一種連續轉換的方式可以直接將一個普通函式變成WebWorker

  這真是一個令人興奮的資訊。   試看看他的操作:
// 檔名為main.js
function work () {
  onmessage = ({data: {message}}) => {
    console.log ('i am worker, receive:' + message);
    postMessage ({result: 'message from worker'});
  };
}

const runWorker = f => {
  const worker = new Worker (
    URL.createObjectURL (new Blob ([`(${f.toString ()})()`]))
  );

  worker.onmessage = ({data: {result}}) => {
    console.log ('i am main thread, receive:' + result);
  };

  worker.postMessage ({message: 'message from main thread'});
};

const testWorker = runWorker (work);
這段程式碼是我在他的程式碼基礎上簡化的   輸出結果:  

用Promise和閉包的方式去改造

我們再讓它更通用一些,用Promise和閉包的方式去改造它,把runworker函式改造成一個makeworker函式
// 檔名為index.js
function work () {
  onmessage = ({data: {jobId, message}}) => {
    console.log ('i am worker, receive:-----' + message);
    postMessage ({jobId, result: 'message from worker'});
  };
}

const makeWorker = f => {
  let pendingJobs = {};

  const worker = new Worker (
    URL.createObjectURL (new Blob ([`(${f.toString ()})()`]))
  );

  worker.onmessage = ({data: {result, jobId}}) => {
    // 呼叫resolve,改變Promise狀態
    pendingJobs[jobId] (result);
    // 刪掉,防止key衝突
    delete pendingJobs[jobId];
  };

  return (...message) =>
    new Promise (resolve => {
      const jobId = String (Math.random ());
      pendingJobs[jobId] = resolve;
      worker.postMessage ({jobId, message});
    });
};

const testWorker = makeWorker (work);

testWorker ('message from main thread').then (message => {
  console.log ('i am main thread, i receive:-----' + message);
});

 

輸出結果  

總結

這次探討告訴我們什麼道理呢?
  • 第一,function.toString得到的並不是一個沒有意義的字串,它是完全可以被用來執行的
  • 第二,通過這種方式,webworker不需要藉助額外的JS檔案了,webworker完全動態化和自由化,你可以在主執行緒中建立任意個webworker!
  • 第三,我通過這次的交談了解到一個道理,程式設計除了考量邏輯思維,資訊差也是考量的一大因素。我之前也想過用webworker做這些事情,可是我不知道能用這樣的四重轉換呀!我也不知道function.toString得到的字串居然是有作用的。資訊差,也是會造成差距的。所以工程上也經驗和前瞻也同樣重要。

其他參考資料