乐趣区

关于javascript:如何使用装饰器模式极大地增强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 利用的捆绑大小。其次,你的应用程序与第三方库联合在一起:你失去了该库的所有益处,但也失去了它的所有 bug。

我打算采纳一种从两种状况中都吸取最大好处的办法 - 应用装璜器模式来进步 fetch() API 的易用性和灵活性。

在下一节中,咱们将介绍如何做到这一点。

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'}]

Try the demo

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 数据。最初,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'}]

Try the demo

当初,不必再从响应中手动提取 JSON 数据,你能够从响应对象的 data 属性中拜访提取的数据。

通过将 JSON 提取器移至装璜器,当初在每个应用 const {data} = decoratedFetch(URL) 的中央,你都不用手动提取 JSON 对象。

4. 申请超时装璜器

fetch() API 默认在浏览器指定的工夫超时,在 Chrome 浏览器中,网络申请的超时工夫为 300 秒,而在 Firefox 浏览器中则为 90 秒。

用户最多能够期待 8 秒的工夫来实现简略的申请,所以须要对网络申请设置超时,8 秒后告知用户网络问题。

装璜器模式的益处是,你能够用任意多的装璜器来装璜你的根本实现。所以,让咱们为 fetch 申请创立一个超时装璜器。

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 接口的装璜器。

TimeoutFetcherDecoratorrun() 办法外面:如果在 8 秒内还没有实现申请,就用 AbortController 来停止申请。

当初,让这个装璜器开始工作:

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"

Try the demo

在 demo 中,对 /movies.json 的申请耗时超过 8 秒。

因为 TimeoutFetcherDecorator 的缘故,decoratedFetch('/ movies.json’) 引发超时谬误。

当初,根本的 fetcher 被包裹在 2 个装璜器中:一个提取 JSON 对象,另一个在 8 秒内超时完结申请,这极大地简化了 decoratedFetch() 的应用。

5. 总结

fetch() API 提供了执行获取申请的基本功能,但你须要的不止这些,应用 fetch() 将强制你手动从申请中提取 JSON 数据、配置超时等等。

为了防止模板,你能够应用一个更敌对的库,比方 axios。然而,应用像 axios 这样的第三方库会减少应用程序的捆绑大小,以及你与它的严密耦合。

另一种解决方案是在 fetch() 之上利用装璜器模式。你能够制作装璜器,从申请中提取 JSON,超时申请等等。你能够随时组合、增加或删除装璜器,而不影响应用装璜的 fetch 的代码。

你想把 fetch() 和最常见的装璜器一起应用吗?我为你创立了要点!能够在你的应用程序中随便应用它,甚至能够依据本人的须要增加更多装璜器!

还有什么其余的 fetch() 装璜器可能有用?请在上面的评论中写下你的实现 (请应用 TypeScript)

退出移动版