
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、超时请求等等的装饰器。你可以随时组合、添加或删除装饰器,而不会影响使用装饰器的代码。






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)