日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

axios取消功能的设计与实现

發(fā)布時間:2023/12/10 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 axios取消功能的设计与实现 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

取消功能的設(shè)計(jì)與實(shí)現(xiàn)

#需求分析

有些場景下,我們希望能主動取消請求,比如常見的搜索框案例,在用戶輸入過程中,搜索框的內(nèi)容也在不斷變化,正常情況每次變化我們都應(yīng)該向服務(wù)端發(fā)送一次請求。但是當(dāng)用戶輸入過快的時候,我們不希望每次變化請求都發(fā)出去,通常一個解決方案是前端用 debounce 的方案,比如延時 200ms 發(fā)送請求。這樣當(dāng)用戶連續(xù)輸入的字符,只要輸入間隔小于 200ms,前面輸入的字符都不會發(fā)請求。

但是還有一種極端情況是后端接口很慢,比如超過 1s 才能響應(yīng),這個時候即使做了 200ms 的 debounce,但是在我慢慢輸入(每個輸入間隔超過 200ms)的情況下,在前面的請求沒有響應(yīng)前,也有可能發(fā)出去多個請求。因?yàn)榻涌诘捻憫?yīng)時長是不定的,如果先發(fā)出去的請求響應(yīng)時長比后發(fā)出去的請求要久一些,后請求的響應(yīng)先回來,先請求的響應(yīng)后回來,就會出現(xiàn)前面請求響應(yīng)結(jié)果覆蓋后面請求響應(yīng)結(jié)果的情況,那么就亂了。因此在這個場景下,我們除了做 debounce,還希望后面的請求發(fā)出去的時候,如果前面的請求還沒有響應(yīng),我們可以把前面的請求取消。

從 axios 的取消接口設(shè)計(jì)層面,我們希望做如下的設(shè)計(jì):

const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('/user/12345', { cancelToken: source.token }).catch(function (e) { if (axios.isCancel(e)) { console.log('Request canceled', e.message); } else { // 處理錯誤 } }); // 取消請求 (請求原因是可選的) source.cancel('Operation canceled by the user.');

我們給?axios?添加一個?CancelToken?的對象,它有一個?source?方法可以返回一個?source?對象,source.token?是在每次請求的時候傳給配置對象中的?cancelToken?屬性,然后在請求發(fā)出去之后,我們可以通過?source.cancel?方法取消請求。

我們還支持另一種方式的調(diào)用:

const CancelToken = axios.CancelToken; let cancel; axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { cancel = c; }) }); // 取消請求 cancel();

axios.CancelToken?是一個類,我們直接把它實(shí)例化的對象傳給請求配置中的?cancelToken?屬性,CancelToken?的構(gòu)造函數(shù)參數(shù)支持傳入一個?executor?方法,該方法的參數(shù)是一個取消函數(shù)?c,我們可以在?executor?方法執(zhí)行的內(nèi)部拿到這個取消函數(shù)?c,賦值給我們外部定義的?cancel?變量,之后我們可以通過調(diào)用這個?cancel?方法來取消請求。

#異步分離的設(shè)計(jì)方案

通過需求分析,我們知道想要實(shí)現(xiàn)取消某次請求,我們需要為該請求配置一個?cancelToken,然后在外部調(diào)用一個?cancel?方法。

請求的發(fā)送是一個異步過程,最終會執(zhí)行?xhr.send?方法,xhr?對象提供了?abort?方法,可以把請求取消。因?yàn)槲覀冊谕獠渴桥霾坏?xhr?對象的,所以我們想在執(zhí)行?cancel?的時候,去執(zhí)行?xhr.abort?方法。

現(xiàn)在就相當(dāng)于我們在?xhr?異步請求過程中,插入一段代碼,當(dāng)我們在外部執(zhí)行?cancel?函數(shù)的時候,會驅(qū)動這段代碼的執(zhí)行,然后執(zhí)行?xhr.abort?方法取消請求。

我們可以利用 Promise 實(shí)現(xiàn)異步分離,也就是在?cancelToken?中保存一個?pending?狀態(tài)的 Promise 對象,然后當(dāng)我們執(zhí)行?cancel?方法的時候,能夠訪問到這個 Promise 對象,把它從?pending?狀態(tài)變成?resolved?狀態(tài),這樣我們就可以在?then?函數(shù)中去實(shí)現(xiàn)取消請求的邏輯,類似如下的代碼:

