国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

嘗鮮 workerize 源碼

muzhuyu / 840人閱讀

摘要:同時在初始化的過程中,會將主線程加載的模塊中的每個方法,都綁定一個快捷方法,其方法名與模塊中的函數(shù)聲明保持一致,內(nèi)部則使用來完成調(diào)用邏輯。

寫在前面

最近正好在看web worker相關的東西,今天無意中就看到了github一周最熱項目的推送中,有這么一個項目workerize,repo里的文檔的描述如下:

Moves a module into a Web Worker, automatically reflecting exported functions as asynchronous proxies.
例子

關于README很簡單,包含一個類似hello world的例子就沒其他什么了。但是從例子本身可以看出這個庫要解決的問題,是想通過模塊化的方式編寫運行在web worker中的腳本,因為通常情況下,web worker每加載一個腳本文件是需要通過一個符合同源策略的URL的,這樣會對服務端發(fā)送一個額外的請求。同時對于web worker本身加載的js文件的執(zhí)行環(huán)境,與主線程是隔離的(這也是它在進行復雜運算時不會阻塞主線程的原因),與主線程的通訊靠postMessageapi和onmessage回調(diào)事件來通訊,這樣我們在編寫一些通信代碼時,需要同時在兩個不同的環(huán)境中分別編寫發(fā)送消息和接受消息的邏輯,比較繁瑣,同時這些代碼也不能以模塊化的形式存在。

如果存在一種方式,我們可以以模塊化的方式來編寫代碼,注入web worker,之后還能通過類似Promsie機制來處理等異步,那便是極好的。

先來看看例子:

import workerize from "workerize"

let worker1 = workerize(`
    export function add(a, b) {
        let start = Date.now();
        while (Date.now()-start < 500);
        return a + b;
  }

  export default function minus(a, b){
    let start = Date.now();
        while (Date.now()-start < 500);
    return a - b
  }
`)

let worker2 = workerize(function (m) {
  m.add = function (a, b) {
    let start = Date.now()
    while (Date.now() - start < 500);
    return a + b
  }
});

(async () => {
  console.log("1 + 2 = ", await worker1.add(1, 2))
  console.log("3 + 9 = ", await worker2.call("add", [3, 9]))
})()

worker1和worker2是兩種不同的使用方式,一種是以字符串的形式聲明模塊,一種以函數(shù)的形式聲明模塊。但是無論哪種,最后的結果都是一樣的,我們可以通過worker實例顯示的調(diào)用我們想要調(diào)用的方法,每個方法的調(diào)用結果均是一個Promise,因此它還可以完美的適配async/await語法。

源碼

那么問題來了,這種模塊的加載機制和調(diào)用方式是怎樣實現(xiàn)的呢?我在運行demo代碼的時候心中也默默想到,我去,看了好幾天的web worker原來還能這么玩,所以一定要研究研究它的源碼和它的實現(xiàn)原理。

打開源代碼才發(fā)現(xiàn)其實并沒有多少代碼,官文文檔也通過一句話強調(diào)了這一點:

Just 900 bytes of gzipped ES3

所以對其中主要的兩點進行簡單說明:

如何實現(xiàn)按內(nèi)容模塊化加載腳本而不是通過URL

如何通過Promise來代理主線程與worker線程的通訊過程

使用Blob動態(tài)生成加載腳本資源
let blob = new Blob([code], {
      type: "application/javascript"
    }),
    url = URL.createObjectURL(blob),
    worker = new Worker(url)

這其實不是什么新鮮的東西,就是將代碼的內(nèi)容轉化為Blob對象,之后再通過URL.createObjectURL將Blob對象轉化為URL的形式,之后再用worker加載它,僅此而已。但是這里的問題是,這個code是哪里從哪里來的呢?

將加載代碼模塊化

在加載代碼之前,還有重要的一步,就是需要將加載的代碼轉變?yōu)槟K,模板本身只對外暴露統(tǒng)一的接口,這樣不論對于主線程還是worker線程,就有了統(tǒng)一的約束條件。源碼中作者把上一步中的code轉化為了類似commonjs的形式,主要涉及的代碼有:

