使用装饰器模式强大你的 fetch
1. 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应用程序的bundle大小。其次,您的应用程序与第三方库相结合:您获得了该库的所有好处,但也得到了它的所有bug。
我的目的是采用一种不同的方法,从这两个方面都得到了最好的结果——使用装饰器模式来增加fetch() API的易用性和灵活性。
其思想是将一个基fetch类(我将展示如何定义它)包装为您需要的任何其他功能:提取JSON、超时、在糟糕的HTTP状态下抛出错误、处理auth头,等等。让我们在下一节中看看如何做到这一点。
2. 准备 Fetcher 接口
装饰器模式非常有用,因为它支持以灵活和松散耦合的方式在基本逻辑之上添加功能(换句话说——装饰)。
如果你不熟悉装饰模式,我建议您阅读它是如何工作的。
应用装饰器来增强fetch()需要几个简单的步骤:
- 第一步是声明一个名为
Fetcher
的抽象接口:
type ResponseWithData = Response & { data?: any };
interface Fetcher {
run(
input: RequestInfo,
init?: RequestInit
): Promise<ResponseWithData>;
}
Fetcher
接口只有一个方法,它接受相同的参数并返回与常规fetch()
相同的数据类型。
- 第二步是实现基本的
fetcher
类:
class BasicFetcher implements Fetcher {
run(
input: RequestInfo,
init?: RequestInit
): Promise<ResponseWithData> {
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
类没有带来好处。此外,由于新接口和新类的出现,事情变得更加复杂!稍等片刻,你会发现当装饰者模式被引入到行动中时所带来的巨大好处。
3. 给提取 JSON 数据的方法添加装饰器
装饰器模式的主要是装饰器类。
装饰器类必须符合Fetcher接口,包装被装饰的实例,以及在run()方法中引入额外的功能。
让我们实现一个从响应对象中提取JSON数据的装饰器:
class JsonFetcherDecorator implements Fetcher {
private decoratee: Fetcher;
constructor (decoratee: Fetcher) {
this.decoratee = decoratee;
}
async run(
input: RequestInfo,
init?: RequestInit
): Promise<ResponseWithData> {
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, init)
执行实际的数据获取。
然后json = await response.json()
从响应中提取json
数据。最后,响应。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' }]
现在,您可以从响应对象的data
属性访问所提取的数据,而不是从响应中手动提取JSON数据。
通过将JSON提取器移动到装饰器,现在在任何使用const {data} = decoratedFetch(URL)
的地方,你都不必手动提取JSON对象。
4. 创建请求超时装饰器
默认情况下,fetch() API
会在浏览器指定的时间超时。在Chrome中,网络请求超时时间为300
秒,而在Firefox中超时时间为90
秒。
用户可以等待8
秒来完成简单的请求。这就是为什么需要为网络请求设置一个超时,并在8
秒后通知用户网络问题的原因。
装饰器模式的伟大之处在于,可以使用任意多的装饰器来装饰你的基本实现!那么,让我们为取回请求创建一个超时装饰器:
const TIMEOUT = 8000; // 8 seconds
class TimeoutFetcherDecorator implements Fetcher {
private decoratee: Fetcher;
constructor(decoratee: Fetcher) {
this.decoratee = decoratee;
}
async run(
input: RequestInfo,
init?: RequestInit
): Promise<ResponseWithData> {
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
接口的decorator
。
在TimeoutFetcherDecorator
的run()
方法内部:如果请求在8
秒内没有完成,则使用中止控制器中止请求。
现在让我们来使用这个装饰器:
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) {
// Timeouts if the request takes
// longer than 8 seconds
console.log(error.name);
}
}
executeRequest();
// if the request takes more than 8 seconds
// logs "AbortError"
在这个示例中,对/movies.json
的请求需要超过8秒。
decoratedFetch('/movies.json')
,由于TimeoutFetcherDecorator
,抛出超时错误。
现在基本的获取器被封装在2
个装饰器中:一个提取JSON对象,另一个在8
秒内超时请求。这极大地简化了decoratedFetch()
的使用:当调用decoratedFetch()
时,decorator
逻辑将为你工作。
5. 总结
fetch() API
提供了执行获取请求的基本功能。但你需要的不止这些。单独使用fetch()
强制你手动从请求中提取JSON
数据,配置超时,等等。
为了避免样板文件,你可以使用更友好的库,如axios
。然而,使用像axios这样的第三方库会增加应用包的大小,同时你也会与之紧密结合。
另一种解决方案是在fetch()
上面应用装饰器模式。您可以创建从请求中提取JSON、超时请求等等的装饰器。你可以随时组合、添加或删除装饰器,而不会影响使用装饰器的代码。
- 点赞
- 收藏
- 关注作者
评论(0)