网络工人进行救援 - 如何与JSON字符串合作而不阻止用户互动
#javascript #网络开发人员 #json #workers

网络工作人员已经存在一段时间了。但是您的使用方式取决于您拥有的应用程序类型。您可能使用网络工作人员的一种方法是提高应用程序的性能。

由于JavaScript是单线线,并且具有事件循环执行模型,因此您不想编写阻止代码。这样做会给最终用户带来糟糕的经历,因为应用程序可能会冻结并且不响应任何投入。

在本教程中,我们将开发一个涉及JSON字符串的应用程序,并了解处理大字符串计算所涉及的挑战。为了使用户体验更好,我们将使用网络工人来平滑事物,而不会阻止用户互动。

这是我们将要介绍的内容:

JavaScript事件循环

JavaScript是一种流行的Web开发编程语言,不仅在浏览器中,而且在后端。

javaScript是单线读取的,这意味着它在单个线程上运行。但是,使用事件循环作为执行模型,它也可能是异步的。您可以在此谈话中了解有关事件循环的更多信息。

事件循环允许程序员在等待未来值的同时不阻止其执行代码。无论执行JavaScript的环境如何,无论是在浏览器还是服务器中执行。

在客户端执行的应用程序中,浏览器仍然缺乏以不阻止或没有破坏用户体验的方式执行重型计算任务的方法。

例如,Demian Renzulli和Andrew Guan通过game PROXX描绘了这种情况。他们分享了游戏的作者如何使用网络工人来计算游戏逻辑和从用户互动的角度来计算性能。 Surma详细说明了Proxx是如何在case-study fashion中建造的。

什么是网络工人?

Web工作人员是在2009年创建的,根据Caniuse.com,他们得到了现代浏览器的广泛支持。在他们的早期,拟议的网络工作人员是由W3C支持的一个工作组开发的。

在提案中,作者将网络工作者分为两个类别:为特定任务运行的网络工人(或在MDN Web文档中称为他们,专门的工人)和共享工人,可以在页面和页面上获得不同的页面可以连接到它们。在本教程中,我们将专注于敬业的工人。

网络工作者和服务人员之间有几个相似之处,我们应该提到:

  • 他们只能从服务器运行 - 尝试从文件系统访问它们。
  • 不允许访问文档对象模型(DOM)。

服务工作者用于处理离线用例和拦截请求。网络工作人员本来是通用计算的替代方案,因此您不会超载浏览器的主线程。

Demian Renzulli和Andrew Guan还专门献给section of their work来解释这些差异。

作为一个简单的类比,网络工人充当线程将以Java等编程语言作用。主线程通过新操作员启动Web Worker,将创建它,并等待执行任务。从那里,创建了主脚本和工人之间的关系。

这并不意味着从这里浏览器将自动卸载所有重型计算。开发商仍然有责任决定应委派哪些工作。让我们看看它如何与示例一起工作。

首先,我们定义将成为工人的脚本。后来,我们调用工人将脚本传递为构造函数中的第一个参数,例如:

const myWorker = new Worker('src/script.js');

启动工人的另一种方式是通过带有源代码的原始字符串,然后将其作为斑点传递。使用这种方法,您无需在单独的文件中声明该工人。

除此之外,还可以将标签脚本与类型“ JavaScript/Worker”一起使用,但是这两种方法均未在整个Internet的教程中广泛共享。绿色称这种方法为"inline worker"

const code = 'any javascript code here';const myWorker = new Worker(URL.createObjectURL(new Blob([code])));

通过创建的工人,我们可以开始与之交流。与网络工作者的通信通过事件发生,就像文档对象模型中可用的任何其他应用程序编程接口一样。要对主线程中的任何事件做出反应,我们会收听消息事件。要与创建工人的父母交流,我们使用后母语。

以下示例取自web worker specification。它描述了听取工人计算的简单示例。

请注意,在随后的代码示例中,我们使用OnMessage。这是由于其API。使用消息将要求我们改用AddEventListener。