let exportsObjName = `__EXPORTS_${Math.random().toString().substring(2)}__`
  if (typeof code === "function") code = `(${toCode(code)})(${exportsObjName})`
  code = toCjs(code, exportsObjName, exports)
  code += `
(${toCode(setup)})(self, ${exportsObjName}, {})`

toCjs方法

function toCjs (code, exportsObjName, exports) {
  exportsObjName = exportsObjName || "exports"
  exports = exports || {}
  code = code.replace(/^(s*)exports+defaults+/m, (s, before) => {
    exports.default = true
    return `${before}${exportsObjName}.default = `
  })
  code = code.replace(/^(s*)exports+(function|const|let|var)(s+)([a-zA-Z$_][a-zA-Z0-9$_]*)/m, (s, before, type, ws, name) => {
    exports[name] = true
    return `${before}${exportsObjName}.${name} = ${type}${ws}${name}`
  })
  return `var ${exportsObjName} = {};
${code}
${exportsObjName};`
}

關于toCjs方法,如果你的正則知識比較扎實的話,可以發(fā)現(xiàn),它做了一件事,就是將字符串類型的code中的所有導出方法的聲明,使用commonjs的導出語法替換掉(中間會涉及一些具體的語法規(guī)則),如下:

// 如果 exportsObjName 使用默認值 exports, ...代表省略代碼
export function foo(){ ... } => exports.foo = function foo(){ ... }
export default ... => exports.default = ...

如果code是函數(shù)類型,則首先使用toCode函數(shù)將code轉化為string類型,之后再將它轉化為IIFE的形式,如下

// 如果 exportsObjName 使用默認值 exports, ...代表省略代碼
// 傳入的code是如下形式:
function( m ){ 
  ... 
}
// 轉化為
(function( m ){
  ...
})(exports)

這里的exportsObjName代表模塊的名字,默認值是exports(聯(lián)想commonjs),不過這里會在一開始就隨機生成一個模塊名字,生成代碼如下:

let exportsObjName = `__EXPORTS_${Math.random().toString().substring(2)}__`

這樣只有我們按照約定的語法來編寫web worker加載的代碼,它便會加載了一個符合同樣約定的commonjs模塊。

使用 Promise 來做異步代理

經(jīng)過上面兩步,web worker加載到了模塊化的代碼,但是worker線程與主線程進行通訊則是仍然需要通過postMessage方法和onmessage回調(diào)事件來進行,如果無法優(yōu)雅地處理這里的異步邏輯,那么之前所做的工作其實意義并不大。

workerize針對這里的異步邏輯,設計了一個簡單的rpc協(xié)議(文檔中將這個稱作a tiny, purpose-built RPC),先來看一下源碼中的setup函數(shù):

function setup (ctx, rpcMethods, callbacks) {
    ctx.addEventListener("message", ({ data }) => {
      // 只捕獲滿足條件的數(shù)據(jù)對象
      if (data.type === "RPC") {
        // 獲取數(shù)據(jù)對象中的 id 屬性
        let id = data.id
        if (id != null) {
          // 如果數(shù)據(jù)對象中存在非空 method 屬性,則證明是主線程發(fā)送的消息
          if (data.method) {
            // 獲取所要調(diào)用的方法實例
            let method = rpcMethods[data.method]
            if (method == null) {
              // 如果所調(diào)用的方法實例不存在,則發(fā)送方法不存在的消息
              ctx.postMessage({ type: "RPC", id, error: "NO_SUCH_METHOD" })
            } else {
              // 如果方法存在,則調(diào)用它,并將調(diào)用結果按不同的類型發(fā)送
              Promise.resolve()
                .then(() => method.apply(null, data.params))
                .then(result => { ctx.postMessage({ type: "RPC", id, result }) })
                .catch(error => { ctx.postMessage({ type: "RPC", id, error }) })
            }
          // 如果 method 屬性為空,則證明是 worker 線程發(fā)送的消息
          } else {
            // 獲取每個消息所對應的處于pending狀態(tài)的Promise實例
            let callback = callbacks[id]
            if (callback == null) throw Error(`Unknown callback ${id}`)
            delete callbacks[id]

            // 按消息的類型將Promise轉化為resolve狀態(tài)或reject狀態(tài)。
            if (data.error) callback.reject(Error(data.error))
            else callback.resolve(data.result)
          }
        }
      }
    })
  }

