给 RxJS 的 Ajax 的功能拦截器

RxJS 系列文章

一、简介

因为 RxJS 是一个函数式和响应式的编程库,并且 RxJS 也在网络请求方面也有了自己工具封装封装 Ajax 和 fetch。

以上是 RxJS 请求先关的大致内容。

二、RxJS 使用 ajax

方法 描述
ajax.get(url) 发送GET请求
ajax.post(url, data) 发送POST请求
ajax.put(url, data) 发送PUT请求
ajax.patch(url, data) 发送PATCH请求
ajax.delete(url) 发送DELETE请求
ajax({ method, url, ...}) 通用的ajax方法,支持自定义HTTP方法和配置

ajax 方法 API 特别的好理解,ajax 方法上能配置对象(常常是 options),当然也有 ajax 的 http 方法(get/post/...) 等不同的方法。

三、RxJS ajax 示例

ts 复制代码
import { ajax } from 'rxjs/ajax';
import { map, catchError } from 'rxjs/operators';

const url = 'https://jsonplaceholder.typicode.com/posts/1';

ajax.getJSON(url).pipe(
  map(response => {
    console.log('成功的响应:', response);
    // 在这里处理响应数据
    return response;
  }),
  catchError(error => {
    console.error('发生错误:', error);
    // 在这里处理错误
    throw error;
  })
).subscribe({
    next(){},
});

此处使用 ajax 的 getJSON 方法获取 json数据,使用 map 操作符进行数据转换,在 catchError 操作符中进行错误捕获。

四、RxJS 使用 fetch

API 语法

ts 复制代码
fromFetch<T>(
    input: string | Request, 
    initWithSelector: RequestInit & { selector?: (response: Response) => ObservableInput<T>; } = {}): Observable<Response | T>

常用方法

方法 描述
fromFetch(url) 发送GET请求
fromFetch(url, initWithSelector) 发送GET请求

五、RxJS fromFetch

在 RxJS 提供的 fetch 方法中,rxjs具有客观对象的能力,使用 map 和 catchError 操作符,

ts 复制代码
import { fromFetch } from 'rxjs/fetch';
import { catchError, map } from 'rxjs/operators';

const url = 'https://jsonplaceholder.typicode.com/posts/1';

fromFetch(url).pipe(
  catchError(error => {
    console.error('发生错误:', error);
    // 在这里处理错误
    throw error;
  }),
  map(response => {
    if (!response.ok) {
      throw new Error(`网络错误: ${response.status}`);
    }
    return response.json();
  })
).subscribe(data => {
  console.log('成功的响应:', data);
  // 在这里处理响应数据
});

六、RxJS ajax 定义

它为一个Ajax请求创建一个observable,这个observable要么是一个带有url、header等的请求对象,要么是一个URL字符串。

在我们开始 RxJS ajax 之前,我们开始回顾 Axios 和 fetch API 的基本认知。

七、axios 基本内容

  • 从浏览器基于 XHR
  • 从 Node.js 基于 http
  • Promise API
  • 拦截器和转换器
  • ...

关于本文和 axios 着重关注Promise 和拦截器。

7.1) 添加拦截器

ts 复制代码
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });

7.2) 移除拦截器

ts 复制代码
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

八、一个简单的 RxJS Ajax 拦截器实现

ts 复制代码
import { ajax, AjaxRequest, AjaxResponse } from 'rxjs/ajax';
import { Observable } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';

// 自定义拦截器类
class AjaxInterceptor {
  intercept(request: AjaxRequest): Observable<AjaxRequest> {
    // 在这里可以对请求进行修改或添加头部等操作
    console.log('Request Interceptor:', request);
    return Observable.of(request);
  }

  interceptResponse(response: AjaxResponse): Observable<AjaxResponse> {
    // 在这里可以对响应进行修改,处理错误等操作
    console.log('Response Interceptor:', response);
    return Observable.of(response);
  }
}

具体用法

ts 复制代码
// 创建配置实例
const ajaxConfig = new AjaxConfig();

// 注册拦截器
const interceptorA = new AjaxInterceptor();
const interceptorB = new AjaxInterceptor();

ajaxConfig.use(interceptorA);
ajaxConfig.use(interceptorB);

// 创建并发送 Ajax 请求
const request: AjaxRequest = {
  url: 'https://jsonplaceholder.typicode.com/posts/1',
  method: 'GET',
};

ajaxConfig.sendRequest(request).subscribe(
  (response) => console.log('Final Response:', response),
  (error) => console.error('Error:', error)
);

九、分析

我们需要一个拦截器类,管理拦截器相关的内容,整体实现的内容是拦截器和响应拦截器。然后就实现一个 AjaxConfig 类。

ts 复制代码
// 配置类,包含 use 方法用于注册中间件
class AjaxConfig {
  private interceptors: AjaxInterceptor[] = [];

  // 注册拦截器中间件的方法
  use(interceptor: AjaxInterceptor): void {
    this.interceptors.push(interceptor);
  }

  // 发送 Ajax 请求的方法,应用注册的拦截器
  sendRequest(request: AjaxRequest): Observable<AjaxResponse> {
    let observable: Observable<AjaxRequest> = Observable.of(request);

    // 应用注册的拦截器
    this.interceptors.forEach((interceptor) => {
      observable = observable.pipe(mergeMap((req) => interceptor.intercept(req)));
    });

    // 发送实际的 Ajax 请求
    return observable.pipe(
      mergeMap((req) => ajax(req)),
      map((response) => {
        // 应用响应拦截器
        this.interceptors.forEach((interceptor) => {
          response = interceptor.interceptResponse(response);
        });
        return response;
      }),
      catchError((error) => {
        console.error('Error:', error);
        throw error;
      })
    );
  }
}