var n = 1;
search: while (true) {
  n += 1;
  for (var i = 2; i <= Math.sqrt(n); i += 1)
    if (n % i == 0)
     continue search;
  // found a prime!
  postMessage(n);
}

然后,在index.html文件中:

<!DOCTYPE HTML>
<html lang="en">
 <head>
  <meta charset="utf-8"/>
  <title>Worker example: One-core computation</title>
 </head>
 <body>
  <p>The highest prime number discovered so far is: <output id="result"></output></p>
  <script>
   var worker = new Worker('worker.js');
   worker.onmessage = function (event) {
     document.getElementById('result').textContent = event.data;
   };
  </script>
 </body>
</html>

您也可以终止工人。这释放了分配在浏览器中的资源。终止工人意味着杀死其执行,并删除主线程与工人之间的通信。要确定它,需要再次创建工人。

有两种终止工人的方法:首先,可以从主线程调用。在此示例中,它是index.html中的脚本。您还可以从工人内部终止它(Stackoverflow具有一个question,可以潜入此主题)。

从主线程中,终止工人将是这样的:

<!DOCTYPE HTML>
<html lang="en">
 <head>
  <meta charset="utf-8" />
  <title>Worker example: One-core computation</title>
 </head>
 <body>
  <p>The highest prime number discovered so far is: <output id="result"></output></p>
  <script>
   var worker = new Worker('worker.js');
   worker.onmessage = function (event) {
     document.getElementById('result').textContent = event.data;
     worker.terminate();
   };
  </script>
 </body>
</html>

从工人中,您不需要工人。终止,您可以直接致电。给父母的消息将被发布,然后工人将立即终止:

var n = 1;
search while (true) {
  n += 1;
  for (var i = 2; i <= Math.sqrt(n); i += 1)
    if (n % i == 0)
     continue search;
  // found a prime!
  postMessage(n);
  self.close();
}

您无需在执行后终止工人,但是在没有该工人的情况下,将消耗资源。下面的图1描述了此情况(要检查工人正在发生的事情,使用了“来源”)。

在左侧,您可以看到创建的工人列表。当用户单击按钮“单击我的工作者”时创建了这些。

单击一旦创建一个工人,单击该按钮,该按钮将创建与单击的时间一样多的工人。要进行进一步检查,您可以在GitHub上查看源代码。

Figure 1: Web worker with multiple instances running, if no close function is invoked, the worker instance keeps listening to events consuming resources.

图1:具有多个实例运行的Web Worker,如果未调用关闭功能,则工人实例将继续聆听消耗资源的事件。

您可以根据需要创建尽可能多的工人 - 网络工人规范不会迫使任何硬限制。但是真实设备中的资源也受到限制,因此请注意不要创建太多。

Figure 2: Once the worker finishes its job it can invoke the close function to terminate its execution.

图2:工人完成其作业后,它可以调用关闭功能以终止其执行。

图2显示,一旦完成计算,工人就会关闭。在左侧,您只能看到一个。

使用这种方法,工人仍将被创建(与用户点击一样多次),但区别在于,每个人都会在通信工作完成后终止自己。

最后,我们需要讨论实施工人时会更改的执行模型。

与一个工人一起,有一个新的演员,并且使用脚本同步运行的代码现在将其委派给工人并等待其响应。

与创建和处置工人有相关的成本。如果我们将其与无需它的代码执行进行比较,那么生命周期就会更加复杂。

在下一部分中,我将向您介绍JSON工具,以便您了解它的作用以及它解决了什么问题。

JSON工具 - 解析和验证JSON的应用程序

要跟随本节,我认为您对React.js。

有基本知识

JSON工具是由于网络上的格式化工具在处理敏感数据时所具有的隐私而创建的。

根据ThoughtWorks in the tech radar volume 27的说法,开发人员应使用不符合数据管辖权的格式工具进行格式化或共享信息的做法。

问题在于,没有简单的方法可以保证这些工具提供私人体验,JSON工具通过开源代码来处理它的方法,并且没有使用任何类型的跟踪,伐木或cookie的用法收集用户信息。

