Web Workers RPC

2022-10-05 18:38:36 浏览数 (1)

CSDN话题挑战赛第2期

参赛话题:前端技术分享

说在前面

对于需要花费大量时间才能处理的任务,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 浏览器整体兼容性很好,为我们大面积使用奠定了基础~~~

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

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

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

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

案例

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

传统写法

代码语言:javascript复制
// 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 方法

代码语言:javascript复制
// 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 示例

代码语言:javascript复制
// 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)来处理。

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

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

index.html

代码语言:javascript复制
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

代码语言:javascript复制
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方式)使我们可以更多的关注业务内容,忽略调用(网络)细则。

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

链接

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

0 人点赞