讓你的函數(shù)運(yùn)行在web worker中

前言

我們都知道JavaScript在瀏覽器中執(zhí)行采用的是單線程模型酝陈,也就是說,在同一時(shí)間下所有的任務(wù)都只能在一個(gè)線程上完成,只有上一件任務(wù)完成才能開始下一件任務(wù)杭措。如果javascript的代碼計(jì)算量太大辽社,執(zhí)行會(huì)耗費(fèi)很長的時(shí)間伟墙,會(huì)影響了其他任務(wù)的執(zhí)行,嚴(yán)重的還可能阻塞UI線程的渲染滴铅,導(dǎo)致頁面出現(xiàn)卡頓等情況戳葵。而且現(xiàn)在很多的cpu都是多核的,單線程執(zhí)行會(huì)浪費(fèi)很多cpu的性能汉匙。所以瀏覽器廠商提供的web worker的接口譬淳,就是為了提供讓javascript代碼運(yùn)行在多個(gè)線程的環(huán)境。web worker的工作線程和主線程是分開的盹兢,兩者互不干擾邻梆,相互間通過事件接口進(jìn)行通信。不過web worker提供的接口太原始了绎秒,不是很方便我們使用浦妄,每次實(shí)例化worker之后都要預(yù)定義單獨(dú)的javascript腳本文件,而且還要單獨(dú)維護(hù)一套通信的方法见芹。
所以我們得想一個(gè)辦法讓worker用起來優(yōu)雅順手一點(diǎn)剂娄,最好是提供一個(gè)接口可以像函數(shù)一樣調(diào)用,比如像這樣:

  const work = someWorker()
  work.add(function count(n){
    return n + 1
  })
  work.count(1).then(res => {
    console.log(res) // 2
  })

這樣看上去是不是比較直觀一點(diǎn)玄呛,配合asyncawait用起來就非常優(yōu)雅了阅懦,完全屏蔽了js主線程和worker之間通信的細(xì)節(jié)。

實(shí)現(xiàn)方法

其實(shí)具體通信的解決方法很簡單徘铝,我們只需要在函數(shù)調(diào)用的時(shí)候耳胎,postMessage要調(diào)用的函數(shù)和參數(shù)到worker里面去惯吕,再監(jiān)聽worker返回的結(jié)果。worker內(nèi)部也是一樣的道理怕午,監(jiān)聽主線程傳過來的消息废登,再執(zhí)行相應(yīng)的函數(shù),用postMessage返回執(zhí)行結(jié)果郁惜。

// js主線程
function invoke (method = '', params = []) {
  const promise = new Promise((resolve, reject)=> {
    worker.onmessage = (e) => {
      resolve(JSON.parse(e.data))
    }
    worker.onerror = (e) => {
      reject(e)
    }
  })
  worker.postMessage(JSON.stringify({
    method,
    params
  }))
  return promise
}
// worker
self.onmessage = (e) => {
  const {method, params} = JSON.parse(e).data
  const result = self[method].apply(null, params)
  postMessage(JSON.stringify(result))
}
// self是worker全局環(huán)境的引用堡距,和window差不多
// 所以調(diào)用self的方法就是調(diào)用全局環(huán)境下注冊的方法

這樣我們就實(shí)現(xiàn)了一個(gè)簡單的worker通信模型,只需要在傳入worker的js腳本中提前定義好函數(shù)兆蕉,就可以在主線程通過invoke調(diào)用函數(shù)了羽戒。但這和我們的想法還是有點(diǎn)不一樣,我們的模型的可以動(dòng)態(tài)地往worker中添加函數(shù)虎韵,而且函數(shù)可以定義在主線程中易稠,這樣可以獲得更好的靈活性和可維護(hù)性。
那么問題來了劝术,實(shí)例化worker需要傳入js文件的地址缩多,而且這個(gè)地址不能是file://開頭的,意味著不能訪問本地的文件养晋,所以worker的腳本必須加載至網(wǎng)絡(luò)衬吆。那么有沒有一種好的方法可以動(dòng)態(tài)生成js代碼片段,而且能夠包裝成worker可以接受的類型呢绳泉?
其實(shí)是有的逊抡,瀏覽器廠商提供了一個(gè)URL的對象,這個(gè)對象有一個(gè)create?ObjectURL方法零酪,這個(gè)方法可以接受一個(gè)二進(jìn)制對象生成URL冒嫡,所以我們還需要Blob類來生成二進(jìn)制數(shù)據(jù),我們的問題就可以完美解決了四苇。

let funcStr = ''
// 我們把函數(shù)名和函數(shù)引用以key-value的方式用Map儲(chǔ)存起來
for (const [name, func] of methods.entries()) {
  let str = ''
  if (isArrowFunc(func)) {
    str = `;var ${name} = ${Function.prototype.toString.call(func)}`
  } else {
    str = `;${Function.prototype.toString.call(func)}`
  }
  funcStr += str
}

const code = `${code};\n${worker_scheduler}`
const url = URL.createObjectURL(new Blob([code]))
const worker = new Worker(url)

