近期使用Worker线程,发现在其内部无法调用fetch方法,但可以使用原生的XMLHttpRequest对象、Headers对象、Request对象、Response对象、URLSearchParams对象,所以就模拟封装了一个可在Worker线程使用的 ajax 函数。
目录
一、源码
/**
* [基于 promise 封装的 ajax 方法]
* @param {string} url - [路径]
* @param {object} init - [配置对象,还不是很完善]
* return - 若成功则调用 resolve 并向 then() 传递结果。若失败则调用 reject 并传递错误对象
*
* init 可配置属性:
* method - 请求方法,默认 'GET',不区分大小写
* body - 传递参数,'GET / HEAD' 请求不许有参数
* headers - 头信息,该怎么写就这么写
*
* Tips:
* 1. 基于 promise 的简单封装,所以可以使用 then() 方法
* 2. 可以在 worker 线程使用(将该函数放在 worker 线程再调用)
*/
function _fetch(url, init) {
return new Promise((resolve, reject) => {
// 若空参则直接返回 reject()
if (!url && !init) {
// 判断当前线程
let thread = '';
if (/window/gi.test(self.constructor.name)) {
thread = 'Window';
} else if (/worker/gi.test(self.constructor.name)) {
thread = 'Worker';
}
// 创建错误对象
const err = new TypeError(`Failed to execute '_fetch' on '${thread}': 1 argument required, but only 0 present.`);
// 返回 reject()
return reject(err);
}
// 初始化请求方法
let method = !!init && !!init.method ? init.method : 'GET';
// 初始化传递参数
let params = null;
// 创建请求实例
const xhr = new XMLHttpRequest();
// 初始化请求
xhr.open(method, url, true);
// 设置头信息
if (!!init && !!init.headers) {
// 遍历添加头信息
for (let key in init.headers) {
// 过滤非自身属性
if (init.headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, init.headers[key]);
}
}
}
// 设置请求得到的数据类型
// 选blob,因为 Response 实例有 text()/json()/blob() 等方法进行转换
xhr.responseType = 'blob';
// 响应
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 请求成功
if ( (xhr.status >= 200 && xhr.status < 300)
|| (xhr.status === 304) ) {
// 创建 Response 实例
const response = new Response(this.response);
// 返回 resolve() 并传递 Response 实例给 then()
return resolve(response);
// 请求失败
} else {
// 错误提示字符串内容
const err = new TypeError('Failed to _fetch');
// 返回 reject() 并传递 Error 实例
return reject(err);
}
}
};
// GET / HEAD 请求不能发送 data
if (!!init && !!init.body
&& method.toUpperCase() !== 'GET'
&& method.toUpperCase() !== 'HEAD'){
// 处理json格式与字符串两种参数
if (({}).toString.call(init.body) === '[object Object]') {
params = new URLSearchParams(init.body);
} else {
params = init.body;
}
}
// 发送参数
xhr.send(params);
});
}
// ----------------- 示例 -----------------
// 1. 获取 blob
// _fetch('./demo.jpg')
// .then(res => res.blob())
// .then(res => conole.log(res));
// 2. 获取 json
// _fetch('./demo.json')
// .then(res => res.json())
// .then(res => console.log(res));
// 3. 获取 string
// _fetch('./demo.txt')
// .then(res => res.text())
// .then(res => console.log(res));
二、使用方法
1. 在主线程中使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="demo">
<div class="txt"></div>
<div class="pic"></div>
</div>
<script>
// 获取 json 数据
// _fetch('https://api.btstu.cn/yan/api.php?charset=utf-8&encode=json')
// .then(res => res.json())
// .then(json => {
// console.log(json);
// document.querySelector('.txt').innerHTML = json.text;
// });
// 获取 blob 数据
// _fetch('https://unsplash.it/1600/900')
// .then(res => res.blob())
// .then(blob => {
// console.log(blob);
// const img = document.createElement('img');
// img.src = URL.createObjectURL(blob); // 生成 URL 实例
// img.onload = function () {
// this.width = 500;
// document.querySelector('.pic').appendChild(this);
// URL.revokeObjectURL(this.src); // 释放 URL 实例
// }
// });
// 使用 async / await 语法改写,使得语义更清晰
(async function() {
// json
const response1 = await _fetch('https://api.btstu.cn/yan/api.php?charset=utf-8&encode=json');
const json = await response1.json();
document.querySelector('.txt').innerHTML = json.text;
// blob
const response2 = await _fetch('https://unsplash.it/1600/900');
const blob = await response2.blob();
const img = document.createElement('img');
img.src = URL.createObjectURL(blob); // 生成 URL 实例
img.onload = function () {
this.width = 500;
document.querySelector('.pic').appendChild(this);
URL.revokeObjectURL(this.src); // 释放 URL 实例
}
})();
// _fetch 封装
function _fetch(url, init) {
return new Promise((resolve, reject) => {
// 若空参则直接返回 reject()
if (!url && !init) {
// 判断当前线程
let thread = '';
if (/window/gi.test(self.constructor.name)) {
thread = 'Window';
} else if (/worker/gi.test(self.constructor.name)) {
thread = 'Worker';
}
// 创建错误对象
const err = new TypeError(`Failed to execute '_fetch' on '${thread}': 1 argument required, but only 0 present.`);
// 返回 reject()
return reject(err);
}
// 初始化请求方法
let method = !!init && !!init.method ? init.method : 'GET';
// 初始化传递参数
let params = null;
// 创建请求实例
const xhr = new XMLHttpRequest();
// 初始化请求
xhr.open(method, url, true);
// 设置头信息
if (!!init && !!init.headers) {
// 遍历添加头信息
for (let key in init.headers) {
// 过滤非自身属性
if (init.headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, init.headers[key]);
}
}
}
// 设置请求得到的数据类型
// 选blob,因为 Response 实例有 text()/json()/blob() 等方法进行转换
xhr.responseType = 'blob';
// 响应
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 请求成功
if ( (xhr.status >= 200 && xhr.status < 300)
|| (xhr.status === 304) ) {
// 创建 Response 实例
const response = new Response(this.response);
// 返回 resolve() 并传递 Response 实例给 then()
return resolve(response);
// 请求失败
} else {
// 错误提示字符串内容
const err = new TypeError('Failed to _fetch');
// 返回 reject() 并传递 Error 实例
return reject(err);
}
}
};
// GET / HEAD 请求不能发送 data
if (!!init && !!init.body
&& method.toUpperCase() !== 'GET'
&& method.toUpperCase() !== 'HEAD'){
// 处理json格式与字符串两种参数
if (({}).toString.call(init.body) === '[object Object]') {
params = new URLSearchParams(init.body);
} else {
params = init.body;
}
}
// 发送参数
xhr.send(params);
});
}
</script>
</body>
</html>
2. 在 Worker 线程中使用
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="demo">
<div class="txt"></div>
<div class="pic"></div>
</div>
<script>
const worker = new Worker('work.js');
worker.onmessage = function (e) {
console.log(e.data[0], e.data[1]);
// txt
document.querySelector('.txt').innerHTML = e.data[0].text;
// pic
const img = document.createElement('img');
img.src = URL.createObjectURL(e.data[1]); // 生成 URL 实例
img.onload = function () {
this.width = 500;
document.querySelector('.pic').appendChild(this);
URL.revokeObjectURL(this.src); // 释放 URL 实例
}
}
</script>
</body>
</html>
- work.js
// 使用 async / await 语法改写,使得语义更清晰
(async function() {
// json
const response1 = await _fetch('https://api.btstu.cn/yan/api.php?charset=utf-8&encode=json');
const json = await response1.json();
// blob
const response2 = await _fetch('https://unsplash.it/1600/900');
const blob = await response2.blob();
// 方便主线程读取
const data = [json, blob];
self.postMessage(data);
})();
// _fetch 封装
function _fetch(url, init) {
return new Promise((resolve, reject) => {
// 若空参则直接返回 reject()
if (!url && !init) {
// 判断当前线程
let thread = '';
if (/window/gi.test(self.constructor.name)) {
thread = 'Window';
} else if (/worker/gi.test(self.constructor.name)) {
thread = 'Worker';
}
// 创建错误对象
const err = new TypeError(`Failed to execute '_fetch' on '${thread}': 1 argument required, but only 0 present.`);
// 返回 reject()
return reject(err);
}
// 初始化请求方法
let method = !!init && !!init.method ? init.method : 'GET';
// 初始化传递参数
let params = null;
// 创建请求实例
const xhr = new XMLHttpRequest();
// 初始化请求
xhr.open(method, url, true);
// 设置头信息
if (!!init && !!init.headers) {
// 遍历添加头信息
for (let key in init.headers) {
// 过滤非自身属性
if (init.headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, init.headers[key]);
}
}
}
// 设置请求得到的数据类型
// 选blob,因为 Response 实例有 text()/json()/blob() 等方法进行转换
xhr.responseType = 'blob';
// 响应
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 请求成功
if ( (xhr.status >= 200 && xhr.status < 300)
|| (xhr.status === 304) ) {
// 创建 Response 实例
const response = new Response(this.response);
// 返回 resolve() 并传递 Response 实例给 then()
return resolve(response);
// 请求失败
} else {
// 错误提示字符串内容
const err = new TypeError('Failed to _fetch');
// 返回 reject() 并传递 Error 实例
return reject(err);
}
}
};
// GET / HEAD 请求不能发送 data
if (!!init && !!init.body
&& method.toUpperCase() !== 'GET'
&& method.toUpperCase() !== 'HEAD'){
// 处理json格式与字符串两种参数
if (({}).toString.call(init.body) === '[object Object]') {
params = new URLSearchParams(init.body);
} else {
params = init.body;
}
}
// 发送参数
xhr.send(params);
});
}
三、Some Tips
- Worker 线程无法读取 window 对象,所以无法使用原生 fetch,但可以使用 XMLHttpRequest 对象(还有与AJAX相关的对象:Headers、Request、Response…),所以可以进行 ajax 操作,在 Worker 线程封装一个 fetch();
- Worker 线程无法使用 DOM 对象,所以无法进行 DOM 操作,而需要将数据发送给主线程,再由主线程进行相关 DOM 操作;
- 为了尽可能的去还原 fetch,所以在封装里使用了 Response 对象,并将其实例传递给 then(),即,fetch().then() 里可以使用
response.json()
读取数据,在 _fetch().then() 中也是同样的操作(连报错提示也尽可能的去还原了);
四、获取 XML 数据
虽然原生的 AJAX(XMLHttpRequest 对象)可以通过 xhr.responseType = 'document'
来规定返回响应数据的类型为 ‘document’ 结构,读取 XML/HTML 数据,但 fetch() 和 _fetch()并没有响应的方法调用,去读取 XML/HTML(是的,就是要尽可能的去还原)。其主要原因是 Response 对象 并未提供对应转换方法。
所以,得借助 DOMParser 对象 去实现获取。
【关于 xhr.responseType
,在 _fetch() 源码中设置的是 ‘blob’ 类型,如果更改,将无法正常调用相关读取方法,如:text()、json()、blob()等】
XML 部分
- demo.xml
<?xml version="1.0" encoding="utf-8" ?>
<student>
<member>
<name>张三</name>
<age>14</age>
<gender>男</gender>
</member>
<member>
<name>李四</name>
<age>16</age>
<gender>男</gender>
</member>
<member>
<name>王五</name>
<age>17</age>
<gender>男</gender>
</member>
<member>
<name>赵六</name>
<age>13</age>
<gender>女</gender>
</member>
<member>
<name>田七</name>
<age>15</age>
<gender>女</gender>
</member>
</student>
1. 主线程读取 XML 数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="xml"></div>
<script>
// _fetch('demo.xml')
// .then(res => res.text())
// .then(str => {
// // 创建解析对象
// const parser = new DOMParser();
// // 解析 XML 字符串,并传递给下一个 then()
// return parser.parseFromString(str, 'application/xml');
// })
// .then(data => {
// console.log(data.firstChild);
// // 获取 XML 第一个子节点,并添加给一个 DOM 元素
// document.querySelector('.xml').appendChild(data.firstChild);
// });
// 使用 async / await 语法改写,使得语义更清晰
(async function() {
// 发送请求
const response = await _fetch('demo.xml');
// 获取字符串
const str = await response.text();
// 创建解析对象
const parser = new DOMParser();
// 解析 XML 字符串,并得到 XML Document
const xmlDocument = parser.parseFromString(str, 'application/xml');
// 将 XML 第一个字节点添加给 DOM 元素
document.querySelector('.xml').appendChild(xmlDocument.firstChild);
})();
// _fetch 封装
function _fetch(url, init) {
return new Promise((resolve, reject) => {
// 若空参则直接返回 reject()
if (!url && !init) {
// 判断当前线程
let thread = '';
if (/window/gi.test(self.constructor.name)) {
thread = 'Window';
} else if (/worker/gi.test(self.constructor.name)) {
thread = 'Worker';
}
// 创建错误对象
const err = new TypeError(`Failed to execute '_fetch' on '${thread}': 1 argument required, but only 0 present.`);
// 返回 reject()
return reject(err);
}
// 初始化请求方法
let method = !!init && !!init.method ? init.method : 'GET';
// 初始化传递参数
let params = null;
// 创建请求实例
const xhr = new XMLHttpRequest();
// 初始化请求
xhr.open(method, url, true);
// 设置头信息
if (!!init && !!init.headers) {
// 遍历添加头信息
for (let key in init.headers) {
// 过滤非自身属性
if (init.headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, init.headers[key]);
}
}
}
// 设置请求得到的数据类型
// 选blob,因为 Response 实例有 text()/json()/blob() 等方法进行转换
xhr.responseType = 'blob';
// 响应
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 请求成功
if ( (xhr.status >= 200 && xhr.status < 300)
|| (xhr.status === 304) ) {
// 创建 Response 实例
const response = new Response(this.response);
// 返回 resolve() 并传递 Response 实例给 then()
return resolve(response);
// 请求失败
} else {
// 错误提示字符串内容
const err = new TypeError('Failed to _fetch');
// 返回 reject() 并传递 Error 实例
return reject(err);
}
}
};
// GET / HEAD 请求不能发送 data
if (!!init && !!init.body
&& method.toUpperCase() !== 'GET'
&& method.toUpperCase() !== 'HEAD'){
// 处理json格式与字符串两种参数
if (({}).toString.call(init.body) === '[object Object]') {
params = new URLSearchParams(init.body);
} else {
params = init.body;
}
}
// 发送参数
xhr.send(params);
});
}
</script>
</body>
</html>
2. Worker 线程读取 XML 数据
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="xml"></div>
<script>
const worker = new Worker('work.js');
worker.onmessage = function (e) {
// 创建解析对象
const parser = new DOMParser();
// 解析 XML 字符串,并得到 XML Document
const xmlDocument = parser.parseFromString(e.data, 'application/xml');
// 将 XML 第一个字节点添加给 DOM 元素
document.querySelector('.xml').appendChild(xmlDocument.firstChild);
}
</script>
</body>
</html>
- work.js
// 使用 async / await 语法改写,使得语义更清晰
(async function() {
// 发送请求
const response = await _fetch('demo.xml');
// 获取字符串
const str = await response.text();
// Worker 无法访问 window 对象,自然无法使用 DOMParser()
self.postMessage(str);
})();
// _fetch 封装
function _fetch(url, init) {
return new Promise((resolve, reject) => {
// 若空参则直接返回 reject()
if (!url && !init) {
// 判断当前线程
let thread = '';
if (/window/gi.test(self.constructor.name)) {
thread = 'Window';
} else if (/worker/gi.test(self.constructor.name)) {
thread = 'Worker';
}
// 创建错误对象
const err = new TypeError(`Failed to execute '_fetch' on '${thread}': 1 argument required, but only 0 present.`);
// 返回 reject()
return reject(err);
}
// 初始化请求方法
let method = !!init && !!init.method ? init.method : 'GET';
// 初始化传递参数
let params = null;
// 创建请求实例
const xhr = new XMLHttpRequest();
// 初始化请求
xhr.open(method, url, true);
// 设置头信息
if (!!init && !!init.headers) {
// 遍历添加头信息
for (let key in init.headers) {
// 过滤非自身属性
if (init.headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, init.headers[key]);
}
}
}
// 设置请求得到的数据类型
// 选blob,因为 Response 实例有 text()/json()/blob() 等方法进行转换
xhr.responseType = 'blob';
// 响应
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 请求成功
if ( (xhr.status >= 200 && xhr.status < 300)
|| (xhr.status === 304) ) {
// 创建 Response 实例
const response = new Response(this.response);
// 返回 resolve() 并传递 Response 实例给 then()
return resolve(response);
// 请求失败
} else {
// 错误提示字符串内容
const err = new TypeError('Failed to _fetch');
// 返回 reject() 并传递 Error 实例
return reject(err);
}
}
};
// GET / HEAD 请求不能发送 data
if (!!init && !!init.body
&& method.toUpperCase() !== 'GET'
&& method.toUpperCase() !== 'HEAD'){
// 处理json格式与字符串两种参数
if (({}).toString.call(init.body) === '[object Object]') {
params = new URLSearchParams(init.body);
} else {
params = init.body;
}
}
// 发送参数
xhr.send(params);
});
}
五、关于报错
1. 无参调用(还原了 fetch)
- 主线程调用
- Worker 线程调用
2. 请求失败(路径正确,但服务器未返回有效数据)