给 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 等服务端环境中使用。最后希望能够帮助到大家。

相关推荐
神仙别闹16 分钟前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
aPurpleBerry40 分钟前
JS常用数组方法 reduce filter find forEach
javascript
GIS程序媛—椰子1 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0011 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端1 小时前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x1 小时前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟10091 小时前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43912 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢2 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安2 小时前
前端第二次作业
前端·css·css3