其實(shí)原理也很簡單孝凌,我們通過Function.toString這個(gè)方法得到函數(shù)的定義,相當(dāng)于把函數(shù)定義復(fù)制到了worker腳本月腋。這里需要提醒一下的是蟀架,es6箭頭函數(shù)的函數(shù)定義和普通的函數(shù)有一定的區(qū)別,我們需要分別處理榆骚。

const fn1 = () => {}
function fn2 () {}
Function.prototype.toString.call(fn1)
// () => {}
Function.prototype.toString.call(fn2)
// function fn2 () {}

我們可以看到箭頭函數(shù)的定義沒有定義的名字片拍,不過我們可以通過Function.name獲取到函數(shù)定義名。

fn1.name //  "fn1"
fn2.name //   "fn2"

接下來的東西都很簡單了妓肢,我們自己在內(nèi)部維護(hù)這樣的一套機(jī)制捌省,只需要對外暴露addinvoke兩個(gè)接口就可以讓主線程定義的函數(shù)跑在worker當(dāng)中了。
所以我順手實(shí)現(xiàn)了一個(gè)庫funcwork碉钠,內(nèi)部實(shí)現(xiàn)代碼只要一百多行纲缓,對外暴露了3個(gè)方法卷拘,用起來很方便。

import funcwork form 'funcwork'

const { add, invoke, terminate } = funcwork()

function sayName (name) {
  return `Hello ${name}!`
}

const sayHi () {
  return 'Hi!'
}

async function requestInfo (url, id) {
    return fetch(url, {id})
}

add(sayName, sayHi)

await invoke('sayName', ['naeco'])    // Hello naeco!
await invoke('sayHi')                // Hi!
await invoke('requestInfo', ['api/getUserInfo', 'xxx123456']) //  user info...

// 不用的時(shí)候記得銷毀
terminate()

大家覺得不錯(cuò)的可以順手給個(gè)star??????

后續(xù)

其實(shí)web worker這個(gè)東西出現(xiàn)了也挺久了色徘,現(xiàn)在瀏覽器支持度已經(jīng)很不錯(cuò)了恭金,但是我發(fā)現(xiàn)實(shí)際項(xiàng)目還是很少人用到操禀。個(gè)人認(rèn)為主要原因有兩個(gè):

  1. 接口不友好
  2. 使用場景有限

針對第一點(diǎn)我們可以自己進(jìn)行封裝褂策,可以讓web worker用起來像promise一樣順手。第二點(diǎn)要看我們具體的業(yè)務(wù)場景了颓屑,一些計(jì)算量比較大的工作可以嘗試交給web worker斤寂,比如canvas和圖片的計(jì)算,服務(wù)器輪詢和上傳文件等等場景揪惦。


??????
我是naecoo遍搞,前端打雜工程師,偶爾寫寫灌水文章...
Github
博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末器腋,一起剝皮案震驚了整個(gè)濱河市溪猿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纫塌,老刑警劉巖诊县,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異措左,居然都是意外死亡依痊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門怎披,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胸嘁,“玉大人,你說我怎么就攤上這事凉逛⌒院辏” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵状飞,是天一觀的道長毫胜。 經(jīng)常有香客問我,道長昔瞧,這世上最難降的妖魔是什么指蚁? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮自晰,結(jié)果婚禮上凝化,老公的妹妹穿的比我還像新娘。我一直安慰自己酬荞,他們只是感情好搓劫,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布瞧哟。 她就那樣靜靜地躺著,像睡著了一般枪向。 火紅的嫁衣襯著肌膚如雪勤揩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天秘蛔,我揣著相機(jī)與錄音陨亡,去河邊找鬼。 笑死深员,一個(gè)胖子當(dāng)著我的面吹牛负蠕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倦畅,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼遮糖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了叠赐?” 一聲冷哼從身側(cè)響起欲账,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芭概,沒想到半個(gè)月后赛不,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谈山,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年俄删,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奏路。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡畴椰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸽粉,到底是詐尸還是另有隱情斜脂,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布触机,位于F島的核電站帚戳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏儡首。R本人自食惡果不足惜片任,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔬胯。 院中可真熱鬧对供,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至京景,卻和暖如春窿冯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背确徙。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工醒串, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人米愿。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓厦凤,卻偏偏與公主長得像鼻吮,于是被迫代替她去往敵國和親育苟。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 概要 64學(xué)時(shí) 3.5學(xué)分 章節(jié)安排 電子商務(wù)網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,223評論 0 3
  • 一椎木、概述 JavaScript 語言采用的是單線程模型违柏,也就是說,所有任務(wù)只能在一個(gè)線程上完成香椎,一次只能做一件事漱竖。...
    零星小雨_c84a閱讀 2,471評論 0 2
  • 作者:阮一峰www.ruanyifeng.com/blog/2018/07/web-worker.html 概述 ...
    grain先森閱讀 1,083評論 0 1
  • 清晨是闖入我夢的怪獸,燈籠一樣的眼睛畜伐,告訴我他的名字馍惹。長長的舌頭舔著我,讓我知曉清晨的風(fēng)玛界,自雪山上來万矾,融化在這愜意...
    應(yīng)心閱讀 142評論 1 0
  • 問君可有歸期 未語先有淚滴 心中千言萬語 悄然藏于夢里
    蘭亭舟陽閱讀 188評論 0 2