与工人合作,如您所愿,而不是“尽可能地”

本文将使用脏,不安全,“拐杖”,“吓人”等eval方法。 心里淡淡的不看!


我必须马上说不可能解决一些可用性问题:您不能在将传递给工作程序的代码中使用闭包。
与工作人员“随心所欲”地工作,而不是“尽可能地”工作


我们所有人都喜欢新技术,并且在方便使用这些技术时,我们都喜欢它。 但是对于工人来说,这并不完全正确。 Worker使用文件或文件链接,但这很不方便。 我希望能够将任何任务放入工作程序中,而不仅仅是专门计划的代码。


需要什么才能使与工人的工作更加方便? 我认为以下内容:


  • 能够随时在worker中运行任意代码
  • 能够将复杂的数据传递给工作人员(类实例,函数)
  • 能够通过员工的回应获得承诺。

首先,我们需要工作人员和主窗口之间的通信协议。 通常,协议只是浏览器窗口和工作程序将与之通信的结构和数据类型。 没有什么复杂的。 您可以使用类似这样的内容或编写自己的版本。 在每条消息中,我们将有一个ID和特定于特定类型消息的数据。 首先,我们将为工作者提供两种类型的消息:


  • 向工作者添加库/文件
  • 开始工作

内部文件


在开始创建工作程序之前,您需要描述一个将在工作程序中运行并支持我们描述的协议的文件。 我喜欢OOP ,所以它将是一个称为WorkerBody的类。 此类必须从父窗口预订事件。


 self.onmessage = (message) => { this.onMessage(message.data); }; 

现在我们可以从父窗口监听事件。 我们有两种类型的事件:隐含答案的事件和所有其他事件。 我们处理事件。
使用importScripts API将库和文件添加到工作器中。


最糟糕的是:我们将使用eval运行任意函数。


 ... onMessage(message) { switch (message.type) { case MESSAGE_TYPE.ADD_LIBS: this.addLibs(message.libs); break; case MESSAGE_TYPE.WORK: this.doWork(message); break; } } doWork(message) { try { const processor = eval(message.job); const params = this._parser.parse(message.params); const result = processor(params); if (result && result.then && typeof result.then === 'function') { result.then((data) => { this.send({ id: message.id, state: true, body: data }); }, (error) => { if (error instanceof Error) { error = String(error); } this.send({ id: message.id, state: false, body: error }); }); } else { this.send({ id: message.id, state: true, body: result }); } } catch (e) { this.send({ id: message.id, state: false, body: String(e) }); } } send(data) { data.body = this._serializer.serialize(data.body); try { self.postMessage(data); } catch (e) { const toSet = { id: data.id, state: false, body: String(e) }; self.postMessage(toSet); } } 

onMessage方法负责接收消息并选择处理程序, doWork启动传递的函数,然后send将响应发送到父窗口。


解析器和序列化器


现在我们有了worker的内容,我们需要学习如何序列化和解析任何数据以将其传递给worker。 让我们从序列化器开始。 我们希望能够将任何数据传递给工作程序,包括类实例,类和函数。 但是使用worker的本机功能,我们只能传输类似JSON的数据。 为了绕开这个禁令,我们需要评估 。 所有不能接受JSON的内容,我们都将包装相应的字符串构造并在另一端运行。 为了保持不变性,将即时克隆获得的数据,将无法以常规方式序列化的数据替换为服务对象,然后将其反过来由另一端的解析器替换。 乍一看,这个任务似乎并不困难,但是有很多陷阱。 这种方法的最大局限性是无法使用闭包,闭包的编写方式略有不同。 让我们从最简单的功能开始。 首先,您需要学习如何将函数与类构造函数区分开。


