疯狂的技术宅

以前出于工作目的,编写和翻译了大量的技术文章,以前端为主,删掉了过时的、毫无营养的内容,留下的都是精华。


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于本站

  • 回到主站

  • 搜索

用装饰器模式增强 fetch()

时间: 2021-03-01 分类: 前端技术   字数: 1806 字 阅读: 4分钟
标签: #装饰器# #设计模式# #JavaScript#

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<ResponseWithData>;
} 

Fetcher 接口只有一个方法,该方法接受与普通 fetch() 相同的参数并返回相同类型的数据。

第二步是实现基本的访存器类:

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 类不会带来任何好处。而且由于引入了新的接口和类,使代码变得更加复杂了。

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

如果请求没有在 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() 的其他代码。

标签: #装饰器# #设计模式# #JavaScript#

标题:用装饰器模式增强 fetch()

链接:https://fe-tech.viewnode.com/post/202103/01/

作者:疯狂的技术宅

声明: 本博客文章除特别声明外,均采用 CC BY-NC-ND 4.0 国际许可协议( 知识共享署名-非商业性使用-禁止演绎 4.0),转载请注明出处!

即将发布的 ES2021(ES12)中有哪些有趣的功能
在 promise 中 then 和 finally 有什么区别
  • 文章目录
  • 站点概览
疯狂的技术宅

疯狂的技术宅

退休程序员,硬件发烧友,人工智能爱好者。写写代码喝喝茶,晒晒太阳带带娃。

457 日志
8 分类
583 标签
GitHub
友情链接
  • viewnode
  • mofish
标签云
  • Javascript 172
  • Node.Js 62
  • Vue 36
  • Typescript 28
  • 实战项目 28
  • 面试 21
  • React 20
  • Css 17
  • 面试题 16
  • 教程 13
  • Promise 12
  • Chrome 9
  • Debug 9
  • 调试 9
  • 资源 9
  • Deno 8
  • Dom 8
  • 杂谈 8
  • 正则表达式 8
  • 测试 8
  • fetch() 很好用,但还能更好用
  • 准备Fetcher 接口
  • JSON 提取装饰器
  • 请求超时装饰器
  • 总结
© 2018 - 2022 疯狂的技术宅 All Rights Reserved
Powered by - Hugo v0.99.0 / Theme by - NexT
Storage by 俺的服务器 / 冀ICP备2022010157号
0%