JSON工具提供了一些功能,这些功能在使用与JSON的API一起工作时会变得方便,其中一些功能包括:

  • JSON内容验证,它显示了错误消息警告无效JSON。
  • 按钮可以轻松与剪贴板(粘贴并复制到剪贴板)。
  • 通过JSON字符串搜索(由代码镜面编辑器提供)。
  • 上传JSON文件。

此外,与剪贴板还有一种交互,使开发人员可以轻松复制/粘贴字符串。这样的功能使开发人员可以检查JSON值并进行日常活动。

Figure 3: JSON tool opened with JSON string loaded, in the left the original content, on the right, the formatted output.

图3:JSON工具用JSON String打开,左侧的原始内容,右侧为格式的输出。

该工具使用ReactJS,Tailwind,CodeMirror作为浏览器中的编辑,测试图表进行测试,格式与JSON进行处理JSON和测试,它使用Jest,它使用Jest,它使用了ReactJS应用程序的标准,它是使用Create React App。

进行引导。

它通过Snapcraft具有包装版本,但也可以通过github页面以原始格式获得。
构建一个不会跟踪任何正在使用它并尊重数据隐私的信息的JSON工具是任何程序员无需付出太多努力就可以执行的任务。

谷歌搜索“ JSON Prettier”提供了许多不同的网站。因此,处理边缘案例是挑战所在的地方。

JSON工具的结构按照ReactJS文档建议的方式进行分组,有一些组件代表应用程序的不同职责:页面,用户界面组件和核心(某些文件,例如App.tsx和The类型文件夹已被删除以适合此处)。源代码可在Github上找到。

src                                            
├── App.tsx                                    
├── components                                 
│   ├── tailwind.d.ts                          
│   └── ui                                     
│       ├── editor                             
│       │   ├── default-options.ts             
│       │   ├── EditorContainer.tsx            
│       │   └── JsonEditor.tsx                 
│       ├── Footer.tsx                         
│       ├── Header.tsx                         
│       ├── io                                 
│       │   ├── Button.tsx                     
│       │   └── InputText.tsx                  
│       ├── Label.tsx                          
│       ├── layout                             
│       │   └── Default.tsx                    
│       ├── Loading.tsx                        
│       └── menu                               
│           ├── JsonMenu.tsx                   
│           └── ResultMenu.tsx                 
├── core                                       
│   ├── cleanUp.ts                             
│   ├── formatter.ts                           
│   └── worker.js                              
├── pages                                      
│   ├── Editors.tsx                            
│   └── Settings.tsx   

被采用的结构不是出于特定原因而使用的,它主要是为了反映出一个结构,该结构对屏幕上的组件如何显示。

简而言之,每个文件夹/文件都有以下职责:

  • 页面:是存储应用程序页面的地方,主要的是editors.tsx。
  • 组件:是存储所有用户界面组件的地方,这些组件是无状态的组件,可以在整个应用程序上使用。
  • 最后但并非最不重要的是核心。格式json所在的文件夹,在此处file formatter.ts是应用程序中使用的库格式的包装器。

在下一部分中,我们将深入研究它在处理大型JSON字符串时达到的局限性。

探索没有网络工人的JSON计算的限制

该工具取决于该工具在100KB和700KB之间运行格式的硬件而不阻止用户界面。

因此,使用大于导致用户界面冻结而不响应用户互动的JSON,基于生成JSON的实验,JSON大于1MB的JSON文件足以引起此效果。

当代码用React编写时,用于处理上一节中描述的功能的代码位于以下代码段中显示的单个位置:

const onJsonChange = useCallback(async (value: string) => {
  setError('');

  if (!spacing) return;

  try {
    if (value) {
      JSON.parse(value);
    }
  } catch (e: any) {
    setError('invalid json');
  }

  let format = new Formatter(value, 2);

  const parseSpacing = parseInt(spacing);
  if (!isNaN(parseSpacing)) {
    format = new Formatter(value, parseSpacing);
  }

  const result = await format.format();

  setOriginalResult(value);
  setResult(result);
}, [spacing]);