根據(jù)注釋我們可以知道,這里的setup函數(shù)包含了rpc協(xié)議的解析規(guī)則,因此主線程和worker線程對會調(diào)用該方法來注冊安裝這個rpc協(xié)議,具體的代碼如下:

主線程: setup(worker, worker.rpcMethods, callbacks)

worker線程: code += ` (${toCode(setup)})(self, ${exportsObjName}, {})

這兩處代碼都是在各自的作用域中,將rpc協(xié)議與當前加載的模塊綁定起來,只不過主進程所傳callbacks是有意義的,而worker則使用一個空對象代替。

注冊調(diào)用邏輯

在擁有了rpc協(xié)議的基礎上,只需要實現(xiàn)調(diào)用邏輯即可,代碼如下:

worker.call = (method, params) => new Promise((resolve, reject) => {
    let id = `rpc${++counter}`
    callbacks[id] = { method, resolve, reject }
    worker.postMessage({ type: "RPC", id, method, params })
})

這個call方法,每次會將一次方法的調(diào)用,轉化為一個pending狀態(tài)的Promise實例,并存在callbacks變量中,同時向worker線程發(fā)送一個格式為調(diào)用方法數(shù)據(jù)格式的消息。

for (let i in exports) {
   if (exports.hasOwnProperty(i) && !(i in worker)) {
     worker[i] = (...args) => worker.call(i, args)
   }
}

同時在初始化的過程中,會將主線程加載的模塊中的每個方法,都綁定一個快捷方法,其方法名與模塊中的函數(shù)聲明保持一致,內(nèi)部則使用worker.call來完成調(diào)用邏輯。

最后

關于這個庫本身,還存在一些可以探討的問題,比如:

是否支持依賴解析機制

如果引入外部依賴模塊

針對消息是否需要按隊列進行處理

關于前兩點,似乎作者有一個相同的項目,叫做workerize-loader,可以解決,關于第三點,作者在代碼中增加了todo,表示實現(xiàn)消息隊列機制可能沒有必要,因為當前的通訊基于postMessage,本身的結果已經(jīng)是有序狀態(tài)的了。

關于源碼本身的分析大概就這樣了,希望可以拋磚引玉,如有錯誤,還望指正。

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92514.html

相關文章

  • php7嘗鮮

    摘要:從容器里拷貝文件到宿主機這個根據(jù)你自己生成的容器來宿主機映射的配置文件夾位置修改宿主機上的配置文件文件注意點表示程序在前臺運行。里面是我創(chuàng)建的一個文件。 通過yum源安裝php7 PHP 7.0.0 已經(jīng)推出了幾天,帶來了新版本的Zend引擎,不僅如此,還有許多新特性和改進,比如: 性能提升:PHP 7速度是PHP 5.6的兩倍 內(nèi)存的使用顯著降低 抽象語法樹 支持64位 許多重大的...

    Jackwoo 評論0 收藏0
  • php7嘗鮮

    摘要:從容器里拷貝文件到宿主機這個根據(jù)你自己生成的容器來宿主機映射的配置文件夾位置修改宿主機上的配置文件文件注意點表示程序在前臺運行。里面是我創(chuàng)建的一個文件。 通過yum源安裝php7 PHP 7.0.0 已經(jīng)推出了幾天,帶來了新版本的Zend引擎,不僅如此,還有許多新特性和改進,比如: 性能提升:PHP 7速度是PHP 5.6的兩倍 內(nèi)存的使用顯著降低 抽象語法樹 支持64位 許多重大的...

    suemi 評論0 收藏0
  • 前端每周清單第 49 期:Webpack 4 Beta 嘗鮮,React Windowing 與 s

    摘要:盡管等待了多年,但是最終還是發(fā)布了正式版本與上一個版本相比未有重大變化,主要著眼于部分錯誤修復與提升。能夠將異步函數(shù)移入獨立線程中,可以看做函數(shù)的單函數(shù)簡化版。不過需要注意的是,僅支持純函數(shù),其會在獨立的作用域中運行這些函數(shù)。 showImg(https://segmentfault.com/img/remote/1460000013038757); 前端每周清單專注前端領域內(nèi)容,以對...

    muzhuyu 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<