在web worker中使用fetch实例详解
Youky 人气:01.Web Worker意义
由于 JS 是单线程的,费时的 JS 操作将会导致整个页面的阻塞。Web Worker 提供了创建多线程的方法,将一些耗时且 UI 无关的工作交给 worker,可提高页面的使用体验。
限制:
同源策略:worker 线程执行的脚本要和当前页面同源
API 限制:
- 不能操作 DOM
- 不能使用 window 的全局变量,但可以使用 navigator 和 location 对象
- 不能使用 alert、confirm 方法
- 无法读取本地文件
和主线程不在一个上下文环境,通讯要通过 postMessage
完成
2. 主线程的使用
创建
创建一个子线程,要传入一个脚本的 URL。如果该脚本加载失败,则 Worker 会静默失败
const worker = new Worker('url');
如果要在本文件中描述执行的内容,可以用 Blob 和 window.URL.createObjectURL 生成一个 URL
function createWorker(f) { const blob = new Blob(['(' + f.toString() +')()']); const url = window.URL.createObjectURL(blob); const worker = new Worker(url); return worker; }
通信
- 主线程 => 子线程
worker.postMessage(param);
参数可以是任意类型,包括二进制数据。但传递是拷贝形式而不是引用形式。因此对于大数据会存在性能问题。
- 子线程 => 主线程
worker.onmessage = function (event) { console.log('Received message ' + event.data); }
错误处理
worker.onerror(function (e) { console.log([ 'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message ].join('')); });
关闭
worker.terminate();
3. 子线程的使用
子线程中无法使用 window,self
代表全局对象
和主线程的通信
- 主线程 => 子线程
self.addEventListener('message', function (e) { self.postMessage('Received: ' + e.data); }, false);
- 子线程 => 主线程
self.postMessage('something');
加载其他脚本
在子线程中加载其他脚本:
importScripts('script1.js', 'script2.js');
关闭
self.close();
4. 在WebWorker中使用fetch
网络请求是和DOM无关且可能耗时较长的操作,worker线程支持使用Fetch,是适合放在worker中进行的操作。
而要在worker中使用fetch,如果每次都要自己处理线程间的通信的话,会十分麻烦,因此我对通信进行了封装,写成了一个可以直接使用的库。
安装依赖:
npm i web-worker-fetch
使用时先实例化一个WF对象,然后就可以像使用fetch一样在worker中使用fetch:
import WebWorkerFetch from "web-worker-fetch"; const wf = new WebWorkerFetch(); wf.fetch("url", { method: "POST", // *GET, POST, PUT, DELETE, etc. mode: "cors", // no-cors, *cors, same-origin cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached credentials: "same-origin", // include, *same-origin, omit headers: { "Content-Type": "application/json" // 'Content-Type': 'application/x-www-form-urlencoded', }, redirect: "follow", // manual, *follow, error referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url body: JSON.stringify(data) // body data type must match "Content-Type" header }).then((res) => console.log(res));
除此之外,借鉴 axios
的思路,配置中可以提供 requestInterceptor
和 responseInterceptor
,对请求参数和返回数据做统一处理
5. 实现思路
这个库的封装主要是解决了两个问题:
- 发送请求时如何把参数传递给worker线程
- 请求结束后如何从worker线程获取结果
对于第一个问题,主线程使用 ostMessage
向worker线程传递参数。
对于第二个问题,worker线程通过 self.postMessage
向主线程传递消息,主线程通过 worker.onmessage
监听消息。
此时就引出了问题所在:如果多次使用 wf.fetch
发送请求,那么在一个请求完成后,worker线程触发的消息将让所有请求处都认为请求已完成。
因此,在每次请求时,使用一个fetchId确定该请求做唯一性。将该id传给worker线程,后续worker线程向主线程通信时也会带上这个id。
在主线程中监听onmessage事件时,判断id是否和自己的请求id一致,只有在相同时才做处理。
具体的实现大家可以移步仓库源码,实际上也非常简单。
加载全部内容