if (cancelToken) {cancelToken.promise .then(reason => { request.abort() reject(reason) }) }

#CancelToken 類實(shí)現(xiàn)

接下來,我們就來實(shí)現(xiàn)這個?CancelToken?類,先來看一下接口定義:

#接口定義

types/index.ts:

export interface AxiosRequestConfig {// ... cancelToken?: CancelToken } export interface CancelToken { promise: Promise<string> reason?: string } export interface Canceler { (message?: string): void } export interface CancelExecutor { (cancel: Canceler): void }

其中?CancelToken?是實(shí)例類型的接口定義,Canceler?是取消方法的接口定義,CancelExecutor?是?CancelToken?類構(gòu)造函數(shù)參數(shù)的接口定義。

#代碼實(shí)現(xiàn)

我們單獨(dú)創(chuàng)建?cancel?目錄來管理取消相關(guān)的代碼,在?cancel?目錄下創(chuàng)建?CancelToken.ts?文件:

import { CancelExecutor } from '../types' interface ResolvePromise { (reason?: string): void } export default class CancelToken { promise: Promise<string> reason?: string constructor(executor: CancelExecutor) { let resolvePromise: ResolvePromise this.promise = new Promise<string>(resolve => { resolvePromise = resolve }) executor(message => { if (this.reason) { return } this.reason = message resolvePromise(this.reason) }) } }

在?CancelToken?構(gòu)造函數(shù)內(nèi)部,實(shí)例化一個?pending?狀態(tài)的 Promise 對象,然后用一個?resolvePromise?變量指向?resolve?函數(shù)。接著執(zhí)行?executor?函數(shù),傳入一個?cancel?函數(shù),在?cancel?函數(shù)內(nèi)部,會調(diào)用?resolvePromise?把 Promise 對象從?pending?狀態(tài)變?yōu)?resolved?狀態(tài)。

接著我們在?xhr.ts?中插入一段取消請求的邏輯。

core/xhr.ts:

const { /*....*/ cancelToken } = config if (cancelToken) { cancelToken.promise.then(reason => { request.abort() reject(reason) }) }

這樣就滿足了第二種使用方式,接著我們要實(shí)現(xiàn)第一種使用方式,給?CancelToken?擴(kuò)展靜態(tài)接口。

#CancelToken 擴(kuò)展靜態(tài)接口

#接口定義

types/index.ts:

export interface CancelTokenSource {token: CancelToken cancel: Canceler } export interface CancelTokenStatic { new(executor: CancelExecutor): CancelToken source(): CancelTokenSource }

其中?CancelTokenSource?作為?CancelToken?類靜態(tài)方法?source?函數(shù)的返回值類型,CancelTokenStatic?則作為?CancelToken?類的類類型。

#代碼實(shí)現(xiàn)

cancel/CancelToken.ts:

export default class CancelToken { // ... static source(): CancelTokenSource { let cancel!: Canceler const token = new CancelToken(c => { cancel = c }) return { cancel, token } } }

source?的靜態(tài)方法很簡單,定義一個?cancel?變量實(shí)例化一個?CancelToken?類型的對象,然后在?executor?函數(shù)中,把?cancel?指向參數(shù)?c?這個取消函數(shù)。

這樣就滿足了我們第一種使用方式,但是在第一種使用方式的例子中,我們在捕獲請求的時候,通過?axios.isCancel?來判斷這個錯誤參數(shù) e 是不是一次取消請求導(dǎo)致的錯誤,接下來我們對取消錯誤的原因做一層包裝,并且把給?axios?擴(kuò)展靜態(tài)方法

#Cancel 類實(shí)現(xiàn)及 axios 的擴(kuò)展

#接口定義

export interface Cancel {message?: string } export interface CancelStatic { new(message?: string): Cancel } export interface AxiosStatic extends AxiosInstance { create(config?: AxiosRequestConfig): AxiosInstance CancelToken: CancelTokenStatic Cancel: CancelStatic isCancel: (value: any) => boolean }

其中?Cancel?是實(shí)例類型的接口定義,CancelStatic?是類類型的接口定義,并且我們給?axios?擴(kuò)展了多個靜態(tài)方法。

#代碼實(shí)現(xiàn)

我在?cancel?目錄下創(chuàng)建?Cancel.ts?文件。

export default class Cancel { message?: string constructor(message?: string) { this.message = message } } export function isCancel(value: any): boolean { return value instanceof Cancel }

Cancel?類非常簡單,擁有一個?message?的公共屬性。isCancel?方法也非常簡單,通過?instanceof來判斷傳入的值是不是一個?Cancel?對象。

接著我們對?CancelToken?類中的?reason?類型做修改,把它變成一個?Cancel?類型的實(shí)例。

先修改定義部分。

types/index.ts:

export interface CancelToken {promise: Promise<Cancel> reason?: Cancel }

再修改實(shí)現(xiàn)部分:

import Cancel from './Cancel'interface ResolvePromise { (reason?: Cancel): void } export default class CancelToken { promise: Promise<Cancel> reason?: Cancel constructor(executor: CancelExecutor) { let resolvePromise: ResolvePromise this.promise = new Promise<Cancel>(resolve => { resolvePromise = resolve }) executor(message => { if (this.reason) { return } this.reason = new Cancel(message) resolvePromise(this.reason) }) } }

接下來我們給?axios?擴(kuò)展一些靜態(tài)方法,供用戶使用。

axios.ts:

import CancelToken from './cancel/CancelToken' import Cancel, { isCancel } from './cancel/Cancel' axios.CancelToken = CancelToken axios.Cancel = Cancel axios.isCancel = isCancel

#額外邏輯實(shí)現(xiàn)

除此之外,我們還需要實(shí)現(xiàn)一些額外邏輯,比如當(dāng)一個請求攜帶的?cancelToken?已經(jīng)被使用過,那么我們甚至都可以不發(fā)送這個請求,只需要拋一個異常即可,并且拋異常的信息就是我們?nèi)∠脑?#xff0c;所以我們需要給?CancelToken?擴(kuò)展一個方法。