十、使用 from 操作符将 Promise 转化成可观察对象

Axios, fetch 都是基于 Promise 的请求对象。Promise 在 RxJS 中可以方便进行转换。from 操作符就是能方便的将 Promise 转化为可观察对象。

10.1) fetch

直接将 fetch 使用 from 操作符进行封装,将 Promise 进行转换

ts 复制代码
import { from } from 'rxjs';

const fetch$ = from(fetch(someUrl));

10.2) 在 pipe 函数中转换

有时候我们的需要输入、或者点击一些内容之后,在 pipe 函数中才能拿到数据,此时我们需要流的转换,此时我们就输需要 mergeMap。

ts 复制代码
import { fromEvent } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

const button = document.getElementById('myButton');

const clicl$ = fromEvent(button, 'click');

click$.pipe(
  mergeMap(() => fetch('https://api.example.com/data'))
).subscribe({
  next: (data) => {
    // 在这里处理接收到的数据
    console.log(data);
  },
  error: (error) => {
    // 处理错误
    console.error(error);
  }
});

十一、反向从客观到Promise

RxJS 中 from 操作符可方便的将各种不同的类型转换成可观察对象,也可以将可观察对象转换成 Promise。

11.1) toPromise

toPromsie 将在 v8 版本中被废弃

ts 复制代码
import { from } from 'rxjs';

from([1, 2, 3, 4, 5]).toPromise().then((result) => {
    // 处理成功时的结果
    console.log('Promise resolved with result:', result);
  },
  (error) => {
    // 处理发生错误时的情况
    console.error('Promise rejected with error:', error);
  }
);

次数输出的结果是 Promise resolved with result: 5

可观察对象具有 toPromise 方法,调用之后,可以使用 Promise thenable 处理异步操作。

11.2) lastValueFrom/firstValueFrom

为什么会有这些 API? 因为可观察对象可以发送处多个值,但是 Promise 只能决策出一个值。

11.2.1) lastValueFrom

lastValueFrom 使用Observable完成时到达的最后一个值进行解析,返回类型是 Promise<T>。

ts 复制代码
import { interval, take, lastValueFrom } from 'rxjs'

async function execute() {
  const source$ = interval(2000).pipe(take(10));
  const finalNumber = await lastValueFrom(source$); // `Promise<Number>
  console.log(finalNumber);
}

execute()

11.2.2) firstValueFrom

第一个值到达时就取它,而不等待Observable完成,因此你可以使用 firstValueFrom 。 firstValueFrom 将使用从Observable发出的第一个值解析Promise,并立即取消订阅以保留资源。如果Observable完成而没有发出任何值,则 firstValueFrom 也将拒绝 EmptyError 。

ts 复制代码
import { interval, firstValueFrom } from 'rxjs'

async function execute() {
  const source$ = interval(2000)
  const firstNumber = await firstValueFrom(source$)
  console.log(`The first number is ${firstNumber}`);
}

execute()

十一、请求重试

ts 复制代码
import { from, throwError, of } from "rxjs";
import { mergeMap, retry, catchError } from "rxjs/operators";

const apiUrl = "your address";

from(fetch(apiUrl))
  .pipe(
    mergeMap((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }
      return response.json();
    }),
    retry(3), // 重试3次
    catchError((error, retryCount) => {
      if (retryCount < 3) {
        // 可以根据具体情况判断是否需要重试
        console.log(`Retrying (${retryCount + 1})...`);
        return of(null); // 返回一个新的Observable来触发重试
      } else {
        return throwError("Max retries reached"); // 超过重试次数,抛出错误
      }
    })
  )
  .subscribe({
    next: (data) => {
      console.log("Data received:", data);
    },
    error: (error) => {
      console.error("Error:", error);
    },
  });

十二、小结

本文主要关注 RxJS 网络请求相关的问题,包含了 Ajax 和 fetch 两个先关的技术,以及使用方法,同时也关注底层的Promise化和重试网络请求、错误处理等等,当然我们如果需求也自己封装 RxJS 计划的 axios 方便我们在 Node.js 等服务端环境中使用。最后希望能够帮助到大家。

相关推荐
计算机魔术师1 小时前
管谷歌的不是Pichai一个人,是一整层AI智能体
前端
米米商聊1 小时前
图标能点,按钮就可用了吗?键盘、名称与收藏状态
前端·javascript
Younglina2 小时前
三个晚上,我和 WorkBuddy 做了个生图网站
前端·后端·ai编程
xy34532 小时前
Axure 9.0 内联框架加载方式
前端·ui·html·axure·原型·产品设计
技术小甜甜2 小时前
【Cursor】Skill 被标黄色警告,怎么改成合格的 SKILL.md
开发语言·javascript·ai编程·cursor·1024程序员节·ai agent·skills
陈随易3 小时前
副业搞钱点子之收入查看器
前端·后端
Profile排查笔记3 小时前
浏览器指纹逆向怎么分析:从网络请求追踪到 JavaScript 采集链
前端·人工智能·后端·自动化
hudie7653 小时前
LikeShop 二开最常改的 5 个文件(附改前改后对比)
前端·uni-app·php·个人开发
qetfw4 小时前
Bash 函数与流程控制:read、if、for、case 验证
linux·前端·javascript·bash
鬼手点金4 小时前
从 API 消息到模型 Token:Chat Template
java·前端·javascript·数据库·人工智能·深度学习·1024程序员节