让我们尝试区分:


 static isFunction(Factory){ if (!Factory.prototype) { // Arrow function has no prototype return true; } const prototypePropsLength = Object.getOwnPropertyNames(Factory.prototype) .filter(item => item !== 'constructor') .length; return prototypePropsLength === 0 && Serializer.getClassParents(Factory).length === 1; } static getClassParents(Factory) { const result = [Factory]; let tmp = Factory; let item = Object.getPrototypeOf(tmp); while (item.prototype) { result.push(item); tmp = item; item = Object.getPrototypeOf(tmp); } return result.reverse(); } 

首先,我们将查找该函数是否具有原型。 如果不是,那绝对是一个功能。 然后,我们查看原型中的属性数量,如果原型中只有构造函数和函数不是另一个类的继承人,则我们将其视为函数。


找到一个函数后,我们只需将其替换为带有__type = "serialized-function"template字段的服务对象,该对象等于该函数的模板( func.toString() )。


现在,跳过该类并解析该类实例。 在数据中,我们还需要区分普通对象和类实例。


 static isInstance(some) { const constructor = some.constructor; if (!constructor) { return false; } return !Serializer.isNative(constructor); } static isNative(data) { return /function .*?\(\) \{ \[native code\] \}/.test(data.toString()); } 

如果对象没有构造函数或其构造函数是本机函数,则认为该对象是普通的。 确定了类实例之后,我们将其替换为具有字段的服务对象:


  • __type '序列化实例'
  • data -实例中的数据
  • index该实例在类服务列表中的类索引。

要传输数据,我们需要添加一个附加字段:在其中,我们将存储我们传递的所有唯一类的列表。 最困难的部分是,当检测到一个类时,不仅要获取其模板,还要获取所有父类的模板,并将它们另存为单独的类-这样每个“父”都不会被传递一次以上-并保存对instanceof的检查。 定义一个类很容易:这是一个未通过Serializer.isFunction测试的函数。 添加类时,我们检查序列化数据列表中是否存在此类,并仅添加唯一的类。 将类收集到模板中的代码很大,位于此处


在解析器中,我们首先处理传递给我们的所有类,如果之前没有传递过它们,则对其进行编译。 然后,我们递归地遍历每个数据字段,并用已编译的数据替换服务对象。 最有趣的是在类实例中。 我们有一个类,并且实例中有数据,但是我们不能仅仅实例化它,因为对构造函数的调用可能具有我们没有的参数。 一个几乎被遗忘的Object.create方法可以为我们提供帮助,该方法返回具有给定原型的对象。 因此,我们避免调用构造函数并获取类的实例,然后将属性重写为该实例。


创作工作者


为了使工作程序成功工作,我们需要在工作程序的内部和外部都有一个解析器和序列化器,因此我们将使用序列化器,然后将工作程序的序列化器,解析器和主体转换为模板。 我们从模板中创建一个Blob,并通过URL.createObjectURL创建下载链接(此方法可能不适用于某些“ Content-Security-Policy”)。 此方法也适用于从字符串运行任意代码。


 _createWorker(customWorker) { const template = `var MyWorker = ${this._createTemplate(customWorker)};`; const blob = new Blob([template], { type: 'application/javascript' }); return new Worker(URL.createObjectURL(blob)); } _createTemplate(WorkerBody) { const Name = Serializer.getFnName(WorkerBody); if (!Name) { throw new Error('Unnamed Worker Body class! Please add name to Worker Body class!'); } return [ '(function () {', this._getFullClassTemplate(Serializer, 'Serializer'), this._getFullClassTemplate(Parser, 'Parser'), this._getFullClassTemplate(WorkerBody, 'WorkerBody'), `return new WorkerBody(Serializer, Parser)})();` ].join('\n'); } 

结果


因此,我们获得了一个易于使用的库,该库可以在worker中运行任意代码。 它支持TypeScript中的类。 例如:


 const wrapper = workerWrapper.create(); wrapper.process((params) => { // This code in worker. Cannot use closure! // do some hard work return 100; // or return Promise.resolve(100) }, params).then((result) => { // result = 100; }); wrapper.terminate() // terminate for kill worker process 

进一步的计划


不幸的是,这个图书馆远非理想。 有必要在类,对象,原型,静态属性上添加对setter和getter的支持。 我们还想添加缓存,通过URL.createObjectURL使其他脚本在不eval情况下运行,并向程序集添加一个包含worker内容的文件(如果无法即时创建)。 来到仓库

Source: https://habr.com/ru/post/zh-CN462155/


All Articles