0
点赞
收藏
分享

微信扫一扫

Web Workers RPC


说在前面

对于需要花费大量时间才能处理的任务,javascript 引擎通常会有两种现象:

  1. 执行当前任务花费大量的时间,使得无法执行任何其他操作,导致浏览卡顿
  2. 如果此时回调队列被阻塞的任务过多时,大多数浏览器都会抛出一个提示信息,征求是否要关闭网页

    那么,我们如何在不阻塞UI并使浏览器正常响应的情况下执行繁重的代码呢?

引言

javascript 是单线程编程语言,这使得我们开发过程中不必关注因多线程导致的复杂场景(如,死锁)。

单线程意味着某一时刻只能做一件事情! javascript 引擎,以最常见的 v8 举例,内置了 事件循环 Event Loop + 回调队列 Callback Queue 机制,以及通过宏任务 macrotask + 微任务 microtask 来分配执行优先级,来确保高效运行。

因此,解决上述问题,通常有两种方案:

  1. 异步回调(asynchronous callbacks):依赖第三方服务
  2. 开启多线程(web worker):本文重点,浏览器提供了相应 web api

关于「JavaScript的工作原理」「Event loop及macrotask & microtask」相关内容,可阅读下述文章:

  • ​​JavaScript的工作原理:引擎,运行时和调用堆栈的概述​​
  • ​​Event loop及macrotask & microtask​​

Web Workers

worker 的一个优势在于能够执行处理器密集型的运算而不会阻塞 UI 线程。

web workers 浏览器整体兼容性很好,为我们大面积使用奠定了基础~~~

Web Workers RPC_web worker


在一个 worker 中最主要的是不能直接影响父页面,包括操作父页面的节点以及使用页面中的对象。只能间接地实现,通过 ​​DedicatedWorkerGlobalScope.postMessage​​ 回传消息给主脚本,然后从主脚本那里执行操作或变化。

worker 的优势明显,但在通信上的处理极其繁琐,导致大家使用的频次并不高。

Comlink 解决了通信的问题,其借助 Proxy 可以忽略所有繁琐的通信细节(无需考虑事件订阅所带来的复杂性),极大降低了 Worker 的维护成本。-- RPC方式

RPC 全称是 Remote Procedure Call,即远程过程调用。目的是:让我们调用远程方法像调用本地方法一样,无需了解底层网络技术的协议等。

案例

地址:https://github.com/381510688/practice/tree/master/web-api-test

传统写法

// index.js
const worker = new Worker('worker.js')
comput.addEventListener('click', function () {
worker.postMessage({
num1: num1.value,
num2: num2.value
})
})
worker.onmessage = function (msgEvent) {
res.innerHTML = msgEvent.data
}

// worker.js
onmessage = function (msgEvent) {
let {num1, num2} = msgEvent.data
postMessage(Number(num1) + Number(num2))
}

尝试保留 add 方法

// index.js
const worker = new Worker('worker.js')
comput.addEventListener('click', function () {
worker.postMessage('ok')
})
worker.onmessage = function (msgEvent) {
res.innerHTML = msgEvent.data.add(num1.value, num2.value)
}

// worker.js
function add (num1, num2) {
return Number(num1) + Number(num2)
}
onmessage = function (msgEvent) {
postMessage({add: add})
}

UncaughtDOMException: Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope': function add (num1, num2) {}

Worker.postMessage()

​​Worker​​​ 接口的 ​​postMessage()​​方法向worker的内部作用域发送一个消息。接受单个参数(要发送给worker的数据)。数据可以是由​​结构化克隆​​算法处理的任何值或JavaScript对象,其包括循环引用。

结构化克隆所不能做到的:

  • ​Error​​​ 以及 ​​Function​​​ 对象是不能被结构化克隆算法复制的;如果你尝试这样子去做,这会导致抛出 ​​DATA_CLONE_ERR​​ 的异常。
  • 企图去克隆 DOM 节点同样会抛出 ​​DATA_CLONE_ERR​​ 异常。
  • 对象的某些特定参数也不会被保留
  • ​RegExp ​​​对象的 ​​lastIndex​​ 字段不会被保留
  • 属性描述符,setters 以及 getters(以及其他类似元数据的功能)同样不会被复制。例如,如果一个对象用属性描述符标记为 read-only,它将会被复制为 read-write,因为这是默认的情况下。
  • 原形链上的属性也不会被追踪以及复制。

comlink 示例

// index.js
const worker = new Worker("worker.js");
const cw = Comlink.wrap(worker);
comput.addEventListener('click', async function () {
let result = await cw(num1.value, num2.value)
res.innerHTML = result
})

// worker.js
importScripts("https://unpkg.com/comlink/dist/umd/comlink.js");
function add (num1, num2) {
return Number(num1) + Number(num2)
}
Comlink.expose(add);

本质上依然是 MessagePort 消息通讯,不过封装了我们所头疼的“操作判断”,并以一种更优雅的方式(Proxy + Promise)来处理。

Web Workers RPC_rpc_02


Web Workers RPC_RPC_03


Comlink 采用的 RPC 代理方式,并不是传递上下文环境(因为这是非常危险的,而且函数传递时会导致 ​​Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'Worker': xxx could not be cloned.​​ 报错)。

RPC:​​Remote Procedure Call​​,远程过程调用,指调用不同于当前上下文环境的方法,通常可以是不同的线程、域、网络主机,通过提供的接口进行调用。

index.html

import * as Comlink from "https://unpkg.com/comlink/dist/esm/comlink.mjs"

const worker = new Worker("worker.js")
const cw = Comlink.wrap(worker)
const cpt = await new cw()
comput.addEventListener('click', async function () {
let result = await cpt.add(num1.value, num2.value)
dom.innerHTML = result
})

worker.js

importScripts("https://unpkg.com/comlink/dist/umd/comlink.js")
class Comput {
constructor () {}
add (num1, num2) {
return Number(num1) + Number(num2);
}
sub (num1, num2) {
return Number(num1) - Number(num2);
}
}
Comlink.expose(Comput)

​importScripts()​​​ 将一个或多个脚本同步导入到工作者的作用域中。隶属于:​​WorkerGlobalScope​​ 接口。

注意:​new Worker('worker.js')​​ scriptURL will be fetched and executed in the background, creating a new global environment for which worker represents the communication channel. – https://html.spec.whatwg.org/multipage/workers.html#dom-worker-dev

总结

Comlink(RPC方式)使我们可以更多的关注业务内容,忽略调用(网络)细则。

Web Workers RPC_RPC_04


客户端应用程序调用本地存根(stub),而不是调用实际代码;服务端应用程序接受参数,通过服务器存根(stub)检索实际代码进行运行。

链接

  • JS性能基准:https://github.com/zxch3n/js-performance
  • 本机测试:https://zxch3n.github.io/js-performance/


举报

相关推荐

0 条评论