最近在深入研究 TanStack Query
在项目中的各种高阶用法,发现结合 Fetch API
封装接口请求方法也还挺好用的,它不仅是原生的高性能 API
,还能避免引入一个库(通常是 Axios
)。而且好多主流的网站实际上都使用了 Fetch API
:
然后就想着深入学习一下前端中常见的一些网络资源请求相关API和工具之间的演进和区别。
先抛出一个问题,浏览器中的 http请求
有哪些方式?有这些:
URL
Links
JavaScript(window.location.href = 'http://www.google.com'
)XMLHttpRequest (XHR)
Fetch API
Axios
WebSocket
在实际开发中,XHR
和 Fetch API
是发送异步 HTTP
请求的两种主要方式,在使用它们时需要注意跨域问题和安全问题。Axios
提供了更多易于使用的功能和选项,使得发送 HTTP
请求更加方便和灵活。WebSocket
则适用于需要实现实时双向通信的应用场景。
日常开发更多可能接触到的是Ajax
、Fetch API
、Axios
三种:
Ajax
:是一种早期的用于在浏览器中发送异步HTTP
请求的技术。Ajax
通过XMLHttpRequest
对象来发送请求,并通过回调函数处理响应数据。Ajax
的优点是简单易用,缺点是需要手动编写大量的回调函数来处理请求和响应,代码可读性较差。Fetch API
:是一个新的JavaScript API
,用于在浏览器中发送异步HTTP
请求。Fetch
使用Promise
对象来处理请求和响应,支持链式调用和异步处理,代码可读性较好。Fetch
的优点是支持跨域请求、使用标准的Promise API
、返回的是Response
对象,可以使用各种方法处理响应数据。缺点是不兼容低版本的浏览器,需要使用polyfill
来解决。Axios
:是一个流行的JavaScript HTTP
客户端库,用于在浏览器和Node.js
环境中发送HTTP
请求。Axios
使用Promise
对象来处理请求和响应,支持链式调用和异步处理,代码可读性较好。Axios
的优点是具有丰富的功能和选项,如请求取消、拦截请求和响应、转换请求和响应数据等。缺点是需要手动引入库文件,增加了代码量和体积。
接下来,我们就详细总结主要的三种方式,一次性彻底搞懂它们之间的区别。
1、Ajax
1.1 什么是 Ajax
Asynchronous JavaScript + XML
(异步 JavaScript 和 XML), 其本身不是一种新技术,而是一个在 2005 年被Jesse James Garrett
提出的新术语,用来描述一种使用现有技术集合的‘新’方法,包括:HTML 或 XHTML, CSS, JavaScript, DOM, XML (en-US), XSLT, 以及最重要的XMLHttpRequest
。当使用结合了这些技术的 AJAX 模型以后,网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面。这使得程序能够更快地回应用户的操作。
尽管 X 在 Ajax 中代表 XML,但由于JSON的许多优势,比如更加轻量以及作为 Javascript 的一部分,目前 JSON 的使用比 XML 更加普遍。JSON 和 XML 都被用于在 Ajax 模型中打包信息。
1.2 Ajax 基本使用
1.2.1 发出 HTTP 请求
要使用 JavaScript 向服务器发出 HTTP 请求,你需要一个具有必要功能的对象实例 XMLHttpRequest
:
const httpRequest = new XMLHttpRequest();
发出请求后,你将收到响应。在此阶段,你需要XMLHttpRequest
通过将对象的属性设置为请求更改状态时调用的函数来告诉对象哪个 JavaScript 函数将处理响应onreadystatechange
,如下所示:
function handler() {
// ...
}
httpRequest.onreadystatechange = handler;
请注意:函数名称后没有括号或参数,因为你是在为函数分配一个引用,而不是实际调用它。或者,你可以使用动态定义函数的 JavaScript 技术(称为“匿名函数”)来定义将处理响应的操作,而不是提供函数名称,如下所示:
httpRequest.onreadystatechange = () => {
// ...
};
接下来,在声明收到响应时会发生什么之后,你需要通过调用HTTP 请求对象的open()
和方法来实际发出请求,如下所示:send()
httpRequest.open("GET", "http://www.example.org/some.file", true);
httpRequest.send();
- 调用
open()
的第一个参数是HTTP请求方法 ——GET
、POST
、HEAD
,或其他你的服务器支持的方法。根据HTTP标准,保持该方法的全大写,否则一些浏览器(如Firefox
)可能无法处理该请求。关于可能的HTTP
请求方法的更多信息,请查看 规范。
第二个参数是你要发送请求的URL。作为一项安全
feature
,你默认不能调用第三方域名的URL。请确保在你所有的页面上使用准确的域名,否则当你调用open()
时,你会得到一个 “权限拒绝 “的错误。一个常见的陷阱是用domain.tld
访问你的网站,但试图用www.domain.tld
来调用页面。如果你真的需要向另一个域名发送请求,请参阅 HTTP 访问控制 (CORS)。可选的第三个参数设置请求是否异步。如果是(默认)
true
,JavaScript 将继续执行,并且用户可以在服务器响应尚未到达时与页面进行交互。这是AJAX
中的第一个 A。
send()
方法的参数可以是任何你想发送给服务器的数据,如果是 POST-ing
请求。表单数据应该以服务器可以解析的格式发送,如查询字符串。
"name=value&anothername="+encodeURIComponent(myVar)+"&so=on"
或其他格式,如multipart/form-data
、JSON
、XML
等。
请注:如果你想要
POST
数据,你可能必须设置请求的 MIME 类型。例如,在调用send()
作为查询字符串发送的表单数据之前使用以下内容:
httpRequest.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded"
);
1.2.2 处理服务器响应
当你发送请求时,您提供了一个 JavaScript
函数的名称来处理响应:
httpRequest.onreadystatechange = nameOfTheFunction;
这个功能应该做什么?首先,函数需要检查请求的状态。如果 state
的值为XMLHttpRequest.DONE
(对应4),表示已经收到完整的服务器响应,可以继续处理了。
if (httpRequest.readyState === XMLHttpRequest.DONE) {
// 处理响应
} else {
// 处理异常
}
值的完整列表readyState
记录在XMLHTTPRequest.readyState中,如下所示:
- 0(
uninitialized
)- 请求未初始化 - 1(
loading
)- 建立服务器连接 - 2(
loaded
)- 收到请求 - 3(
interactive
)- 处理请求 - 4(
complete
)- 请求完成且响应准备就绪
接下来,检查HTTP 响应的 HTTP 响应状态代码。在以下示例中,我们通过检查响应代码来区分成功和不成功的 AJAX 调用200 OK
。
if (httpRequest.status === 200) {
// 请求成功~
} else {
// 请求出错~
}
检查请求的状态和响应的 HTTP
状态代码后,你可以对服务器发送的数据做任何您想做的事情。你有两个选项来访问该数据:
httpRequest.responseText
– 将服务器响应作为文本字符串返回httpRequest.responseXML
– 将响应作为一个XMLDocument
对象返回,你可以使用JavaScript DOM
函数遍历
请注意,上述步骤仅在你使用异步请求时有效( 的第三个参数
open()
未指定或设置为true
)。如果你使用同步请求,则无需指定函数,但强烈建议不要这样做,因为它会带来糟糕的用户体验。
1.2.3 一个完整的例子
<!DOCTYPE html>
<html lang="en">
<head>
<title>Home</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
</head>
<body>
<button id="ajaxButton" type="button">Make a request</button>
</body>
<script>
(() => {
let httpRequest;
document.getElementById('ajaxButton').addEventListener('click', makeRequest);
function makeRequest() {
// 1. 创建XMLHttpRequest对象
httpRequest = new XMLHttpRequest();
// 2. 检查是否成功创建XMLHttpRequest对象,如果失败,则显示错误消息并返回false
if (!httpRequest) {
alert('Giving up :( Cannot create an XMLHTTP instance');
return false;
}
// 3. 设置XMLHttpRequest对象的回调函数,当XMLHttpRequest对象的状态发生变化时,将调用该函数
httpRequest.onreadystatechange = alertContents;
// 4. 初始化XMLHttpRequest对象,指定HTTP请求的类型和URL
httpRequest.open('GET', 'https://fakestoreapi.com/products/1');
// 5. 发送HTTP请求
httpRequest.send();
}
/*
如果发生通信错误(例如服务器宕机),则在 onreadystatechange 访问响应状态时方法中会抛出异常。
为了缓解这个问题,可以将 if...else 语句包装在 try...catch。
*/
function alertContents() {
try {
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
alert(httpRequest.responseText);
} else {
alert('There was a problem with the request.');
}
}
} catch (e) {
alert(`Caught Exception: ${e.description}`);
}
}
})();
</script>
</html>
测试:
在这个例子中:
- 用户点击“提出请求”按钮;
- 事件处理程序调用
makeRequest()
函数; - 发出请求,然后 (
onreadystatechange
) 将执行传递给alertContents()
; alertContents()
检查是否收到响应并确定,然后alert()
是接口的内容。
注意1: 如果您不设置标头,
Cache-Control: no-cache
浏览器将缓存响应并且永远不会重新提交请求,从而使调试变得困难。您还可以添加一个始终不同的 GET 参数,例如时间戳或随机数(参阅 绕过缓存)注意2: 如果
httpRequest
全局使用该变量,则竞态函数调用makeRequest()
会相互覆盖,从而导致竞态条件。将httpRequest
变量声明为包含 AJAX 函数的 闭包可以避免这种情况
1.3 Asynchronous JS
Asynchronous JS
:指的是 JavaScript 以非阻塞方式运行的能力。
想象一下,如果每个需要时间给我们响应的网络请求都阻止了任何其他操作的执行?整个互联网将处于停滞状态。为处理异步代码而开发的初始方法是使用回调来提供一个在请求被 resolve
后运行的函数。
以下代码片段是用于处理异步 downloadPhoto
函数结果的回调示例:
downloadPhoto('http://coolcats.com/cat.gif', handlePhoto)
funcion handlePhoto(error, photo) {
if (error) {
console.error('Download error!', error)
} else {
console.log('Download finished', photo)
}
}
console.log('Download started')
- 虽然回调很重要,但它们可能会导致所谓的回调地狱:
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})
为了更好地理解正确的异步回调用法,有一个很好的网站叫 callbackhell.com,它很好地介绍了组成异步回调函数的最佳实践,避免了可怕的 “回调地狱”。
1.4 Ajax 的优缺点
Ajax(Asynchronous JavaScript and XML)
是一种用于在 Web
页面中实现异步通信的技术,通过在页面不刷新的情况下,使得用户可以与服务器进行数据交互。以下是 Ajax
的优缺点:
优点:
- 减少页面刷新:
Ajax
可以在页面不刷新的情况下获取和显示数据,减少了用户等待时间和流量消耗。 - 提高用户体验:由于
Ajax
可以实现异步请求和响应,使得用户可以在不中断操作的情况下获取数据,从而提高了用户的体验。 - 减轻服务器压力:由于
Ajax
可以部分更新页面,减少了服务器处理请求的次数,从而减轻了服务器的压力。 - 支持多种数据格式:
Ajax
可以支持多种数据格式,如XML
、JSON
等,使得数据的传输和处理更加灵活。
缺点:
- 对
SEO
不友好:由于Ajax
的异步请求不会刷新整个页面,搜索引擎很难获取Ajax
加载的数据,从而降低了网站的SEO
优化效果。 - 安全性问题:
Ajax
可能会导致跨站点脚本攻击(Cross-site scripting, XSS)
和跨站点请求伪造(Cross-site request forgery, CSRF)
等安全问题,开发人员需要采取相应的安全措施。 - 开发复杂度高:
Ajax
的开发需要涉及到多个技术领域,包括HTML
、CSS
、JavaScript
、XML
或JSON
等,开发人员需要具备多方面的技能,开发难度较大。
2、Fetch API
2.1 什么是 Fetch?
Fetch API
ES6 之后出现的基于 Promise
的一个强大而灵活的JavaScript库,是一个现代的网络请求API,可以使客户端与服务器之间的通信变得更加容易和直观。它提供了一种简单的方法来发送和接收数据,并支持各种HTTP请求和响应类型。
在使用Fetch API时,请注意其异步性质,并确保正确处理响应和错误。
以下是使用Fetch API发送GET请求的示例代码:
fetch('https://example.com/data.json')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
在这个例子中,我们向https://example.com/data.json
发送了一个GET请求,并在响应中获得JSON数据。使用.then()
方法来处理响应数据,使用.catch()
方法来处理任何错误。
2.1.1 发送GET请求
要发送GET请求,只需调用 fetch()
函数并传递URL作为参数即可。fetch()
返回一个 Promise
对象,该对象将在响应可用时解析为 Response
对象。
fetch('https://example.com/data.json')
.then(response => console.log(response))
.catch(error => console.error(error));
在上面的例子中,我们使用 console.log()
方法输出 Response
对象。如果请求成功,该对象将包含有关响应的信息(如状态代码和头信息)。如果请求失败,则 catch()
方法将被调用。
2.1.2 发送POST请求
要发送 POST
请求,需要创建一个包含请求选项的对象,并将其作为fetch()
函数的第二个参数传递。请求选项对象应包含请求的方法、标题、正文等信息。
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'JohnDoe', password: 'mySecretPassword' })
};
fetch('https://example.com/login', requestOptions)
.then(response => console.log(response))
.catch(error => console.error(error));
在这个例子中,我们向https://example.com/login
发送一个POST请求,正文为一个JSON字符串。请注意,headers属性
必须设置为包含 Content-Type
标头的对象,以指示请求正文的类型。
2.1.3 处理响应数据
要处理响应数据,可以使用 Response对象
提供的方法。例如,使用.json()
方法将响应正文解析为 JSON格式
的数据:
fetch('https://example.com/data.json')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
在上面的例子中,我们使用.json()
方法将响应正文解析为JSON格式的数据,并使用 console.log()
方法输出解析后的数据。
2.1.4 处理错误
如果请求失败,catch()
方法将被调用。您可以在 catch()
方法中处理错误,并使用 console.error()
方法将其输出到控制台。
fetch('https://example.com/data.json')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
在上面的例子中,我们使用 console.error()
方法输出错误信息。
2.1.5 处理HTTP错误
如果服务器返回HTTP错误状态代码(例如404或500),fetch()
方法不会引发错误。相反,它会将响应对象传递给 then()
方法。您可以使用 Response
对象的属性来确定响应的状态代码,并相应地处理响应。
fetch('https://example.com/data.json')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error(error));
在上面的例子中,我们使用 Response对象
的.ok属性
来检查响应的状态代码是否成功(即为200-299之间的数字)。如果不是,我们将抛出一个错误,以使 catch()
方法被调用。
2.1.6 自定义请求头
您可以使用 Headers对象
设置自定义请求头。例如,您可以设置 Authorization
标头以进行身份验证。
const myHeaders = new Headers();
myHeaders.append('Authorization', 'Bearer myToken');
fetch('https://example.com/data', { headers: myHeaders })
.then(response => console.log(response))
.catch(error => console.error(error));
在这个例子中,我们创建了一个包含 Authorization标头
的 Headers对象
,并将其传递给 fetch()
函数的请求选项。
2.1.7 使用async/await语法
Fetch API
也可以使用 async/await
语法进行异步请求。下面是一个使用 async/await
语法的示例。
async function fetchData() {
try {
const response = await fetch('https://example.com/data.json');
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
fetchData();
在这个例子中,我们定义了一个异步函数 fetchData()
,它使用 async
关键字进行标记。在函数中,我们使用await关键字来等待fetch()
函数的响应,并将其存储在 response
变量中。然后,我们使用await关键字等待将响应解析为JSON的过程,并将结果存储在data变量中。
使用 async/await
语法可以使代码更加简洁和易于理解,特别是对于异步请求和响应的处理。
2.2 Headers
Fetch API Headers
的接口允许您对HTTP 请求和响应标头执行各种操作。这些操作包括从请求的标头列表中检索、设置、添加和删除标头。
一个Headers
对象有一个关联的头列表,它最初是空的,由零个或多个名称和值对组成。您可以使用类似的方法添加到此append()
(请参阅示例。)在该接口的所有方法中,标头名称通过不区分大小写的字节序列进行匹配。
出于安全原因,某些标头只能由用户代理控制。这些标头包括禁止的标头名称和禁止的响应标头名称。
Headers 对象也有一个关联的守卫,它的值是immutable
, request
, request-no-cors
, response
, 或none
。这会影响set()
、delete()
和append()
方法是否会改变标头。有关详细信息,请参阅守卫。
您可以Headers
通过Request.headers
和Response.headers
属性检索对象,并Headers
使用Headers()
构造函数创建新对象。
一个对象实现Headers
可以直接在结构中使用for...of
,而不是entries()
:for (const p of myHeaders)
等同于for (const p of myHeaders.entries())
.
Fetch API
中的 Headers对象
表示请求或响应的头信息,包含了键值对的集合,可以通过该对象进行添加、修改、删除头信息。
下面是一个简单的例子,介绍了如何使用 Headers对象
:
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer my-token');
首先,我们创建一个新的 Headers对象
。然后,使用 append()
方法向 Headers对象
中添加两个键值对。第一个键值对的键是"Content-Type"
,值是"application/json"
。第二个键值对的键是"Authorization"
,值是"Bearer my-token"
。
Headers对象
的常用方法包括:
- Headers.append():将新值附加到对象内的现有标头上
Headers
,或者添加标头(如果尚不存在)。 - Headers.delete():从对象中删除标头
Headers
。 - Headers.entries():返回
iterator
允许遍历此对象中包含的所有键/值对。 - Headers.forEach():为此对象中的每个键/值对执行一次提供的函数
Headers
。 - Headers.get():返回具有给定名称的对象
String
中标头的所有值的序列。Headers
- Headers.has():返回一个布尔值,说明对象是否
Headers
包含某个标头。 - Headers.keys():返回一个
iterator
允许您遍历此对象中包含的键/值对的所有键。 - Headers.set():为对象内的现有标头设置新值
Headers
,或者添加标头(如果尚不存在)。 - Headers.values():返回一个
iterator
允许您遍历此对象中包含的键/值对的所有值。
注意1:
Headers.set()
要清楚,和之间的区别Headers.append()
在于,如果指定的标头已经存在并且确实接受多个值,Headers.set()
则会用新值覆盖现有值,而Headers.append()
会将新值附加到值集的末尾。请参阅他们的专用页面以获取示例代码。注意2:
TypeError
如果您尝试传入对不是有效 HTTP 标头名称的名称的引用,则所有标头方法都将抛出异常。TypeError
如果标头具有不可变的Guard ,则变异操作将抛出一个。在任何其他失败情况下,他们都会默默地失败。注意3: 当 Header 值被迭代时,它们会自动按字典顺序排序,并且来自重复 header 名称的值被合并。
Headers对象
还支持使用迭代器遍历其中的键值对。例如:
for (const [name, value] of headers) {
console.log(`${name}: ${value}`);
}
以上代码将遍历 headers对象
中的所有键值对,并将它们打印到控制台。
Headers对象
常用于 fetch API
中的请求和响应中,例如:
const response = await fetch('https://example.com', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer my-token'
}
});
在 fetch API
中,可以通过传递一个包含 headers对象
的配置对象来设置请求或响应中的头信息。在以上代码中,我们向 fetch()
方法传递了一个包含两个键值对的 headers对象
,这些键值对将被添加到发送给服务器的请求头中。
除了在 fetch API
的请求和响应中使用 Headers对象
之外,它还可以用于其他HTTP请求和响应场景。以下是一个使用 Headers对象
的HTTP请求的示例代码:
const headers = new Headers({
'Content-Type': 'application/json',
'Authorization': 'Bearer my-token'
});
fetch('https://example.com/api/data', {
method: 'POST',
headers: headers,
body: JSON.stringify({ data: 'example data' })
})
.then(response => {
console.log(response.status);
});
在以上代码中,我们首先创建了一个新的 Headers对象
,并使用构造函数中的参数传递了两个键值对。接下来,我们使用 fetch()
方法向服务器发送了一个POST请求,并通过 headers属性
将 Headers对象
传递给了 fetch()
方法。最后,我们将一个JSON对象序列化为字符串,并将其作为请求体发送给了服务器。
此外,Headers对象
还支持类似数组的操作,例如使用数组下标访问Headers对象
中的键值对。例如:
const headers = new Headers({
'Content-Type': 'application/json',
'Authorization': 'Bearer my-token'
});
console.log(headers['Content-Type']); // 输出: "application/json"
需要注意的是,在 Headers对象
中,键值对的键是不区分大小写的。因此,使用 headers.get()
方法获取键值对的值时,可以传入大小写不敏感的键名。例如:
const headers = new Headers({
'Content-Type': 'application/json',
'Authorization': 'Bearer my-token'
});
// or, using an array of arrays:
myHeaders = new Headers([
['Content-Type', 'application/json'],
['Authorization', 'Bearer my-token']
]);
console.log(headers.get('content-type')); // 输出: "application/json"
总之,Headers对象
提供了一种方便的方法来处理 HTTP请求
和响应中的头信息。通过使用 Headers对象
,我们可以轻松地添加、修改和删除请求或响应的头信息,并可以使用类似数组的操作和迭代器遍历来操作 Headers对象
中的键值对。
2.3 Request
Fetch API Request
的接口表示资源请求。
你可以使用
Request()
构造函数创建一个新的Request
对象,但你更有可能遇到一个Request
对象作为另一个API
操作的结果被返回,比如一个service worker
FetchEvent.request。
2.3.1 构造函数
- Request():创建一个新
Request
对象。
2.3.2 实例属性
- Request.body - 只读:正文内容的 ReadableStream。
- Request.bodyUsed - 只读:存储
true
或false
,以表示该主体是否已经在请求中被使用。 - Request.cache - 只读:包含请求的缓存模式(例如,
default
,reload
,no-cache
)。 - Request.credentials - 只读:包含请求的凭据(例如
omit
,same-origin
,include
,same-origin
)。默认值为。 - Request.destination - 只读:返回描述请求目的地的字符串。这是一个字符串,表示所请求内容的类型。
- Request.headers - 只读:Headers包含请求的关联对象。
- Request.integrity - 只读:包含请求的子资源完整性值(例如,
sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=
)。 - Request.method - 只读:包含请求的方法(
GET
,POST
等) - Request.mode - 只读:包含请求的模式(例如
cors
,no-cors
,same-origin
,navigate
) - Request.redirect - 只读:包含如何处理重定向的模式。它可能是
follow
、error
或manual
之一。 - Request.referrer - 只读:包含请求的引荐来源网址(例如,
client
)。 - Request.referrerPolicy - 只读:包含请求的引用策略(例如,
no-referrer
)。 - Request.signal - 只读:返回
AbortSignal
与请求关联的 - Request.url - 只读:包含请求的 URL。
2.3.3 实例方法
- Request.arrayBuffer():ArrayBuffer 返回一个以请求主体的表示解析的
promise
。 - Request.blob():Blob 返回一个以请求主体的表示解析的
promise
。 - Request.clone():创建当前对象的副本
Request
。 - Request.formData():FormData 返回一个以请求主体的表示解析的
promise
。 - Request.json():返回一个
promise
,该promise
以将请求主体解析为JSON. - Request.text():返回使用请求正文的文本表示解析的
promise
。
注意: 请求体函数只能运行一次;后续调用将解析为
空字符串/ArrayBuffers
。
2.3.4 例子
在下面的代码片段中,我们使用构造函数创建一个新请求Request()
(对于与脚本位于同一目录中的图像文件),然后返回请求的一些属性值:
const request = new Request('https://www.mozilla.org/favicon.ico');
const url = request.url;
const method = request.method;
const credentials = request.credentials;
Request
然后,您可以通过将对象作为参数传递给调用来获取此请求fetch(),例如:
fetch(request)
.then((response) => response.blob())
.then((blob) => {
image.src = URL.createObjectURL(blob);
});
在以下代码片段中,我们使用Request()
构造函数创建一个新请求,其中包含一些初始数据和需要主体有效负载的 API 请求的主体内容:
const request = new Request('https://example.com', {method: 'POST', body: '{"foo": "bar"}'});
const url = request.url;
const method = request.method;
const credentials = request.credentials;
const bodyUsed = request.bodyUsed;
注意: body 只能是 Blob、 ArrayBuffer、TypedArray、DataView、FormData、URLSearchParams、ReadableStream 或 String 对象,以及字符串文字,因此要将 JSON 对象添加到有效负载,你需要对该对象进行字符串化。
然后,你可以通过将 Request
对象作为参数传递给调用来获取此 API 请求fetch(),例如并获得响应:
fetch(request)
.then((response) => {
if (response.status === 200) {
return response.json();
} else {
throw new Error('Something went wrong on API server!');
}
})
.then((response) => {
console.debug(response);
// …
}).catch((error) => {
console.error(error);
});
2.4 Response
Fetch API Response
的接口表示对请求的响应。
你可以使用 Response() 构造函数创建一个新的
Response
对象,但你更有可能遇到一个Response
对象作为另一个API操作的结果被返回—例如,一个service worker
FetchEvent.replyWith,或一个简单的 fetch()。
2.4.1 构造函数
- Response():创建一个新
Response
对象。
2.4.2 实例属性
- Response.body:正文内容的 ReadableStream。
- Response.bodyUsed - 只读:存储一个布尔值,声明正文是否已在响应中使用。
- Response.headers - 只读:Headers 与响应关联的对象。
- Response.ok - 只读:一个布尔值,指示响应是否成功(状态在
200
–范围内299
)。 - Response.redirected - 只读:指示响应是否是重定向的结果(即,其 URL 列表具有多个条目)。
- Response.status - 只读:响应的状态代码。(这将是
200
成功的)。 - Response.statusText - 只读:状态码对应的状态信息。(例如,
OK
对于200
)。 - Response.type - 只读:响应的类型(例如,
cors
,basic
)。 - Response.url - 只读:响应的 URL。
2.4.3 静态方法
- Response.error():
Response
返回与网络错误关联的新对象。 - Response.redirect():使用不同的 URL 创建新的响应。
2.4.4 实例方法
Response.arrayBuffer()
:返回一个Promise
,该Promise
以 ArrayBuffer 响应主体的表示形式解析。- Response.blob():返回一个
Promise
,该Promise
以Blob
响应主体的表示形式解析。 - Response.clone():创建对象的克隆
Response
。 - Response.formData():返回一个
Promise
,该Promise
以FormData
响应主体的表示形式解析。 - Response.json():返回一个
Promise
,该Promise
以将响应正文文本解析为JSON
. - Response.text():返回一个
Promise
,该Promise
以响应主体的文本表示形式解析。
2.4.5 例子
在我们的 基本获取示例 中(实时运行示例),我们使用一个简单的fetch()调用来抓取一张图片并将其显示在<img>
元素中。fetch()
调用返回一个 promise
,它解析为与资源获取操作相关的Response
对象。
你会注意到,由于我们正在请求图像,因此我们需要运行 Response.blob 以向响应提供正确的 MIME 类型。
const image = document.querySelector(".my-image");
fetch("flowers.jpg")
.then((response) => response.blob())
.then((blob) => {
const objectURL = URL.createObjectURL(blob);
image.src = objectURL;
});
2.5 Fetch API 优缺点
Fetch API
是一个基于 Promise
设计的 JavaScript API
,用于在 Web 应用程序中进行网络请求。以下是 Fetch API
的优缺点:
优点:
- 更简洁的代码:相比
XMLHttpRequest
,Fetch API
的语法更加简单易懂,使用起来更加方便。 - 基于
Promise
设计:Promise
是JavaScript
中一种非常强大的异步编程机制,可以帮助开发者更好地处理异步操作,避免回调地狱。 - 支持跨域请求:
Fetch API
内置了跨域请求的支持,可以让开发者更加轻松地处理跨域问题。 - 更好的错误处理:
Fetch API
在网络请求失败时会抛出异常,可以帮助开发者更好地处理错误。 - 可以自定义请求头:
Fetch API
允许开发者自定义请求头,可以更好地控制请求。
缺点:
- 不兼容旧版本浏览器:
Fetch API
并不是所有浏览器都支持的,特别是在一些旧版本浏览器中可能会有兼容性问题。 - 默认不携带
cookie
:Fetch API
默认不携带cookie
,需要手动设置才能携带cookie
。 - 无法取消请求:
Fetch API
并不支持直接取消请求,需要开发者自己处理取消请求的逻辑。
2.6 Fetch API 安全性问题
Fetch API
是用于获取和发送资源的现代 Web API
。它可以使网络请求更加灵活和高效。然而,由于它的开放性和灵活性,Fetch API
也可能存在一些安全性问题。以下是一些可能存在的安全性问题:
- 跨站点请求伪造(
CSRF
):攻击者可以通过伪造一个可信站点的请求,以在用户不知情的情况下执行某些操作。这可以通过在请求头中包含攻击者的自定义Cookie或其他授权信息来实现。 - 跨域资源共享(
CORS
):由于浏览器的同源策略限制,浏览器只允许来自同一域的请求。但是,使用CORS
,站点可以通过在响应头中包含Access-Control-Allow-Origin
来授权其他站点进行请求。如果站点的CORS
设置不正确,攻击者可能会利用它来执行跨域攻击。 HTTPS劫持
:攻击者可能会尝试在HTTPS
连接中中间攻击并篡改Fetch API
请求。这可以通过使用代理或恶意软件来实现。- 注入攻击:如果站点的Fetch API处理不正确,攻击者可能会利用它来执行
SQL注入
或其他类型的注入攻击。
为了避免这些安全问题,开发人员应该采取适当的措施来保护站点的Fetch API
。例如:
- 使用随机生成的令牌来防止
CSRF
攻击。 - 设置适当的CORS策略,并使用适当的
Access-Control-Allow-Origin
标头来限制请求来源。 - 在
HTTPS
连接中使用证书,并使用证书钩子来验证证书。 - 对于所有输入,使用参数化查询来避免注入攻击。
3、Axios
3.1 什么是 Axios?
官方:Axios 是一个基于 promise 的 JavaScript HTTP 客户端,可用于node.js和浏览器。它是同构的(=它可以在浏览器和nodejs中以相同的代码库运行)。在服务器端,它使用本地
node.js
的http
模块,而在客户端(浏览器)则使用XMLHttpRequests
。Axios 支持
JS ES6
原生的Promise API
,它比.fetch()
具有的另一个特性是它执行 JSON 数据的自动转换。
它的 Features
:
- 从浏览器中发出 XMLHttpRequests
- 从
node.js
发出http请求 - 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 超时处理
- 查询参数序列化,支持嵌套条目
- 自动将请求主体序列化为:
JSON (application/json)
Multipart/FormData(multipart/form-data)
URL编码的表单(application/x-www-form-urlencoded)
- 将HTML表单发布为
JSON
- 响应中自动处理
JSON数据
- 为浏览器和
node.js
捕捉进度,并提供额外的信息(速度、剩余时间)。 - 为
node.js
设置带宽限制 - 与符合规范的
FormData
和Blob
(包括node.js)兼容 - 客户端支持对 XSRF 的保护
3.2 Axios 基本用法
3.2.1 axios 实例
可以用自定义配置创建一个新的 axios
实例:
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
下面是可用的一些实例方法,指定的配置将被合并到实例配置中:
axios#request(config)
axios#get(url[, config])
axios#delete(url[, config])
axios#head(url[, config])
axios#options(url[, config])
axios#post(url[, data[, config]])
axios#put(url[, data[, config]])
axios#patch(url[, data[, config]])
axios#getUri([config])
3.2.2 处理请求
axios
为所有支持的请求方法提供了简洁的 api
:
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
axios.postForm(url[, data[, config]])
axios.putForm(url[, data[, config]])
axios.patchForm(url[, data[, config]])
3.2.2.1 发送 GET 请求
axios.get('/user?ID=12345')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
// 也可以用以下方式
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.finally(function () {
// always executed
});
async function getUser() {
try {
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
}
}
还可以通过传递相关的配置参数实现get请求:
// GET request for remote image in node.js
axios({
method: 'get',
url: 'http://bit.ly/2mTM3nY',
responseType: 'stream'
})
.then(function (response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
多个并行请求:
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
const [acct, perm] = await Promise.all([getUserAccount(), getUserPermissions()]);
// or
Promise.all([getUserAccount(), getUserPermissions()])
.then(function ([acct, perm]) {
// ...
});
3.2.2.2 发送 POST 请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// or
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
- Application/json
const {data} = await axios.post('/user', document.querySelector('#my-form'), {
headers: {
'Content-Type': 'application/json'
}
})
- Multipart (
multipart/form-data
)
const {data} = await axios.post('https://httpbin.org/post', {
firstName: 'Fred',
lastName: 'Flintstone',
orders: [1, 2, 3],
photo: document.querySelector('#fileInput').files
}, {
headers: {
'Content-Type': 'multipart/form-data'
}
}
)
- URL encoded form (
application/x-www-form-urlencoded
)
const {data} = await axios.post('https://httpbin.org/post', {
firstName: 'Fred',
lastName: 'Flintstone',
orders: [1, 2, 3]
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
3.2.2.3 发送 PUT 请求
axios.put('/api/users/1', {
name: 'John Doe',
email: 'john@example.com'
})
.then(response => console.log(response.data))
.catch(error => console.log(error));
3.2.2.4 发送 DELETE 请求
axios.delete('/api/users/1')
.then(response => console.log(response.data))
.catch(error => console.log(error));
3.2.2.5 设置请求头
axios.get('/api/users', {
headers: {
'Authorization': 'Bearer ' + token
}
})
.then(response => console.log(response.data))
.catch(error => console.log(error));
3.3 取消请求
timeout
在 axios
调用中设置属性会处理与响应相关的超时。在某些情况下(例如网络连接变得不可用),axios
调用将受益于提前取消 连接。如果不取消,axios
调用可能会挂起,直到父代码/堆栈超时(在服务器端应用程序中可能是几分钟)。
要终止 axios
调用,你可以使用以下方法:
signal
cancelToken
(弃用)
组合 timeout
和取消方法(例如 signal
)应涵盖与 响应 相关的超时和 连接 相关的超时。
3.3.1 signal: 中止控制器
从
v0.22.0 axios
开始支持 AbortControllerfetch API
方式取消请求:
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
AbortSignal.timeout() 使用最新 API [nodejs 17.3+]
的超时示例:
axios.get('/foo/bar', {
signal: AbortSignal.timeout(5000) //Aborts request after 5 seconds
}).then(function(response) {
//...
});
带有超时辅助函数的示例:
function newAbortSignal(timeoutMs) {
const abortController = new AbortController();
setTimeout(() => abortController.abort(), timeoutMs || 0);
return abortController.signal;
}
axios.get('/foo/bar', {
signal: newAbortSignal(5000) //Aborts request after 5 seconds
}).then(function(response) {
//...
});
3.3.2 CancelToken
你还可以使用 CancelToken
取消请求。
- axios cancel token API 基于撤回的可取消
promise
提案。- 此 API 已弃用,因为
v0.22.0
不应在新项目中使用
你可以使用工厂创建取消令牌,CancelToken.source
如下所示:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求(消息参数是可选的)
source.cancel('Operation canceled by the user.');
你还可以通过将执行函数传递给构造函数来创建取消令牌CancelToken
:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// 一个执行器函数接收一个取消函数作为参数
cancel = c;
})
});
// cancel the request
cancel();
在过渡期间,你可以使用两种取消 API,即使是针对同一个请求:
const controller = new AbortController();
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token,
signal: controller.signal
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
// OR
controller.abort(); // the message parameter is not supported
3.4 拦截请求和响应
axios.interceptors.request.use(config => {
// Do something before request is sent
return config;
}, error => {
// Do something with request error
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
// Do something with response data
return response;
}, error => {
// Do something with response error
return Promise.reject(error);
});
3.5 错误处理
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// 请求已经发出,但是服务器回应的状态码不是 2xx。
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// 请求已经发出,但没有收到响应
// `error.request`在浏览器中是 XMLHttpRequest 的一个实例
// 在node.js中是 http.ClientRequest 的一个实例。
console.log(error.request);
} else {
// 设置触发错误的请求时需要处理的一些事情
console.log('Error', error.message);
}
console.log(error.config);
});
3.6 Axios 优缺点
优点:
Promise API
:Axios使用Promise API
,因此可以轻松处理异步操作。Promise API
具有更清晰的语法和更好的可读性,因为它们允许在异步操作完成之前进行链式调用,从而避免了回调地狱问题。简单易用:
Axios
的API设计简单且易于使用,而且它还提供了许多可用的配置选项,例如设置请求头、超时时间、认证等等,让开发者可以更轻松地定制请求。- 可扩展性:
Axios
可以通过添加拦截器(interceptors
)来实现许多自定义功能,例如添加请求拦截器、响应拦截器和错误拦截器等等。这些拦截器可以让开发者在请求和响应过程中进行自定义操作。 - 支持浏览器和
Node.js
:Axios
可以同时在浏览器和Node.js
环境中使用。这意味着开发者可以使用相同的代码库在两个不同的环境中实现网络请求功能。 - 轻量级:
Axios
是一个轻量级库,它只依赖于Promise
和一些基本的JavaScript
库,因此它的体积相对较小。
缺点:
学习曲线:尽管
Axios
的API
设计简单且易于使用,但是它仍然需要一些学习和理解。特别是对于初学者来说,可能需要一些时间才能掌握如何使用Axios
。对于大型应用程序可能不够强大:对于大型应用程序来说,
Axios
可能会显得有些简单。因为它不提供复杂的数据管理功能,例如状态管理、数据缓存等等。这时候可能需要使用其他更为强大的HTTP客户端库
(比如TanStack Query
)来实现这些功能。- 可能出现跨域问题:
Axios
不能直接解决跨域请求的问题。尽管Axios
可以设置跨域请求头,但是它不能绕过浏览器的安全限制。这意味着在某些情况下,开发者可能需要通过其他方式来解决跨域请求的问题。
3.7 Axios 安全性
Axios
在 Web 安全方面做了以下工作:
防止跨站点请求伪造(
CSRF
)攻击:Axios 会自动在请求头中添加CSRF token
(如果可用的话),以防止攻击者利用用户身份进行 CSRF 攻击。此外,Axios
还支持自定义请求头,允许开发人员手动添加其他安全相关的请求头。防止
XSS
攻击:Axios
不会自动解析或执行响应的内容,因此可以防止跨站点脚本(XSS
)攻击。- 支持
HTTPS
:Axios
支持使用HTTPS
进行加密传输,从而保护数据的安全性。 - 支持自定义验证:
Axios
提供了自定义验证的接口,允许开发人员根据自己的需求进行验证,以确保请求和响应的安全性。 - 支持取消请求:
Axios
提供了取消请求的接口,允许开发人员在请求过程中取消请求,以避免恶意攻击或其他安全问题的发生。
4、Fetch API 和 Axios 的区别
Ajax
实际上不是一个特定的技术,而是一系列技术的统称,通常我们将其默认为是 XMLHttpRequest
。Fetch API
是ES6之后出现的一个新的基于 Promise
的新API,可以认为是 XMLHttpRequest
的最新替代品,开发人员终于可以忘记丑陋且难以使用的HTMLHttpRequests了。Axios
则是一个专门用于HTTP请求的库,也是基于 Promise
。实际开发中我们可能更多使用 Fetch API
和 Axios
,接下来就结合实际例子来分析它们的区别。
4.1 基本定义
4.1.1 Axios
Axios 是用于发出网络请求的第三方 HTTP 客户端库。它是基于 Promise
的,可以在以下示例中看到:
axios.get('https://fakestoreapi.com/products/1')
.then(response => console.log(response));
默认情况下,响应数据以 JSON 格式提供,因此我们可以使用 data 属性立即访问它。它在底层使用XMLHttpRequest,这是它广泛支持浏览器的原因之一。你可以通过 CDN 或使用包管理器(如npm)将其添加到你的项目中。
核心功能包括:
- 在浏览器中创建 XMLHttpRequests
- 在 node.js 环境中发出 http 请求
- 取消请求
- 拦截请求和响应
兼容性方面,大多数浏览器都广泛支持 Axios,即使是像 IE11 这样的旧浏览器。
4.1.2 Fetch
Fetch 是一个基于 promise 的 API。看一个简单的例子:
fetch('https://fakestoreapi.com/products/1')
.then((response) => console.log(response.json()))
.catch((error) => console.error(error));
Fetch API
提供了一个简单易用的接口来获取资源,更具体地说,它为我们提供了访问和操作部分 HTTP 管道的方法,其中部分是请求和响应。
Fetch API 提供了fetch() 方法。使用 Fetch API 我们可以完全实现 Axios 的所有核心功能。实际上,Fetch API 是一个比 Axios 具有更多可能性的原生接口。但是,因为它是底层的API,所以通常使用起来有点繁琐。
兼容性方面,Fetch 是一个内置的 API,因此我们不需要安装或导入任何东西。它在所有现代浏览器中都可用,您可以在caniuse上查看。Fetch 在 node.js 中也可用 - 可以在此处阅读更多相关信息。
请记住,即使你的浏览器不支持 Fetch,你始终可以使用polyfill。然而,如果你必须使用这个 polyfill,你可能还需要一个promise polyfill。
4.2 基本语法
4.2.1 Axios
Axios 提供了多种不同的调用方式:
axios(url, {
// configuration options
})
我们可以使用点表示法,而不是在配置选项中指定 HTTP 方法:
axios.post(url, {
// configuration options
})
如果我们省略配置选项和点符号,它默认为 GET 方法:
axios(url)
我们可以为请求使用许多不同的配置设置,使用第二个参数:
axios(url, {
method: 'post',
timeout: 1000,
headers: {
"Content-Type": "application/json",
},
data: {
property: "value",
},
})
可以在此处 找到完整API列表。
4.2.2 Fetch
Fetch 接收两个参数,第一个参数是 URL,第二个是配置选项:
fetch(url, {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
property: "value",
}),
})
有关配置选项的完整列表,请查看此链接。
4.2.3 总结
来看两个具体的例子:
let url = 'https://someurl.com';
let options = {
method: 'POST',
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
body: JSON.stringify({
property_one: value_one,
property_two: value_two
})
};
let response = await fetch(url, options);
let responseOK = response && response.ok;
if (responseOK) {
let data = await response.json();
// do something with data
}
let url = 'https://someurl.com';
let options = {
method: 'POST',
url: url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
data: {
property_one: value_one,
property_two: value_two
}
};
let response = await axios(options);
let responseOK = response && response.status === 200 && response.statusText === 'OK';
if (responseOK) {
let data = await response.data;
// do something with data
}
基本语法非常相似,几乎完全相同。但是,存在多个小差异:
- 不同的属性用于 post 请求以将数据发送到
endpoint
-Axios
使用data
属性,而fetch
使用body属性。 - 我们需要将数据序列化为
JSON
字符串来发送数据。Axios
在使用POST
方法向API
发送JavaScript
对象时自动将数据字符串化。 Axios
会自动转换从服务器返回的数据,但使用fetch()
时,你必须调用response.json
方法,将数据解析为JavaScript
对象。关于response.json
方法的更多信息可以在这里找到。- 使用
fetch()
时,我们必须使用JSON.stringify
将数据转换为字符串。 - 使用
Axios
,服务器提供的数据响应可以在 数据对象 中访问,而对于fetch()
方法,最终数据可以被命名为任何变量。
4.3 响应处理
4.3.1 Axios
在 Axios 中,响应数据默认以 JSON 格式提供。我们所要做的就是访问响应对象的数据属性:
axios.get('https://fakestoreapi.com/products/1')
.then(response => {
console.log(response.data);
}, error => {
console.log(error);
});
可以使用配置选项更改响应类型,即responseType属性。它指示服务器将响应的数据类型,选项包括:
arraybuffer
document
json (default)
text
stream
blob
4.3.2 Fetch
使用 Fetch,在访问响应数据之前我们还需要执行一个步骤:
fetch('https://fakestoreapi.com/products/1')
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => console.error(error));
使用fetch()
时,我们会在所有标头到达后立即收到响应。 那时,我们还没有尚未加载的 HTTP 响应主体。 这就是我们收到另一个应许的原因。简而言之,response.json()
等待正文加载。
我们每天使用的 JSON 对象通常都比较小。但是,想象一下您必须加载非常大的图像的情况。这可能需要一段时间,在图像完全加载之前我们可能需要相关信息。
要阅读有关
response.json()
的更多信息,请查看此链接。
4.3.2 总结
Fetch 是一个相对低级的 API,可以精确控制加载过程。 它伴随着每次使用它时必须处理两个 Promise
的成本。
Fetch
要求我们在处理响应时多做一步,因为它返回一个Promise
,此时我们没有我们需要的JSON
数据格式,因此需要.json()
方法。- 要在
Axios
中访问响应数据,我们使用data
属性,而在使用fetch()
时,最终数据可以命名为任何变量。
4.4 错误处理
还记得 Axios
和 Fetch
都是基于 promise
的 HTTP
客户端吗?因此,他们都返回一个可以 resolve
或 reject
的 Promise
。然而,就相似性而言,仅此而已—— promise
得到 resolve
或 reject
的条款大不相同。
4.4.1 Axios
让我们看一个典型的 Axios
错误处理示例,使用 .catch()
:
axios.get('https://codilime.com/')
.then(response => console.log(response.data))
.catch((err) => console.log(err.message));
每个响应都包含一个状态属性,它只是一个 HTTP 响应状态代码。这些代码表示请求是否已成功完成。例子包括:
- 200
OK
- 请求成功, - 403
Forbidden
——客户端无权访问内容, - 418
I'm a teapot
- 服务器拒绝尝试用茶壶冲泡咖啡(不,这不是玩笑,请在此处查看)。
可以在此处阅读有关这些 HTTP 响应状态代码的更多信息。
现在我们知道了 HTTP 响应状态代码是什么,我们可以理解 Axios 错误处理是如何工作的。Axios
将拒绝任何状态码超出 200-299 范围(成功响应)的Promise
。
我们可以检查错误对象以获取有关错误的更多信息:
axios.get('https://fakestoreapi.com/products/1')
.then(response => console.log(response.data))
.catch((err) => {
if (err.response) {
// The request was made, but the server responded with a status code that falls out of the 2xx range
const { status } = err.response;
if (status === 401) {
// The client must authenticate itself to get the requested response
}
else if (status === 502) {
// The server got an invalid response
}
...
}
else if (err.request) {
// The request was made, but no response was received
}
else {
// Some other error
}
});
当我们在错误对象上有 response
属性时,这意味着发出了请求,并且服务器响应了,但是响应状态代码在2xx 范围之外。另一方面,请求属性表示已发出请求,但未收到响应。如果这两者都不正确,则表示设置网络请求时出现问题,从而触发错误。
4.4.2 Fetch
获取错误处理与 Axios 有很大不同。最重要的区别是,如果我们收到 HTTP 错误,它不会reject Promise
——不成功的响应仍然会得到解决。因此,HTTP 错误在 .then
块中处理。只有在网络出现故障的情况下,Fetch API Promise
才会被拒绝。
fetch('https://fakestoreapi.com/products/1')
.then(response => {
if (!response.ok) {
throw Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch((error) => {
console.log(error)
});
使用 Fetch API 需要我们检查response.ok属性来控制 HTTP 错误。
两个最重要的响应属性是status和ok:
- status - 响应状态代码(整数)。
- ok - 检查状态是否在 2xx(布尔值)范围内的简写。
在成功的场景中,它们将分别具有200和true值:
{ ... status: 200, ok: true, ... }
但是,在错误情况下,我们将获得 HTTP 错误状态代码和false。例如,如果我们没有通过身份验证来接收请求的响应,我们将得到:
{ ... status: 401, ok: false, ... }
4.4.3 总结
在 Axios 中,超出 200-299 范围(成功响应)的响应将被自动拒绝。使用 .catch() 块,我们可以获得有关错误的信息,例如是否收到响应,如果收到,则返回其状态代码。
另一方面,在 fetch() 中,不成功的响应仍然得到解决。这就是为什么我们必须检查响应的ok属性并在它设置为 false 时抛出错误。然后在.catch() 块中处理该错误。
4.5 响应超时
在设定的时间后中止请求的功能是发出 HTTP 请求的重要部分。如果没有该功能,请求可能会挂起并可能导致我们的应用程序变慢。
4.5.1 Axios
使用 Axios 设置响应超时非常容易。我们所要做的就是在请求的配置对象中添加一行:
axios.get(url, {
...
timeout: 2000,
...
})
.then(response => console.log(response.data))
.catch(err => console.log(err.message))
超时是一个可选参数,以毫秒为单位。如果超过两秒,上述请求将被中止,并记录错误。timeout属性的默认值为0,表示没有超时。
4.5.2 Fetch
用 Fetch API 做同样的事情并不容易。要实现相同的行为,我们可以使用名为AbortController的接口和fetch() 方法配置选项:
const controller = new AbortController();
const options = {
method: 'POST',
signal: controller.signal,
body: JSON.stringify({
firstName: 'David',
lastName: 'Pollock'
})
};
const promise = fetch('/login', options);
const timeoutId = setTimeout(() => controller.abort(), 4000);
promise
.then(response => {/* handle the response */})
.catch(error => console.error('timeout exceeded'));
在这里,通过创建一个AbortController对象,我们可以访问abort() 方法和信号对象,它允许我们稍后中止该请求。信号是 AbortController
的一个只读属性,提供了一个与请求沟通或中止请求的方法。如果服务器在四秒内没有回应,controller.abort()
就会被调用,操作就会终止。
请注意,如果您在fetch() 完成后调用*abort() ,它只会忽略它。
4.5.3 总结
fetch() 没有像 Axios 那样为我们提供超时配置选项。相反,我们必须使用AbortController接口和setTimeout函数。Axios 隐藏了很多这样的样板文件,这使其成为大多数用例的明显赢家。 但是,我们已经知道 Fetch 是一个低级 API。根据您的需要,在响应超时和取消请求方面,您可能仍然更喜欢 Fetch 而不是 Axios。
4.6 HTTP 请求和响应拦截
Axios
的主要功能之一是其拦截 HTTP请求
的能力。当你需要检查或改变从你的应用程序到服务器的HTTP请求或反之亦然时,HTTP拦截器就会派上用场(例如,记录、认证或重试一个失败的HTTP请求)。
有了拦截器,你就不必为每个HTTP请求单独编写代码。当你想为你处理请求和响应的方式设置一个全局策略时,HTTP拦截器很有帮助。
想象一个场景,我们必须保存从我们的应用程序发送的每个 HTTP 请求的日志。向每个请求添加这样的代码很麻烦,容易出错,简而言之,不可行。这就是拦截器发挥作用的时候。它们允许我们消除为每个请求重复代码,而是创建和设置处理请求和响应的全局行为。
简而言之,HTTP 拦截器用于在客户端和服务器端 HTTP 请求和响应之间应用自定义逻辑。它们可用于不同的操作,例如:
- 修改 HTTP 标头或正文
- 设置自定义令牌
- 修改响应
4.6.1 Axios
Axios 具有用于简单创建 HTTP 拦截器的功能 - 包括响应和请求拦截器。首先,让我们看一个请求拦截器:
axios.interceptor.request.use(config => {
// log data before HTTP request is sent
console.log('HTTP request being sent...');
return config;
})
上面的代码会在发送任何 HTTP 请求之前产生一条日志消息。类似地,响应拦截器被定义为在它们被then或catch处理之前运行一些代码:
axios.interceptor.response.use(
function (response) {
// This function will be triggered on any status code inside 2xx range
return response;
},
function (error) {
// This function will be triggered on any status code outside of 2xx range
return Promise.reject(error);
}
)
例如,当您想要设置一个策略以在失败的情况下始终重试一次 HTTP 请求时,响应拦截器可能会派上用场。
如果您愿意,Axios 还允许您删除拦截器:
const requestInterceptor = axios.interceptor.request.use(config => {
// log data before HTTP request is sent
console.log('HTTP request being sent...');
return config;
});
axios.interceptor.request.eject(requestInterceptor);
4.6.2 Fetch
使用 Fetch API 时,我们没有配置选项或特殊的内置函数来实现拦截器。然而,Vanilla JS(一种快速、轻量级、跨平台的框架)就足够了:
fetch = (originalFetch => {
return (...arguments) => {
const result = originalFetch.apply(this, arguments);
return result.then(console.log('HTTP request being sent ...'));
};
})(fetch);
上面的代码是一个 HTTP 请求拦截器的实现。我们可以像使用普通的旧fetch() 函数一样使用它:
fetch('https://fakestoreapi.com/products/1')
.then(response => response.json())
.then(console.log)
4.6.3 总结
Axios 为我们提供了开箱即用的功能来创建 HTTP 请求和响应拦截器。 与响应超时类似,它隐藏了大部分样板文件并为我们提供了一个漂亮、易于使用的界面。
另一方面,我们有 Fetch API,同样因为它的级别低,没有为我们提供这样的方法。基于 fetch() 的 HTTP 拦截器很容易编写,并且可以在 Internet 上找到。
再一次,我们可以选择避免样板文件而不是更细粒度的控制和自定义选项。选择应基于用例和个人需求。
4.7 download 进度
在 XMLHttpRequests 仍然被广泛使用的日子里,它是用于实现进度指示器的.onprogress 方法。今天,这些指标仍然是加载资产的重要组成部分,尤其是大资产,尤其是对于互联网连接速度较慢的用户。但是,我们不必再摆弄这个接口了。
让我们下载一张图片!
4.7.1 Axios
在使用axios库的时候,一般会使用axios Progress Bar来实现一个进度条。
它可以通过 NPM 包获得:
npm install --save axios-progress-bar
或者通过 CDN:
<script src="https://cdn.rawgit.com/rikmms/progress-bar-4-axios/0a3acf92/dist/index.js"></script>
之后,我们需要将 CSS 导入到 HTML 中,或者通过带有模块打包器的 JavaScript,例如webpack:
<link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/rikmms/progress-bar-4-axios/0a3acf92/dist/nprogress.css" />
就是这样的设置。现在我们可以实现进度条了。我们必须(仅一次)调用loadProgressBar()
函数,该函数可选择采用两个参数 - config 和 instance
。配置对象的属性可以在这里看到。第二个参数用于传递自定义 axios 实例。现在,我们将跳过这两个参数以使其尽可能简单。
...
<img id="codilime-logo" />
...
<script type="text/javascript">
loadProgressBar();
const url = 'http://codilime.com/image/path/logo.jpg';
function downloadFile(url) {
axios.get(url, {
responseType: 'blob',
})
.then(response => {
const fileReader = new window.FileReader();
fileReader.readAsDataURL(response.data);
fileReader.onload = () => {
document.getElementById('codilime-logo').setAttribute('src', fileReader.result);
};
})
.catch(error => {
console.log(error)
})
}
downloadFile(url);
</script>
上面的代码使用了FileReader API。它允许我们使用File或Blob对象读取文件的内容。FileReader.readAsDataURL()
开始读取指定blob的内容,完成后,FileReader.result
属性包含一个编码字符串。我们还使用了load事件,它会在读取成功完成时触发。那是我们将结果插入元素中的src属性的时候。
Axios 进度条设计基于angular-loading-bar。它使用负责在浏览器中显示加载栏的nprogress模块。
4.7.2 Fetch
使用 Fetch API 实现进度条并不容易,我们没有像 Axios 那样为我们提供简单易用 API 的包。不过不要害怕 - 正如你可能猜到的那样,还有另一个低级 API 允许我们实现进度条。可悲的是,有更多的代码。
我们将使用ReadableStream API。这个接口代表一个可读的字节数据流。此外,ReadableStream实例存在于Response 对象的body属性中。我们将使用该字节流来跟踪进度。
HTML 文件:
...
<div id="progress-bar" src="">Progress bar</div>
<img id="codilime-logo" />
...
文件:
const el = document.getElementById('progress-bar');
fetch('http://codilime.com/image/path/logo.jpg')
.then(response => {
if (!response.ok) {
throw Error(`HTTP error: ${response.status}`);
}
// Ensure ReadableStream is supported
if (!response.body) {
throw Error('ReadableStream API is not supported in this browser');
}
// The Content-Length header indicates the size of the message body, in bytes
const contentLength = response.headers.get('content-length');
if (!contentLength) {
throw Error ('Content-Length response header unavailable');
}
const total = parseInt(contentLength, 10);
let loaded = 0;
return new Response(
new ReadableStream({
start(controller) {
const reader = response.body.getReader();
read();
// read() function handles each data chunk
function read() {
reader.read().then(({ done, value }) => {
// No more data to read
if (done) {
controller.close();
return;
}
loaded += value.byteLength;
progress({ loaded, total });
// Get the data and send it to the browser via the controller
controller.enqueue(value);
read();
}).catch(error => {
console.error(error);
controller.error(error);
})
}
}
})
);
})
.then(response => response.blob())
.then(data => {
document.getElementById('codilime-logo').src = URL.createObjectURL(data);
})
.catch(error => {
console.error(error);
})
function progress({ loaded, total }) {
el.innerHTML = Math.round(loaded / total * 100) + '%';
}
好的,这是很多代码 - 让我们一步一步地完成它。
<div id="progress-bar" src="">Progress bar</div>
<img id="codilime-logo" />
在 HTML 文件中,我们创建一个img标签,与 Axios 示例中的相同。此外,我们必须创建一个div作为我们的进度条,我们不使用外部包,所以我们必须自己创建它。
const el = document.getElementById('progress-bar');
fetch('http://codilime.com/image/path/logo.jpg')
.then(response => {
if (!response.ok) {
throw Error(`HTTP error: ${response.status}`);
}
// Ensure ReadableStream is supported
if (!response.body) {
throw Error('ReadableStream API is not supported in this browser');
}
// The Content-Length header indicates the size of the message body, in bytes
const contentLength = response.headers.get('content-length');
if (!contentLength) {
throw Error ('Content-Length response header unavailable');
}
...
然后进入 JS。我们将div元素保存在 el变量中。之后,我们提出请求。标准程序是检查是否存在任何 HTTP 错误、是否支持 ReadableStream API
,以及是否存在 Content-Length
标头。
所有主流浏览器都支持 ReadableStream API
,您可以通过caniuse检查。因此,您可以在实现中省略第二个 if 语句。
我们添加第三个 if 语句,因为要访问标头,服务器必须发送 CORS 标头 - Access-Control-Expose-Headers - “Access-Control-Expose-Headers: content-length”
。此响应标头允许服务器指示哪些响应标头应提供给浏览器中运行的脚本,以响应跨域请求。
现在,进入返回的响应。
...
const total = parseInt(contentLength, 10);
let loaded = 0;
return new Response(
new ReadableStream({
start(controller) {
...
ReadableStream
构造函数从给定的处理程序创建并返回一个可读流对象。它接受underlayingSourceobject
,其中包含定义构造流实例行为方式的方法和属性。您可以在此处阅读更多信息。
在我们的例子中,我们唯一需要的是start(controller)
方法。构造对象时立即调用它。它的内容应该旨在访问流源和设置流功能。控制器属性是ReadableStreamDefaultController或ReadableByteStreamController,具体取决于类型属性。
...
const reader = response.body.getReader();
...
ReadableStream
的getReader()方法创建一个读取器并将流锁定到它。在该锁定期间,在释放此读取器之前,无法获取其他读取器。
...
read();
// read() function handles each data chunk
function read() {
reader.read().then(({ done, value }) => {
// No more data to read
if (done) {
controller.close();
return;
}
loaded += value.byteLength;
progress({ loaded, total });
// Get the data and send it to the browser via the controller
controller.enqueue(value);
read();
}).catch(error => {
console.error(error);
controller.error(error);
})
}
...
在 read()
函数中,我们调用 reader
的 read()
方法,它返回一个Promise
,提供对流内部队列中下一个块的访问。 如果块可用,Promise
将通过以下形式的对象实现:
{
value: theChunk,
done: false,
}
否则,如果流关闭,Promise
将通过以下形式的对象实现:
{
value: undefined,
done: true,
}
只有当流显示错误时,promise 才会被 reject
。
...
// No more data to read
if (done) {
controller.close();
return;
}
loaded += value.byteLength;
progress({ loaded, total });
// Get the data and send it to the browser via the controller
controller.enqueue(value);
read();
...
如果没有更多数据要读取,我们使用close()
方法关闭关联的流。否则,我们使用进度函数修改div
元素。之后,我们使用enqueue()
函数将给定块排入关联流中。
...
console.error(error);
controller.error(error);
...
在 .catch
子句中,我们使用 error()
方法,这会导致未来与关联流的任何交互出错。
请记住,这只是使用 Fetch API 实现进度条的一种方式。您可以在此GitHub 存储库中查看此实现和其他实现。
4.7.3 总结
这是和以前一样的故事。我们有一个选择。对于高级 Axios API
,我们必须使用一个额外的包—— Axios Progress Bar
。
另一方面,我们有低级的 Fetch API
,我们也可以在其中实现进度条。更重要的是,我们可以用各种不同的方式来实现它。这肯定是更多的代码,而且,像往常一样,它提供了对数据处理每个阶段发生的事情的更细粒度的控制。最重要的是,我们不需要任何额外的包。这一切都可以只用 JavaScript 和一些 API 来完成。
4.8 并行请求
如果您已经完成了进度条部分,您会很高兴听到 Axios
和 Fetch API
都提供了易于使用的方式来同时发出多个请求。让我们看看它的实际效果。
4.8.1 Axios
在 Axios 中,我们创建了一个请求数组。每个请求的创建方式与以前相同。此外,我们使用axios.all
方法发出多个 HTTP 请求:
const endpoints = [
'https://fakestoreapi.com/products/1',
'https://fakestoreapi.com/products/2',
'https://fakestoreapi.com/products/3'
];
axios.all(
endpoints.map(endpoint => axios.get(endpoint))
).then(console.log)
我们通过每个端点进行映射,向当前端点发出HTTP GET请求
。
axios.all
返回一个数组作为响应,其中每个条目对应于我们的端点数组中的一个端点。因此,我们将在data[0]
处获得endpoint
codilime.com/endpoint1
的响应,在data[1]
处获得 codilime.com/endpoint2
等的响应。
值得庆幸的是,Axios 还提供了axios.spread方法,它的行为类似于常规的 JavaScript 展开运算符 - … :
const endpoints = [
'https://fakestoreapi.com/products/1',
'https://fakestoreapi.com/products/2',
'https://fakestoreapi.com/products/3'
];
axios.all(
endpoints.map(endpoint => axios.get(endpoint))
)
.then(axios.spread((res1, res2, res3) => {
console.log(res1.data, res2.data, res3.data);
}))
4.8.2 Fetch
使用 Fetch API 时,我们可以使用 Promise.all()
方法和解构实现类似的行为:
const endpoints = [
'http://codilime.com/endpoint1',
'http://codilime.com/endpoint2',
'http://codilime.com/endpoint3',
];
Promise.all(
endpoints.map(endpoint => fetch(endpoint))
)
.then(async ([ res1, res2, res3 ]) => {
const res1JSON = await res1.json();
const res2JSON = await res2.json();
const res3JSON = await res3.json();
console.log(res1JSON, res2JSON, res3JSON);
}))
如果您不熟悉Promise.all()
,它会接受可迭代的Promise
(例如数组)并返回单个Promise
,该Promise
解析为输入Promise
结果的数组。
4.8.3 总结
在同时发出多个请求时,我们有两个非常相似的解决方案。一种利用外部库方法,另一种使用内置 JavaScript 方法。但是,这两个实现的含义和流程几乎相同。
5、总结
底层实现:
Ajax
的底层实现使用了浏览器内置的XMLHttpRequest(XHR)
对象,使用方式相对繁琐;而Fetch API
则使用了基于Promise
的fetch()
函数。Axios
则是基于XHR
和node.js
的http
封装的库。兼容性:
Ajax
具有较好的兼容性,几乎可以在所有浏览器上运行。Fetch
和Axios
虽然也具有较好的兼容性,但需要考虑到浏览器版本和功能支持问题,需要使用polyfill
或其他兼容性解决方案来保证兼容性。Promise
和回调函数:Ajax
不支持Promise
,通常使用回调函数来处理异步请求的结果,这种方式容易出现回调地狱的情况;Fetch
和Axios
则使用Promise
对象来处理异步请求的结果,可以更加方便地进行链式调用和错误处理。浏览器支持:
Ajax
和XHR
对象在旧版浏览器中的支持存在一些问题;Fetch API
是比较新的标准,可能会存在一些兼容性问题;Axios
则是一个基于Promise
的库,支持各种现代浏览器和Node.js
环境。- 功能和扩展性:
Ajax
和XHR
对象提供了基本的请求和响应功能,但是缺乏灵活性和扩展性。Fetch API
则提供了更加丰富的请求和响应选项,支持请求和响应的拦截、转换、缓存等功能。Axios
则在Fetch API
的基础上提供了更加强大和灵活的功能,如请求和响应的取消、并发、批量处理等。 - 请求和响应拦截器:
Axios
支持请求和响应拦截器,可以在请求或响应被处理前或后进行操作,比如添加请求头、修改响应数据等。这对于需要对请求或响应进行全局处理的场景非常有用。Fetch
和Ajax
相比较没有内置的请求和响应拦截器,需要手动编写实现。 - 库的大小和性能:
Ajax
和XHR
对象是浏览器内置的API
,不需要引入额外的库,因此体积较小,但是在处理大量请求时性能可能较差。Fetch API
是新的标准,需要使用polyfill
或者引入额外的库,因此体积较大,但是性能相对较好。Axios
是一个功能丰富的库,体积相对较大,但是性能表现较好,尤其在处理大量请求时优势更为明显。 - 并发请求:
Axios
可以方便地发送多个并发请求,通过Promise.all
或Promise.race
来处理响应。而Fetch
和Ajax
只能通过Promise
来处理单个请求,需要手动编写实现并发请求的逻辑。 - 对于同源策略的处理:
Ajax
和Fetch API
都遵循浏览器的同源策略,不能直接请求不同域名的资源。但是,它们可以通过设置CORS
或使用JSONP
等技术来实现跨域请求。而Axios
对跨域请求的处理更为简单,可以通过使用代理等方式来实现跨域请求。 - 请求的取消和超时:
Axios
支持请求的取消和超时设置,可以在发送请求时设置cancelToken
和timeout
选项,对于需要及时取消或超时的请求非常有用。Fetch API
和Ajax
在原生状态下不支持请求的取消和超时设置,需要手动编写实现。 API
的使用风格:Ajax
、Fetch
和Axios
在API
的使用风格上有所差异。Ajax
使用的是回调函数的方式,代码结构较为混乱,不易维护。Fetch API
使用Promise
的方式,代码结构清晰、易读、易维护。而Axios
同样使用Promise
的方式,但相对于Fetch API
来说,它的API
更为灵活,可以方便地进行扩展和定制。- 使用场景:
Ajax
、Fetch
和Axios
在不同的使用场景下有不同的优势。Ajax
的优势在于它是原生的浏览器API
,功能简单、易用、兼容性好,适合简单的异步请求场景。Fetch API
的优势在于它是Promise
风格的API
,支持流式传输、文件上传和下载等功能,适合复杂的异步请求场景。而Axios
的优势在于它是一个功能齐全、易用、灵活的请求库,支持请求和响应拦截器、请求的取消和超时、并发请求等功能,适合复杂的异步请求场景。
Ajax | Fetch API | Axios | |
---|---|---|---|
基于Promise | 🛑(基于 XMLHttpRequest) | ✅ | ✅ |
兼容性 | ✅ 具有较好的兼容性,几乎可以在所有浏览器上运行 | 🟡 对于现代浏览器的兼容性更好 | 🟡 对于现代浏览器的兼容性更好 |
TypeScript支持 | 🛑 | 🟡 | ✅ |
数据缓存 | 🛑 | ✅ | ✅ |
自动重试 | 🛑 | ✅ | ✅ |
取消请求 | 🟡 原生不支持,需手动编写实现 | 🟡 原生不支持,需手动编写实现 | ✅ |
请求异常 | 🟡 在网络异常和超时处理方面需要手动编写实现 | 🟡 在网络异常和超时处理方面需要手动编写实现 | ✅ 提供了更好的网络异常处理机制,可以方便地处理网络异常和超时异常 |
超时时间 | 🛑 | 🛑 | ✅ |
跨域 | 🟡 原生不支持跨域请求,需手动实现(如JSONP) | ✅ | ✅ |
错误处理 | 🟡 原生对于错误处理和状态码处理的支持相对较少 | 🟡 原生对于错误处理和状态码处理的支持相对较少 | ✅ 提供了丰富的错误处理和状态码处理的功能 |
请求响应拦截 | 🛑 | 🟡 | ✅(全局) |
并发请求 | ✅ | 🟡 原生不支持并发请求,需要手动编写实现 | ✅ |
数据转换 | 🟡 原生状态下需要手动将响应数据转换为 JavaScript 对象 | 🟡 原生状态下需要手动将响应数据转换为 JavaScript 对象 | ✅ 可以自动将响应数据转换为 JavaScript 对象 |
序列化参数 | ✅ 使用 URLSearchParams | 🟡 原生不支持 URLSearchParams 对象,需要手动编写实现 | ✅ 使用 URLSearchParams |
文件处理 | 🟡 原生不支持文件上传和下载,需要手动编写实现 | 🟡 原生不支持文件上传和下载,需要手动编写实现 | ✅ |
插件机制 | 🟡 在扩展性方面相对较差,需要手动编写实现 | 🟡 在扩展性方面相对较差,需要手动编写实现 | ✅ 支持自定义拦截器、转换器、扩展请求和响应等功能 |
数据格式 | 🟡 对数据格式的支持相对较少,需要手动编写实现 | 🟡 对数据格式的支持相对较少,需要手动编写实现 | ✅ 支持多种数据格式的请求和响应,包括 JSON、URL-encoded、XML、HTML、Text 等 |
headers验证 | 🛑 | ✅ 支持通过 request.credentials 属性来控制请求是否携带 cookies 和 cors 相关的头部信息 | ✅ 可以使用 withCredentials 配置选项来控制请求是否携带 cookies 和 cors 相关的头部信息 |