fetch() 很好用,但还能更好用
fetch()
API使你可以在 Web 应用中执行网络请求。
fetch()
的用法非常简单:通过调用 fetch('/movies.json')
启动请求,请求完成后得到一个 Response 对象,然后从中提取数据。
这是一个简单的例子,说明如何从 /movies.json
URL获取 JSON 格式的电影数据:
async function executeRequest() {
const response = await fetch('/movies.json');
const moviesJson = await response.json();
console.log(moviesJson);
}
executeRequest();
// logs [{ name: 'Heat' }, { name: 'Alien' }]
如上面的代码所示,你必须手动从响应中提取 JSON 对象:moviesJson = await response.json()
。这样做一两次没什么问题。但是如果你的程序需要执行许多请求,那么反复用 await response.json()
提取 JSON 对象就显得有些笨拙了。
这时就想到了使用第三方库,例如 axios,这个库大大简化了请求的处理。下面的代码用 axios
封装相同的功能:
async function executeRequest() {
const moviesJson = await axios('/movies.json');
console.log(moviesJson);
}
executeRequest();
// logs [{ name: 'Heat' }, { name: 'Alien' }]
moviesJson = await axios('/movies.json')
能够返回实际的 JSON 响应,不必像 fetch()
那样手动提取JSON。
但是如果使用 axios
之类的库会带来一系列问题。
首先,它增加了 Web 程序的包大小;其次,你的程序与第三方库结合在了一起,不管是好处还是bug。
所以就想到了第三种方法——用装饰器模式来提高 fetch()
API 的易用性和灵活性。
准备Fetcher 接口
装饰器模式非常强大,它能够以灵活和松散耦合的方式在基本逻辑之上添加功能(也就是所谓的“装饰”)。
使用装饰器来增强 fetch()
需要几个简单的步骤。
第一步是声明一个名为 Fetcher
的抽象接口:
type ResponseWithData = Response & { data?: any };
interface Fetcher {
run(
input: RequestInfo,
init?: RequestInit
): Promise;
}
Fetcher
接口只有一个方法,该方法接受与普通 fetch()
相同的参数并返回相同类型的数据。
第二步是实现基本的访存器类:
class BasicFetcher implements Fetcher {
run(
input: RequestInfo,
init?: RequestInit
): Promise {
return fetch(input, init);
}
}
BasicFetcher
实现 Fetcher
接口。它的run()
方法调用 fetch()
函数。就这么简单。
下面是用基本的 fetcher 类来获取数据的代码:
const fetcher = new BasicFetcher();const decoratedFetch = fetcher.run.bind(fetcher);
async function executeRequest() {
const response = await decoratedFetch('/movies.json'); const moviesJson = await response.json();
console.log(moviesJson);
}
executeRequest();
// logs [{ name: 'Heat' }, { name: 'Alien' }]
const fetcher = new BasicFetcher()
创建 fetcher 类的实例。 decoratedFetch = fetcher.run.bind(fetcher)
创建了一个绑定方法。
然后,就可以用 decoratedFetch('/movies.json')
来获取 JSON 数据了,就像使用普通的 fetch()
一样。
在这个步骤中, BasicFetcher
类不会带来任何好处。而且由于引入了新的接口和类,使代码变得更加复杂了。
JSON 提取装饰器
装饰器模式的核心是装饰器类。
装饰器类必须符合 Fetcher
接口,包装装饰后的实例,并在 run()
方法中引入其他功能。
下面实现一个装饰器,它从 response
对象中提取 JSON 数据:
class JsonFetcherDecorator implements Fetcher {
private decoratee: Fetcher;
constructor (decoratee: Fetcher) {
this.decoratee = decoratee;
}
async run(
input: RequestInfo,
init?: RequestInit
): Promise {
const response = await this.decoratee.run(input, init);
const json = await response.json();
response.data = json;
return response;
}
}
接下来看看 JsonFetcherDecorator
是怎样构造的。
JsonFetcherDecorator
符合 Fetcher
接口。JsonExtractorFetch
中有私有字段 Decoratee
,它也符合 Fetcher
接口。在 run()
方法中,this.decoratee.run(input,ini)
获取数据。
然后 json = await response.json()
从响应中提取 JSON 数据。最后,response.data = json
将提取的 JSON 数据分配给响应对象。
下面用 JsonFetcherDecorator
装饰器来装饰 BasicFetcher
,并简化 fetch()
的使用:
const fetcher = new JsonFetcherDecorator( new BasicFetcher());const decoratedFetch = fetcher.run.bind(fetcher);
async function executeRequest() {
const { data } = await decoratedFetch('/movies.json'); console.log(data);
}
executeRequest();
// logs [{ name: 'Heat' }, { name: 'Alien' }]
现在就可以直接从 response 对象的 data
属性访问提取的数据,在每个使用 const { data } = decoratedFetch(URL)
的地方,都不必手动提取 JSON 对象了。
请求超时装饰器
默认情况下,fetch()
API 的超时时间受到浏览器的制约。在 Chrome 中,网络请求超时为 300 秒,而在 Firefox 中为 90 秒。
假设我们最多可以等待 8 秒才能完成简单的请求,这时需要设置网络请求超时时间,并在 8。秒后通知用户有关网络问题的信息。
装饰器模式的优点在于:可以根据需要使用任意数量的装饰器来装饰基本实现。接下来再为 fetch 请求创建一个超时装饰器:
const TIMEOUT = 8000; // 8 秒超时
class TimeoutFetcherDecorator implements Fetcher {
private decoratee: Fetcher;
constructor(decoratee: Fetcher) {
this.decoratee = decoratee;
}
async run(
input: RequestInfo,
init?: RequestInit
): Promise {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), TIMEOUT);
const response = await this.decoratee.run(input, {
...init,
signal: controller.signal
});
clearTimeout(id);
return response;
}
}
TimeoutFetcherDecorator
是实现 Fetcher
接口的装饰器。
如果请求没有在 8 秒内完成,那么在 TimeoutFetcherDecorator.run()
方法中的 controller.abort()
会中止请求。
测试一下:
const fetcher = new TimeoutFetcherDecorator(
new JsonFetcherDecorator(
new BasicFetcher()
)
);
const decoratedFetch = fetcher.run.bind(fetcher);
async function executeRequest() {
try {
const { data } = await decoratedFetch('/movies.json');
console.log(data);
} catch (error) {
//如果请求超过8秒
console.log(error.name);
}
}
executeRequest();
//如果请求耗时超过8秒则输出 “AbortError”
由于使用了 TimeoutFetcherDecorator
,decoratedFetch('/movies.json')
会引发超时错误。
现在 fetch()
被 2 个装饰器包装:一个装饰器用于提取 JSON 对象,另一个在 8 秒内使请求超时。这就极大地简化了 DecoratedFetch()
的使用。
总结
fetch()
API提供了请求的基本功能。但是只用 fetch()
会强制你从请求中手动提取 JSON 数据,自己去配置超时等等。
尽管我们可以使用第三方库,但是使用 axios
之类的库会增加程序包的大小,并增加耦合度。
通过使用装饰器模式,可以使装饰器从请求中提取 JSON,使请求超时等等。另外还可以随时合并、添加或删除装饰器,而不会影响使用了带装饰器的 fetch()
的其他代码。
本文首发微信公众号:前端先锋
欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章
欢迎继续阅读本专栏其它高赞文章:
- 深入理解Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13个帮你提高开发效率的现代CSS框架
- 快速上手BootstrapVue
- JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切
- WebSocket实战:在 Node 和 React 之间进行实时通信
- 关于 Git 的 20 个面试题
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什么?
- 30分钟用Node.js构建一个API服务器
- Javascript的对象拷贝
- 程序员30岁前月薪达不到30K,该何去何从
- 14个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩展插件
- Node.js 多线程完全指南
- 把HTML转成PDF的4个方案及实现