本文深入探讨 JavaScript 中安全获取数据的方法。从原生 fetch 的局限性出发,分析错误处理、状态码识别及上下文丢失等问题,演示如何通过自定义错误类和封装类提升代码健壮性。最后对比推荐 axios、Redaxios 和 Wretch 等成熟解决方案,帮助开发者选择合适的数据请求库。
数据获取途径
fetch或许是JavaScript中获取数据最为普遍的手段。
然而,采用fetch进行数据获取的代码往往潜藏安全隐患:
代码演示:
const res = await fetch('/user')
const user = await res.json()
尽管上述代码简洁易用,却蕴含诸多隐患。
- 缺陷一、缺失“异常处理机制”
或许你会认为此问题易于解决,只需引入try/catch即可,一旦出错便会抛出异常!
代码演示:
try {
const res = await fetch('/user')
const user = await res.json()
} catch (err) {
// 处理错误的代码
}
如此写法确实能处理部分错误,但即便这样仍存在诸多弊端,对各类错误的覆盖并不周全。
- 缺陷二:部分状态码无法被识别
在此,我们假定user代表一个用户对象。假设我们接收到的响应状态为200。
然而fetch并不会针对非200的状态码抛出异常,因此若收到400(错误请求)、401(未授权)、404(未找到)、500(内部服务器错误)或其他各类异常,均不会触发错误抛出。
或许你会提出,使用if语句进行判断并对不同状态码分类处理岂不更好!
于是我们得到了如下代码
try {
const res = await fetch('/user')
if (!res.ok) {
switch (res.status) {
case 400: /* 错误处理 */ break
case 401: /* 错误处理 */ break
case 404: /* 错误处理 */ break
case 500: /*错误处理 */ break
}
}
// User 已经是我们最新的数据了
const user = await res.json()
} catch (err) {
// 错误处理
}
至此,我们基本实现了fetch对数据的安全获取。但这种写法显得臃肿且笨重,因为每次都必须重写错误处理逻辑;若是团队协作,对每位成员的要求更高,需确保所有同事遵循相同的请求处理逻辑。此外,代码可读性较差,维护起来颇为繁琐。
那么,我们能否采用更优雅的方式来处理逻辑代码呢?
更优雅的处理方式
我们可以利用throw来处理不同的错误响应,而非使用switch/case。
try {
const res = await fetch('/user')
if (!res.ok) {
throw new Error('错误的响应')
}
const user = await res.json()
} catch (err) {
// 错误处理
}
但我们仍面临最后一个问题——在处理错误时,我们丢失了大量有用的上下文信息。由于无法在catch块中访问这些信息,因此在处理错误时我们并不知晓响应的状态代码或错误的详细信息。
这将使调试变得异常困难,难以排查错误。那我们该如何获取error的上下文呢?
最佳方案或许是创建自定义错误类,并在其中转发响应的详细信息:
代码:
class ResponseError extends Error {
constructor(message, res) {
super(message)
this.response = res
}
}
try {
const res = await fetch('/user')
if (!res.ok) {
throw new ResponseError('错误的响应信息(error的上下文信息)', res)
}
const user = await res.json()
} catch (err) {
//我们可以拿到错误的详细信息,也就是error的上下文
switch (err.response.status) {
case 400: /* 错误处理 */ break
case 401: /* 错误处理 */ break
case 404: /* 错误处理 */ break
case 500: /* 错误处理 */ break
}
}
现在,我们保留了状态代码等error信息,这样既能让用户了解错误原因,也能让我们更有效地处理错误。
例如,我们可以提醒用户500表明我们遇到了问题,并允许客户联系我们以寻求解决。
或者,如果状态为401,则表明当前未授权,可能需要重新登录等。
封装类
尽管上述代码解决了我们的所有问题,但它仍存在不稳定性,即代码的健壮性取决于开发人员的个人素质与能力。我们的请求安全无法得到统一保障。
我们可以对代码进行封装,使用时直接导出引用即可
class ResponseError extends Error {
constructor(message, res) {
this.response = res
}
}
export async function myFetch(...options) {
const res = await fetch(...options)
if (!res.ok) {
throw new ResponseError('错误响应的信息', res)
}
return res
}
随后我们可以按以下方式调用它:
try {
const res = await myFetch('/user')
const user = await res.json()
} catch (err) {
// 错误的处理代码
}
在封装代码中,最好确保有一套统一的处理错误机制,因为这涉及用户警报、日志记录等。
开源解决方案
当然,如果我们的水平尚未达到能够自行封装完善请求类的程度,我们也可以使用网络上其他封装好的请求类,
axios
- axios是一个非常流行的JS数据请求库,它已经帮我们解决了上述探讨的几个问题。
try {
const { data } = await axios.get('/user')
} catch (err) {
// 错误处理代码
}
我认为Axios唯一的缺点是包体积过大,如果我们在项目中仅获取简单数据时使用axios需要引入一个11kb的包,,反而会使我们的项目变得臃肿。
Redaxios
如果你觉得项目体积对你更为重要,你可以选择Redaxios
- Redaxios使用与Axios相同的API,但体积却不到[1kb]
import axios from 'redaxios'
// use as you would normally
Wretch
另一个不错的选项是Wretch,它是将Fetch封装成的一个非常小的包,与Redaxios类似。Wretch的特别之处在于它在很大程度上还原了原生的数据请求方法,但它帮我们封装了许多错误处理代码。
const user = await wretch("/user")
.get()
// Handle error cases in a more human-readable way
.notFound(error => { /* ... */ })
.unauthorized(error => { /* ... */ })
.error(418, error => { /* ... */ })
.res(response => /* ... */)
.catch(error => { /* uncaught errors */ })以上就是一文详解在JavaScript中如何安全的进行数据获取的详细内容,更多关于JavaScript数据安全获取的资料请关注本站其它相关文章!