先修改定義部分。

types/index.ts:

export interface CancelToken {promise: Promise<Cancel> reason?: Cancel throwIfRequested(): void }

添加一個?throwIfRequested?方法,接下來實(shí)現(xiàn)它:

cancel/CancelToken.ts:

export default class CancelToken { // ... throwIfRequested(): void { if (this.reason) { throw this.reason } } }

判斷如果存在?this.reason,說明這個?token?已經(jīng)被使用過了,直接拋錯。

接下來在發(fā)送請求前增加一段邏輯。

core/dispatchRequest.ts:

export default function dispatchRequest(config: AxiosRequestConfig): AxiosPromise { throwIfCancellationRequested(config) processConfig(config) // ... } function throwIfCancellationRequested(config: AxiosRequestConfig): void { if (config.cancelToken) { config.cancelToken.throwIfRequested() } }

發(fā)送請求前檢查一下配置的 cancelToken 是否已經(jīng)使用過了,如果已經(jīng)被用過則不用法請求,直接拋異常。

#demo 編寫

在?examples?目錄下創(chuàng)建?cancel?目錄,在?cancel?目錄下創(chuàng)建?index.html:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Cancel example</title> </head> <body> <script src="/__build__/cancel.js"></script> </body> </html>

接著創(chuàng)建?app.ts?作為入口文件:

import axios, { Canceler } from '../../src/index' const CancelToken = axios.CancelToken const source = CancelToken.source() axios.get('/cancel/get', { cancelToken: source.token }).catch(function(e) { if (axios.isCancel(e)) { console.log('Request canceled', e.message) } }) setTimeout(() => { source.cancel('Operation canceled by the user.') axios.post('/cancel/post', { a: 1 }, { cancelToken: source.token }).catch(function(e) { if (axios.isCancel(e)) { console.log(e.message) } }) }, 100) let cancel: Canceler axios.get('/cancel/get', { cancelToken: new CancelToken(c => { cancel = c }) }).catch(function(e) { if (axios.isCancel(e)) { console.log('Request canceled') } }) setTimeout(() => { cancel() }, 200)

我們的 demo 展示了 2 種使用方式,也演示了如果一個 token 已經(jīng)被使用過,則再次攜帶該 token 的請求并不會發(fā)送。

至此,我們完成了?ts-axios?的請求取消功能,我們巧妙地利用了 Promise 實(shí)現(xiàn)了異步分離。目前官方?axios?庫的一些大的 feature 我們都已經(jīng)實(shí)現(xiàn)了,下面的章節(jié)我們就開始補(bǔ)充完善?ts-axios?的其它功能。

轉(zhuǎn)載于:https://www.cnblogs.com/QianDingwei/p/11403916.html

總結(jié)

以上是生活随笔為你收集整理的axios取消功能的设计与实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。