每当用户更改所需的间距或更改JSON文本时,每当应用程序中发生更改时,都会调用函数。这种方法允许将处理JSON的守则保持在一个地方,JSON文本所需的任何更改都是集中的。

Figure 4: Sequence diagram that depicts a synchronous code execution for validating and formatting JSON.

图4:描述用于验证和格式化JSON的同步代码执行的序列图。

这也是冻结导致阻止用户界面的主线程的原因。缓解措施得出的结论是,该问题是通过使用Console.Time和Console.TimeEnd。

const onJsonChange = useCallback(async (value: string) => {
  /** skipped code **/
  try {
    console.time();
    if (value) {
      JSON.parse(value);
    }

    console.timeEnd();
  } catch (e: any) {
    setError('invalid json');
  }

  /** skipped code **/
}, [spacing]);

这种方法导致决定移动正在解析JSON并将其验证给工人的代码。有人可能会争辩说,可以检查ReactJS代码以减轻缓慢。

在ReactJ的内部未进行检查,因为时间的测量表明单个函数中完成的计算是根本原因。

请注意,第一步只是将JSON加载到CodeMirror中的操作非常快,按需完成,因为语法亮点是随着JSON视图的滚动而发生的。

与网络工作者实施JSON计算

Web工作人员的好处之一是,它必须将重量强度的计算问题从主线程中委托,购买了更复杂的代码执行流动的权衡。的确,Ido Green列出的“ Web Workers:JavaScript中的多线程程序”列出了“编码/解码大字符串”作为使用Web Worker的主要原因之一,不仅他还指出,如果您的Web应用程序需要”完成一项需要超过150毫秒的任务,您应该考虑使用Web Worker”。

换句话说,在JSON上下文中使用Web工作人员为工具的用户带来了好处,使他们能够在工具下工作时继续使用该工具,以下视频是实现Web Worker的结果。 /p>

在应用程序中实施工人的挑战是在下一节中要解决的事情的测试方面,在那里,我们将审议实施一个解析,验证和格式化JSON字符串的工人的步骤。

移动代码的第一步是与工人一起创建“请求”和“响应”的机制,换句话说,发布接收的JSON,然后将其收到是否有效,该历史记录是否有效可以在github上看到。

Figure 5: Sequence diagram that depicts the worker execution model for validating and formatting JSON.

图5:描述用于验证和格式化JSON的工人执行模型的序列图。

因此,在这一点上,达到了相同的结果,不同的实现,但是在用户界面中具有相同的冻结效果,丢失的是格式化的JSON库,该库是通过组件仍然进行的。

完成此操作后,用户界面停止了滞后,以下代码是Web Worker实现的结果,Worker验证了JSON文本并根据用户间距设置进行了格式化。

importScripts('https://unpkg.com/format-to-json@2.1.2/fmt2json.min.js');

if('function' === typeof importScripts) {
  addEventListener('message', async (event) => {
    if (!event) {
      return;
    }

    const value = event.data.jsonAsString;
    const spacing = event.data.spacing;

    if (value) {
      // eslint-disable-next-line no-undef
      const format = await fmt2json(value, {
        expand: true,
        escape: false,
        indent: parseInt(spacing)
      });

      try {
        JSON.parse(value);
      } catch (e) {
        console.error('error from worker: ', e);
        postMessage({ error: true, originalJson: value, result: format.result });
        return;
      }

      postMessage({ error: false, originalJson: value, result: format.result });
      return;
    }
    // empty json was given
    postMessage({ error: false, originalJson: value, result: value });
  });
}

根据Web Worker规范,将组件的源代码转换为使用事件,此处的添加是组件的数量。现在,该组件在安装时将工人的生命周期绑定到它:

useEffect(() => {
  worker.current = new Worker(URL.createObjectURL(new Blob([code])));
  worker.current.onmessage = (workerSelf: MessageEvent) => {
    setError('');
    if (workerSelf.data.error) {
      setError('invalid json');
    }

    setResult(workerSelf.data.result);
    setInProgress(false);
  };
}, []);

在此阶段,它等待着工人的任何新信息,并根据收入消息来更新其状态。难题的最后一部分是将JSON字符串张贴到工人,如下所示:

const onChange = (eventValue: string, eventSpacing: string) => {
  if (worker.current) {
    worker.current.postMessage({ jsonAsString: eventValue, spacing: eventSpacing });
  }
  setOriginalResult(eventValue);
  setInProgress(true);
};

在这里,从执行所有工作并冻结用户界面的先前代码,现在仅将接收的JSON字符串发布到Worker。

测试使用网络工人的应用程序

因此,使用迄今尚未涵盖的网络工人有一个具体点:测试方面。

测试像从业人员每天在浏览器中处理API时,都特别困难,这些活动负责使测试的设置更加复杂,因为大多数需要用测试双打代替。

此外,测试网络工作者在网络中可用的资源中没有得到关注,重点通常放在网络工作人员的学习方面,而不是如何在测试环境中处理这一方面。

从这个意义上讲,测试网络工作者带来了新的挑战,因为浏览器的窗口下定义了“工人”对象,使用开玩笑的对象不存在图6。

所示的对象。

Figure 6: Undefined Worker in the js-dom environment.

图6:JS-DOM环境中的未定义工人。

JSON工具的测试套件是使用React测试库开发的,因为它与任何导致复杂测试策略的特定组件没有耦合,而是工人是重构步骤,最终目标是保持相同的行为,但是现在,与Web Worker一起。

给出一个具体的测试示例,该测试与生产代码的实际实现相结合,使用JSON文件并根据用户的喜好格式化。

it('should format json from uploaded file', async () => {
  const file = new File(['{"a":"b"}'], 'hello.json', { type: 'application/json' });

  const { getByTestId } = render(<App />);

  await act(async () => {
    await userEvent.upload(getByTestId('upload-json'), file);
  });

  await waitFor(() => {
    expect(getByTestId('raw-result')).toHaveValue(`{
  "a": "b"
}`);
  })
});

感谢Jason Miller库JSDOM-WORKER提供了JS-DOM下的Worker API的实现,没有线程,但它嘲笑了API在浏览器中的行为。

要启动并运行,有两个步骤:

  1. 使用命令安装库:npm i jsdom-worker
  2. 用该行更新setuptests.ts:导入jsdom -workerâ-由于开玩笑用作测试框架,setuptests.ts或setuptests.js是添加导入的地方。因此,使用JS-DOM的其他测试框架可能需要在其框架中找到相应的设置。 setuptest是加载测试套件需要运行所需依赖项的地方,这是一个集中的地方,不需要在每个测试文件中设置JSDOM-worker。

一旦完成了正确的设置,错误就消失了,并且测试套件被执行到更改之前。

在写作时,JSDOM-WORKER不支持共享工人,在GitHub页面中,issue打开了以应对这种情况。

问题面临

jsdom-worker是应用程序所面临的任何问题的替换,因此,在开发应用程序时,有一个问题阻止脚本正常工作,因此使用单独的文件而不是blob如图7所示。

Figure 7: jsdom-worker requesting worker.js to localhost:80 and receiving an error.

图7:jsdom-worker请求worker.js到localhost:80并收到错误。

库使用获取来获取应由工人加载的脚本,因此,所使用的主机始终是Localhost:80,在这种情况下,在开发模式下运行的库是在端口3000中公开的,使得测试失败。

要注意的另一件事是,图书馆给出的示例正在使用BLOB模式,这可能表明Blob是喜欢而不是使用脚本文件。

总结,下一步去哪里

为了防止用户界面冻结,Web工作人员允许应用程序将大量计算卸载到专门的工人中,以改善用户体验。然而,其实现通过以事件为导向的同步代码执行来增加复杂性。

测试方面也受到影响,因为它需要一个库来模仿浏览器中的Web工作人员的行为,因此,库有两个主要局限性:

  • 它不支持脚本,因为浏览器通常会做
  • 它不支持共享工人。

真正的好处在于方程式的用户端,尽管在JSON计算方面并没有提高任何性能,但它可以防止浏览器在任务过程中冻结中断用户。这就是最重要的,因为Proxx的用例也显示了这些好处。