[{"data":1,"prerenderedAt":1776},["ShallowReactive",2],{"post-thinking/avoid-duplicate-requests":3},{"id":4,"title":5,"body":6,"date":1766,"description":1767,"extension":1768,"meta":1769,"navigation":102,"path":1770,"seo":1771,"stem":1772,"tags":1773,"__hash__":1775},"content/thinking/避免重复请求的思考.md","避免重复请求",{"type":7,"value":8,"toc":1762},"minimark",[9,12,21,24,27,33,609,613,616,1736,1739,1748,1758],[10,11,5],"h1",{"id":5},[13,14,15,16,20],"p",{},"对于一个HTTP请求，前端可以经由",[17,18,19],"code",{},"AbortController","取消浏览器对该请求的响应；但请求仍会在服务器处理",[13,22,23],{},"故而，通过在Axios中取消请求，想减少代码在UI上的锁机制是行不通的",[13,25,26],{},"如果此时有一个表单的修改操作，用户重复按下5次，“取消请求”后，服务器仍会收到5次修改操作。这种重复请求被发送到服务器显然是一种资源浪费，甚至污染日志数据。通常，前端会在表单确认按钮上添加loading保证响应返回。而后端如何处理，我理解的是后端如何解决数据竞争问题，此处不做讨论",[13,28,29,30,32],{},"恰巧，本人刚从事前端开发时，网上很盛行如下写法，",[17,31,19],{},"结合请求拦截器和响应拦截器，使用取消响应来解决重复请求的问题。",[34,35,40],"pre",{"className":36,"code":37,"language":38,"meta":39,"style":39},"language-ts shiki shiki-themes github-light github-dark","/*\n * @Description: AbortController解决请求未返回时取消发送同样请求\n */\nimport type { AxiosRequestConfig } from 'axios'\nimport qs from 'qs'\n\nexport class RequestCanceler {\n  // 存储每个请求的标志和取消函数\n  private pendingRequestMap: Map\u003Cstring, AbortController>\n  constructor() {\n    this.pendingRequestMap = new Map\u003Cstring, AbortController>()\n  }\n\n  private generateReqKey(config: AxiosRequestConfig): string {\n    const { method, url } = config\n    return [url || '', method || '', qs.stringify(config.params), qs.stringify(config.data)].join('&')\n  }\n\n  addPendingRequest(config: AxiosRequestConfig) {\n    const requestKey: string = this.generateReqKey(config)\n    if (!this.pendingRequestMap.has(requestKey)) {\n      const controller = new AbortController()\n      // 给config挂载signal\n      config.signal = controller.signal\n      this.pendingRequestMap.set(requestKey, controller)\n    }\n    else {\n      // 如果requestKey已经存在，则获取之前设置的controller，并挂载signal\n      config.signal = this.pendingRequestMap.get(requestKey)!.signal\n    }\n  }\n\n  removePendingRequest(config: AxiosRequestConfig) {\n    const requestKey = this.generateReqKey(config)\n    // message.destroy(requestKey)\n    if (this.pendingRequestMap.has(requestKey)) {\n      // 取消请求\n      ;this.pendingRequestMap.get(requestKey)!.abort()\n      // 从pendingRequest中删掉\n      this.pendingRequestMap.delete(requestKey)\n    }\n  }\n}\n","ts","",[17,41,42,51,57,63,84,97,104,120,126,157,166,194,200,205,234,259,306,311,316,333,360,384,403,409,420,434,440,448,454,476,481,486,491,507,524,530,545,551,574,580,593,598,603],{"__ignoreMap":39},[43,44,47],"span",{"class":45,"line":46},"line",1,[43,48,50],{"class":49},"sJ8bj","/*\n",[43,52,54],{"class":45,"line":53},2,[43,55,56],{"class":49}," * @Description: AbortController解决请求未返回时取消发送同样请求\n",[43,58,60],{"class":45,"line":59},3,[43,61,62],{"class":49}," */\n",[43,64,66,70,73,77,80],{"class":45,"line":65},4,[43,67,69],{"class":68},"szBVR","import",[43,71,72],{"class":68}," type",[43,74,76],{"class":75},"sVt8B"," { AxiosRequestConfig } ",[43,78,79],{"class":68},"from",[43,81,83],{"class":82},"sZZnC"," 'axios'\n",[43,85,87,89,92,94],{"class":45,"line":86},5,[43,88,69],{"class":68},[43,90,91],{"class":75}," qs ",[43,93,79],{"class":68},[43,95,96],{"class":82}," 'qs'\n",[43,98,100],{"class":45,"line":99},6,[43,101,103],{"emptyLinePlaceholder":102},true,"\n",[43,105,107,110,113,117],{"class":45,"line":106},7,[43,108,109],{"class":68},"export",[43,111,112],{"class":68}," class",[43,114,116],{"class":115},"sScJk"," RequestCanceler",[43,118,119],{"class":75}," {\n",[43,121,123],{"class":45,"line":122},8,[43,124,125],{"class":49},"  // 存储每个请求的标志和取消函数\n",[43,127,129,132,136,139,142,145,149,152,154],{"class":45,"line":128},9,[43,130,131],{"class":68},"  private",[43,133,135],{"class":134},"s4XuR"," pendingRequestMap",[43,137,138],{"class":68},":",[43,140,141],{"class":115}," Map",[43,143,144],{"class":75},"\u003C",[43,146,148],{"class":147},"sj4cs","string",[43,150,151],{"class":75},", ",[43,153,19],{"class":115},[43,155,156],{"class":75},">\n",[43,158,160,163],{"class":45,"line":159},10,[43,161,162],{"class":68},"  constructor",[43,164,165],{"class":75},"() {\n",[43,167,169,172,175,178,181,183,185,187,189,191],{"class":45,"line":168},11,[43,170,171],{"class":147},"    this",[43,173,174],{"class":75},".pendingRequestMap ",[43,176,177],{"class":68},"=",[43,179,180],{"class":68}," new",[43,182,141],{"class":115},[43,184,144],{"class":75},[43,186,148],{"class":147},[43,188,151],{"class":75},[43,190,19],{"class":115},[43,192,193],{"class":75},">()\n",[43,195,197],{"class":45,"line":196},12,[43,198,199],{"class":75},"  }\n",[43,201,203],{"class":45,"line":202},13,[43,204,103],{"emptyLinePlaceholder":102},[43,206,208,210,213,216,219,221,224,227,229,232],{"class":45,"line":207},14,[43,209,131],{"class":68},[43,211,212],{"class":115}," generateReqKey",[43,214,215],{"class":75},"(",[43,217,218],{"class":134},"config",[43,220,138],{"class":68},[43,222,223],{"class":115}," AxiosRequestConfig",[43,225,226],{"class":75},")",[43,228,138],{"class":68},[43,230,231],{"class":147}," string",[43,233,119],{"class":75},[43,235,237,240,243,246,248,251,254,256],{"class":45,"line":236},15,[43,238,239],{"class":68},"    const",[43,241,242],{"class":75}," { ",[43,244,245],{"class":147},"method",[43,247,151],{"class":75},[43,249,250],{"class":147},"url",[43,252,253],{"class":75}," } ",[43,255,177],{"class":68},[43,257,258],{"class":75}," config\n",[43,260,262,265,268,271,274,277,279,281,284,287,290,292,295,298,300,303],{"class":45,"line":261},16,[43,263,264],{"class":68},"    return",[43,266,267],{"class":75}," [url ",[43,269,270],{"class":68},"||",[43,272,273],{"class":82}," ''",[43,275,276],{"class":75},", method ",[43,278,270],{"class":68},[43,280,273],{"class":82},[43,282,283],{"class":75},", qs.",[43,285,286],{"class":115},"stringify",[43,288,289],{"class":75},"(config.params), qs.",[43,291,286],{"class":115},[43,293,294],{"class":75},"(config.data)].",[43,296,297],{"class":115},"join",[43,299,215],{"class":75},[43,301,302],{"class":82},"'&'",[43,304,305],{"class":75},")\n",[43,307,309],{"class":45,"line":308},17,[43,310,199],{"class":75},[43,312,314],{"class":45,"line":313},18,[43,315,103],{"emptyLinePlaceholder":102},[43,317,319,322,324,326,328,330],{"class":45,"line":318},19,[43,320,321],{"class":115},"  addPendingRequest",[43,323,215],{"class":75},[43,325,218],{"class":134},[43,327,138],{"class":68},[43,329,223],{"class":115},[43,331,332],{"class":75},") {\n",[43,334,336,338,341,343,345,348,351,354,357],{"class":45,"line":335},20,[43,337,239],{"class":68},[43,339,340],{"class":147}," requestKey",[43,342,138],{"class":68},[43,344,231],{"class":147},[43,346,347],{"class":68}," =",[43,349,350],{"class":147}," this",[43,352,353],{"class":75},".",[43,355,356],{"class":115},"generateReqKey",[43,358,359],{"class":75},"(config)\n",[43,361,363,366,369,372,375,378,381],{"class":45,"line":362},21,[43,364,365],{"class":68},"    if",[43,367,368],{"class":75}," (",[43,370,371],{"class":68},"!",[43,373,374],{"class":147},"this",[43,376,377],{"class":75},".pendingRequestMap.",[43,379,380],{"class":115},"has",[43,382,383],{"class":75},"(requestKey)) {\n",[43,385,387,390,393,395,397,400],{"class":45,"line":386},22,[43,388,389],{"class":68},"      const",[43,391,392],{"class":147}," controller",[43,394,347],{"class":68},[43,396,180],{"class":68},[43,398,399],{"class":115}," AbortController",[43,401,402],{"class":75},"()\n",[43,404,406],{"class":45,"line":405},23,[43,407,408],{"class":49},"      // 给config挂载signal\n",[43,410,412,415,417],{"class":45,"line":411},24,[43,413,414],{"class":75},"      config.signal ",[43,416,177],{"class":68},[43,418,419],{"class":75}," controller.signal\n",[43,421,423,426,428,431],{"class":45,"line":422},25,[43,424,425],{"class":147},"      this",[43,427,377],{"class":75},[43,429,430],{"class":115},"set",[43,432,433],{"class":75},"(requestKey, controller)\n",[43,435,437],{"class":45,"line":436},26,[43,438,439],{"class":75},"    }\n",[43,441,443,446],{"class":45,"line":442},27,[43,444,445],{"class":68},"    else",[43,447,119],{"class":75},[43,449,451],{"class":45,"line":450},28,[43,452,453],{"class":49},"      // 如果requestKey已经存在，则获取之前设置的controller，并挂载signal\n",[43,455,457,459,461,463,465,468,471,473],{"class":45,"line":456},29,[43,458,414],{"class":75},[43,460,177],{"class":68},[43,462,350],{"class":147},[43,464,377],{"class":75},[43,466,467],{"class":115},"get",[43,469,470],{"class":75},"(requestKey)",[43,472,371],{"class":68},[43,474,475],{"class":75},".signal\n",[43,477,479],{"class":45,"line":478},30,[43,480,439],{"class":75},[43,482,484],{"class":45,"line":483},31,[43,485,199],{"class":75},[43,487,489],{"class":45,"line":488},32,[43,490,103],{"emptyLinePlaceholder":102},[43,492,494,497,499,501,503,505],{"class":45,"line":493},33,[43,495,496],{"class":115},"  removePendingRequest",[43,498,215],{"class":75},[43,500,218],{"class":134},[43,502,138],{"class":68},[43,504,223],{"class":115},[43,506,332],{"class":75},[43,508,510,512,514,516,518,520,522],{"class":45,"line":509},34,[43,511,239],{"class":68},[43,513,340],{"class":147},[43,515,347],{"class":68},[43,517,350],{"class":147},[43,519,353],{"class":75},[43,521,356],{"class":115},[43,523,359],{"class":75},[43,525,527],{"class":45,"line":526},35,[43,528,529],{"class":49},"    // message.destroy(requestKey)\n",[43,531,533,535,537,539,541,543],{"class":45,"line":532},36,[43,534,365],{"class":68},[43,536,368],{"class":75},[43,538,374],{"class":147},[43,540,377],{"class":75},[43,542,380],{"class":115},[43,544,383],{"class":75},[43,546,548],{"class":45,"line":547},37,[43,549,550],{"class":49},"      // 取消请求\n",[43,552,554,557,559,561,563,565,567,569,572],{"class":45,"line":553},38,[43,555,556],{"class":75},"      ;",[43,558,374],{"class":147},[43,560,377],{"class":75},[43,562,467],{"class":115},[43,564,470],{"class":75},[43,566,371],{"class":68},[43,568,353],{"class":75},[43,570,571],{"class":115},"abort",[43,573,402],{"class":75},[43,575,577],{"class":45,"line":576},39,[43,578,579],{"class":49},"      // 从pendingRequest中删掉\n",[43,581,583,585,587,590],{"class":45,"line":582},40,[43,584,425],{"class":147},[43,586,377],{"class":75},[43,588,589],{"class":115},"delete",[43,591,592],{"class":75},"(requestKey)\n",[43,594,596],{"class":45,"line":595},41,[43,597,439],{"class":75},[43,599,601],{"class":45,"line":600},42,[43,602,199],{"class":75},[43,604,606],{"class":45,"line":605},43,[43,607,608],{"class":75},"}\n",[610,611,612],"h4",{"id":612},"不发送请求",[13,614,615],{},"然而，当然可以不发送请求，可以有两种方式，一种是每个按钮添加loading；另一种是在请求层统一处理，如下",[34,617,619],{"className":36,"code":618,"language":38,"meta":39,"style":39},"import type { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios'\nimport axios from 'axios'\nimport qs from 'qs'\n\n/** “不发送重复请求”：同键已有在途请求则抛错，不取消、不替换上一次请求 */\nexport class DuplicateRequestGuard {\n  /** 在途请求键（无 AbortController：不通过 abort 取消在途请求） */\n  private pendingKeys = new Set\u003Cstring>()\n\n  /** 用 method + url + 序列化后的 params/data 作为“同一请求”的键；排序保证键稳定 */\n  private generateReqKey(config: AxiosRequestConfig): string {\n    const { method = '', url = '' } = config\n    const params = qs.stringify(config.params, { sort: (a, b) => a.localeCompare(b) })\n    const data = typeof config.data === 'string'\n      ? config.data\n      : qs.stringify(config.data, { sort: (a, b) => a.localeCompare(b) })\n\n    return [method, url, params, data].join('&')\n  }\n\n  /** 若同键已在途则抛错，由拦截器转为 reject，本次请求不会发出 */\n  addPendingRequest(config: AxiosRequestConfig) {\n    const key = this.generateReqKey(config)\n    if (this.pendingKeys.has(key)) {\n      throw new Error('DUPLICATE_REQUEST')\n    }\n    this.pendingKeys.add(key)\n  }\n\n  /** 成功或失败都要调用，否则 Set 会一直认为该键仍在途 */\n  removePendingRequest(config: AxiosRequestConfig) {\n    const key = this.generateReqKey(config)\n    this.pendingKeys.delete(key)\n  }\n\n  clear() {\n    this.pendingKeys.clear()\n  }\n}\n\n// 与拦截器共用的单例（须放在类声明之后，避免 TDZ）\nconst duplicateRequestGuard = new DuplicateRequestGuard()\n\nclass RequestHttp {\n  private service: AxiosInstance\n\n  constructor(config: AxiosRequestConfig) {\n    this.service = axios.create(config)\n\n    this.service.interceptors.request.use(\n      (config) => {\n        try {\n          duplicateRequestGuard.addPendingRequest(config)\n        }\n        catch (e) {\n          // 避免prefer-promise-reject-errors，即 reject 须为 Error，再用属性区分业务类型\n          return Promise.reject(\n            Object.assign(new Error('重复请求'), { type: 'duplicate' as const })\n          )\n        }\n        return config\n      },\n      error => Promise.reject(error)\n    )\n\n    this.service.interceptors.response.use(\n      (response) => {\n        duplicateRequestGuard.removePendingRequest(response.config)\n\n        const { data } = response\n        const { code } = data\n\n        if (code === 401) {\n          // 401\n          return Promise.reject(data)\n        }\n\n        if (code && code !== 200) {\n          return Promise.reject(data)\n        }\n\n        return data\n      },\n      (error: AxiosError) => {\n        if (error.config) {\n          duplicateRequestGuard.removePendingRequest(error.config)\n        }\n\n        if (error.message?.includes('timeout')) {\n          showWarning('请求超时')\n        }\n\n        if (!navigator.onLine) {\n          window.location.hash = '/500'\n        }\n\n        return Promise.reject(error)\n      }\n    )\n  }\n\n  request\u003CT = any>(config: AxiosRequestConfig): Promise\u003CT> {\n    return this.service.request(config)\n  }\n}\n",[17,620,621,634,645,655,659,664,675,680,700,704,709,731,757,803,824,832,864,868,883,887,891,896,910,927,943,960,964,976,980,984,989,1003,1019,1029,1033,1037,1044,1055,1059,1063,1067,1072,1088,1092,1103,1116,1121,1136,1154,1159,1173,1187,1195,1206,1212,1221,1227,1243,1279,1285,1290,1298,1304,1322,1328,1333,1345,1359,1371,1376,1394,1410,1415,1431,1437,1451,1456,1461,1482,1495,1500,1505,1512,1517,1536,1544,1554,1559,1564,1583,1596,1601,1606,1618,1629,1634,1639,1652,1658,1663,1668,1673,1711,1726,1731],{"__ignoreMap":39},[43,622,623,625,627,630,632],{"class":45,"line":46},[43,624,69],{"class":68},[43,626,72],{"class":68},[43,628,629],{"class":75}," { AxiosError, AxiosInstance, AxiosRequestConfig } ",[43,631,79],{"class":68},[43,633,83],{"class":82},[43,635,636,638,641,643],{"class":45,"line":53},[43,637,69],{"class":68},[43,639,640],{"class":75}," axios ",[43,642,79],{"class":68},[43,644,83],{"class":82},[43,646,647,649,651,653],{"class":45,"line":59},[43,648,69],{"class":68},[43,650,91],{"class":75},[43,652,79],{"class":68},[43,654,96],{"class":82},[43,656,657],{"class":45,"line":65},[43,658,103],{"emptyLinePlaceholder":102},[43,660,661],{"class":45,"line":86},[43,662,663],{"class":49},"/** “不发送重复请求”：同键已有在途请求则抛错，不取消、不替换上一次请求 */\n",[43,665,666,668,670,673],{"class":45,"line":99},[43,667,109],{"class":68},[43,669,112],{"class":68},[43,671,672],{"class":115}," DuplicateRequestGuard",[43,674,119],{"class":75},[43,676,677],{"class":45,"line":106},[43,678,679],{"class":49},"  /** 在途请求键（无 AbortController：不通过 abort 取消在途请求） */\n",[43,681,682,684,687,689,691,694,696,698],{"class":45,"line":122},[43,683,131],{"class":68},[43,685,686],{"class":134}," pendingKeys",[43,688,347],{"class":68},[43,690,180],{"class":68},[43,692,693],{"class":115}," Set",[43,695,144],{"class":75},[43,697,148],{"class":147},[43,699,193],{"class":75},[43,701,702],{"class":45,"line":128},[43,703,103],{"emptyLinePlaceholder":102},[43,705,706],{"class":45,"line":159},[43,707,708],{"class":49},"  /** 用 method + url + 序列化后的 params/data 作为“同一请求”的键；排序保证键稳定 */\n",[43,710,711,713,715,717,719,721,723,725,727,729],{"class":45,"line":168},[43,712,131],{"class":68},[43,714,212],{"class":115},[43,716,215],{"class":75},[43,718,218],{"class":134},[43,720,138],{"class":68},[43,722,223],{"class":115},[43,724,226],{"class":75},[43,726,138],{"class":68},[43,728,231],{"class":147},[43,730,119],{"class":75},[43,732,733,735,737,739,741,743,745,747,749,751,753,755],{"class":45,"line":196},[43,734,239],{"class":68},[43,736,242],{"class":75},[43,738,245],{"class":147},[43,740,347],{"class":68},[43,742,273],{"class":82},[43,744,151],{"class":75},[43,746,250],{"class":147},[43,748,347],{"class":68},[43,750,273],{"class":82},[43,752,253],{"class":75},[43,754,177],{"class":68},[43,756,258],{"class":75},[43,758,759,761,764,766,769,771,774,777,780,783,785,788,791,794,797,800],{"class":45,"line":202},[43,760,239],{"class":68},[43,762,763],{"class":147}," params",[43,765,347],{"class":68},[43,767,768],{"class":75}," qs.",[43,770,286],{"class":115},[43,772,773],{"class":75},"(config.params, { ",[43,775,776],{"class":115},"sort",[43,778,779],{"class":75},": (",[43,781,782],{"class":134},"a",[43,784,151],{"class":75},[43,786,787],{"class":134},"b",[43,789,790],{"class":75},") ",[43,792,793],{"class":68},"=>",[43,795,796],{"class":75}," a.",[43,798,799],{"class":115},"localeCompare",[43,801,802],{"class":75},"(b) })\n",[43,804,805,807,810,812,815,818,821],{"class":45,"line":207},[43,806,239],{"class":68},[43,808,809],{"class":147}," data",[43,811,347],{"class":68},[43,813,814],{"class":68}," typeof",[43,816,817],{"class":75}," config.data ",[43,819,820],{"class":68},"===",[43,822,823],{"class":82}," 'string'\n",[43,825,826,829],{"class":45,"line":236},[43,827,828],{"class":68},"      ?",[43,830,831],{"class":75}," config.data\n",[43,833,834,837,839,841,844,846,848,850,852,854,856,858,860,862],{"class":45,"line":261},[43,835,836],{"class":68},"      :",[43,838,768],{"class":75},[43,840,286],{"class":115},[43,842,843],{"class":75},"(config.data, { ",[43,845,776],{"class":115},[43,847,779],{"class":75},[43,849,782],{"class":134},[43,851,151],{"class":75},[43,853,787],{"class":134},[43,855,790],{"class":75},[43,857,793],{"class":68},[43,859,796],{"class":75},[43,861,799],{"class":115},[43,863,802],{"class":75},[43,865,866],{"class":45,"line":308},[43,867,103],{"emptyLinePlaceholder":102},[43,869,870,872,875,877,879,881],{"class":45,"line":313},[43,871,264],{"class":68},[43,873,874],{"class":75}," [method, url, params, data].",[43,876,297],{"class":115},[43,878,215],{"class":75},[43,880,302],{"class":82},[43,882,305],{"class":75},[43,884,885],{"class":45,"line":318},[43,886,199],{"class":75},[43,888,889],{"class":45,"line":335},[43,890,103],{"emptyLinePlaceholder":102},[43,892,893],{"class":45,"line":362},[43,894,895],{"class":49},"  /** 若同键已在途则抛错，由拦截器转为 reject，本次请求不会发出 */\n",[43,897,898,900,902,904,906,908],{"class":45,"line":386},[43,899,321],{"class":115},[43,901,215],{"class":75},[43,903,218],{"class":134},[43,905,138],{"class":68},[43,907,223],{"class":115},[43,909,332],{"class":75},[43,911,912,914,917,919,921,923,925],{"class":45,"line":405},[43,913,239],{"class":68},[43,915,916],{"class":147}," key",[43,918,347],{"class":68},[43,920,350],{"class":147},[43,922,353],{"class":75},[43,924,356],{"class":115},[43,926,359],{"class":75},[43,928,929,931,933,935,938,940],{"class":45,"line":411},[43,930,365],{"class":68},[43,932,368],{"class":75},[43,934,374],{"class":147},[43,936,937],{"class":75},".pendingKeys.",[43,939,380],{"class":115},[43,941,942],{"class":75},"(key)) {\n",[43,944,945,948,950,953,955,958],{"class":45,"line":422},[43,946,947],{"class":68},"      throw",[43,949,180],{"class":68},[43,951,952],{"class":115}," Error",[43,954,215],{"class":75},[43,956,957],{"class":82},"'DUPLICATE_REQUEST'",[43,959,305],{"class":75},[43,961,962],{"class":45,"line":436},[43,963,439],{"class":75},[43,965,966,968,970,973],{"class":45,"line":442},[43,967,171],{"class":147},[43,969,937],{"class":75},[43,971,972],{"class":115},"add",[43,974,975],{"class":75},"(key)\n",[43,977,978],{"class":45,"line":450},[43,979,199],{"class":75},[43,981,982],{"class":45,"line":456},[43,983,103],{"emptyLinePlaceholder":102},[43,985,986],{"class":45,"line":478},[43,987,988],{"class":49},"  /** 成功或失败都要调用，否则 Set 会一直认为该键仍在途 */\n",[43,990,991,993,995,997,999,1001],{"class":45,"line":483},[43,992,496],{"class":115},[43,994,215],{"class":75},[43,996,218],{"class":134},[43,998,138],{"class":68},[43,1000,223],{"class":115},[43,1002,332],{"class":75},[43,1004,1005,1007,1009,1011,1013,1015,1017],{"class":45,"line":488},[43,1006,239],{"class":68},[43,1008,916],{"class":147},[43,1010,347],{"class":68},[43,1012,350],{"class":147},[43,1014,353],{"class":75},[43,1016,356],{"class":115},[43,1018,359],{"class":75},[43,1020,1021,1023,1025,1027],{"class":45,"line":493},[43,1022,171],{"class":147},[43,1024,937],{"class":75},[43,1026,589],{"class":115},[43,1028,975],{"class":75},[43,1030,1031],{"class":45,"line":509},[43,1032,199],{"class":75},[43,1034,1035],{"class":45,"line":526},[43,1036,103],{"emptyLinePlaceholder":102},[43,1038,1039,1042],{"class":45,"line":532},[43,1040,1041],{"class":115},"  clear",[43,1043,165],{"class":75},[43,1045,1046,1048,1050,1053],{"class":45,"line":547},[43,1047,171],{"class":147},[43,1049,937],{"class":75},[43,1051,1052],{"class":115},"clear",[43,1054,402],{"class":75},[43,1056,1057],{"class":45,"line":553},[43,1058,199],{"class":75},[43,1060,1061],{"class":45,"line":576},[43,1062,608],{"class":75},[43,1064,1065],{"class":45,"line":582},[43,1066,103],{"emptyLinePlaceholder":102},[43,1068,1069],{"class":45,"line":595},[43,1070,1071],{"class":49},"// 与拦截器共用的单例（须放在类声明之后，避免 TDZ）\n",[43,1073,1074,1077,1080,1082,1084,1086],{"class":45,"line":600},[43,1075,1076],{"class":68},"const",[43,1078,1079],{"class":147}," duplicateRequestGuard",[43,1081,347],{"class":68},[43,1083,180],{"class":68},[43,1085,672],{"class":115},[43,1087,402],{"class":75},[43,1089,1090],{"class":45,"line":605},[43,1091,103],{"emptyLinePlaceholder":102},[43,1093,1095,1098,1101],{"class":45,"line":1094},44,[43,1096,1097],{"class":68},"class",[43,1099,1100],{"class":115}," RequestHttp",[43,1102,119],{"class":75},[43,1104,1106,1108,1111,1113],{"class":45,"line":1105},45,[43,1107,131],{"class":68},[43,1109,1110],{"class":134}," service",[43,1112,138],{"class":68},[43,1114,1115],{"class":115}," AxiosInstance\n",[43,1117,1119],{"class":45,"line":1118},46,[43,1120,103],{"emptyLinePlaceholder":102},[43,1122,1124,1126,1128,1130,1132,1134],{"class":45,"line":1123},47,[43,1125,162],{"class":68},[43,1127,215],{"class":75},[43,1129,218],{"class":134},[43,1131,138],{"class":68},[43,1133,223],{"class":115},[43,1135,332],{"class":75},[43,1137,1139,1141,1144,1146,1149,1152],{"class":45,"line":1138},48,[43,1140,171],{"class":147},[43,1142,1143],{"class":75},".service ",[43,1145,177],{"class":68},[43,1147,1148],{"class":75}," axios.",[43,1150,1151],{"class":115},"create",[43,1153,359],{"class":75},[43,1155,1157],{"class":45,"line":1156},49,[43,1158,103],{"emptyLinePlaceholder":102},[43,1160,1162,1164,1167,1170],{"class":45,"line":1161},50,[43,1163,171],{"class":147},[43,1165,1166],{"class":75},".service.interceptors.request.",[43,1168,1169],{"class":115},"use",[43,1171,1172],{"class":75},"(\n",[43,1174,1176,1179,1181,1183,1185],{"class":45,"line":1175},51,[43,1177,1178],{"class":75},"      (",[43,1180,218],{"class":134},[43,1182,790],{"class":75},[43,1184,793],{"class":68},[43,1186,119],{"class":75},[43,1188,1190,1193],{"class":45,"line":1189},52,[43,1191,1192],{"class":68},"        try",[43,1194,119],{"class":75},[43,1196,1198,1201,1204],{"class":45,"line":1197},53,[43,1199,1200],{"class":75},"          duplicateRequestGuard.",[43,1202,1203],{"class":115},"addPendingRequest",[43,1205,359],{"class":75},[43,1207,1209],{"class":45,"line":1208},54,[43,1210,1211],{"class":75},"        }\n",[43,1213,1215,1218],{"class":45,"line":1214},55,[43,1216,1217],{"class":68},"        catch",[43,1219,1220],{"class":75}," (e) {\n",[43,1222,1224],{"class":45,"line":1223},56,[43,1225,1226],{"class":49},"          // 避免prefer-promise-reject-errors，即 reject 须为 Error，再用属性区分业务类型\n",[43,1228,1230,1233,1236,1238,1241],{"class":45,"line":1229},57,[43,1231,1232],{"class":68},"          return",[43,1234,1235],{"class":147}," Promise",[43,1237,353],{"class":75},[43,1239,1240],{"class":115},"reject",[43,1242,1172],{"class":75},[43,1244,1246,1249,1252,1254,1257,1259,1261,1264,1267,1270,1273,1276],{"class":45,"line":1245},58,[43,1247,1248],{"class":75},"            Object.",[43,1250,1251],{"class":115},"assign",[43,1253,215],{"class":75},[43,1255,1256],{"class":68},"new",[43,1258,952],{"class":115},[43,1260,215],{"class":75},[43,1262,1263],{"class":82},"'重复请求'",[43,1265,1266],{"class":75},"), { type: ",[43,1268,1269],{"class":82},"'duplicate'",[43,1271,1272],{"class":68}," as",[43,1274,1275],{"class":68}," const",[43,1277,1278],{"class":75}," })\n",[43,1280,1282],{"class":45,"line":1281},59,[43,1283,1284],{"class":75},"          )\n",[43,1286,1288],{"class":45,"line":1287},60,[43,1289,1211],{"class":75},[43,1291,1293,1296],{"class":45,"line":1292},61,[43,1294,1295],{"class":68},"        return",[43,1297,258],{"class":75},[43,1299,1301],{"class":45,"line":1300},62,[43,1302,1303],{"class":75},"      },\n",[43,1305,1307,1310,1313,1315,1317,1319],{"class":45,"line":1306},63,[43,1308,1309],{"class":134},"      error",[43,1311,1312],{"class":68}," =>",[43,1314,1235],{"class":147},[43,1316,353],{"class":75},[43,1318,1240],{"class":115},[43,1320,1321],{"class":75},"(error)\n",[43,1323,1325],{"class":45,"line":1324},64,[43,1326,1327],{"class":75},"    )\n",[43,1329,1331],{"class":45,"line":1330},65,[43,1332,103],{"emptyLinePlaceholder":102},[43,1334,1336,1338,1341,1343],{"class":45,"line":1335},66,[43,1337,171],{"class":147},[43,1339,1340],{"class":75},".service.interceptors.response.",[43,1342,1169],{"class":115},[43,1344,1172],{"class":75},[43,1346,1348,1350,1353,1355,1357],{"class":45,"line":1347},67,[43,1349,1178],{"class":75},[43,1351,1352],{"class":134},"response",[43,1354,790],{"class":75},[43,1356,793],{"class":68},[43,1358,119],{"class":75},[43,1360,1362,1365,1368],{"class":45,"line":1361},68,[43,1363,1364],{"class":75},"        duplicateRequestGuard.",[43,1366,1367],{"class":115},"removePendingRequest",[43,1369,1370],{"class":75},"(response.config)\n",[43,1372,1374],{"class":45,"line":1373},69,[43,1375,103],{"emptyLinePlaceholder":102},[43,1377,1379,1382,1384,1387,1389,1391],{"class":45,"line":1378},70,[43,1380,1381],{"class":68},"        const",[43,1383,242],{"class":75},[43,1385,1386],{"class":147},"data",[43,1388,253],{"class":75},[43,1390,177],{"class":68},[43,1392,1393],{"class":75}," response\n",[43,1395,1397,1399,1401,1403,1405,1407],{"class":45,"line":1396},71,[43,1398,1381],{"class":68},[43,1400,242],{"class":75},[43,1402,17],{"class":147},[43,1404,253],{"class":75},[43,1406,177],{"class":68},[43,1408,1409],{"class":75}," data\n",[43,1411,1413],{"class":45,"line":1412},72,[43,1414,103],{"emptyLinePlaceholder":102},[43,1416,1418,1421,1424,1426,1429],{"class":45,"line":1417},73,[43,1419,1420],{"class":68},"        if",[43,1422,1423],{"class":75}," (code ",[43,1425,820],{"class":68},[43,1427,1428],{"class":147}," 401",[43,1430,332],{"class":75},[43,1432,1434],{"class":45,"line":1433},74,[43,1435,1436],{"class":49},"          // 401\n",[43,1438,1440,1442,1444,1446,1448],{"class":45,"line":1439},75,[43,1441,1232],{"class":68},[43,1443,1235],{"class":147},[43,1445,353],{"class":75},[43,1447,1240],{"class":115},[43,1449,1450],{"class":75},"(data)\n",[43,1452,1454],{"class":45,"line":1453},76,[43,1455,1211],{"class":75},[43,1457,1459],{"class":45,"line":1458},77,[43,1460,103],{"emptyLinePlaceholder":102},[43,1462,1464,1466,1468,1471,1474,1477,1480],{"class":45,"line":1463},78,[43,1465,1420],{"class":68},[43,1467,1423],{"class":75},[43,1469,1470],{"class":68},"&&",[43,1472,1473],{"class":75}," code ",[43,1475,1476],{"class":68},"!==",[43,1478,1479],{"class":147}," 200",[43,1481,332],{"class":75},[43,1483,1485,1487,1489,1491,1493],{"class":45,"line":1484},79,[43,1486,1232],{"class":68},[43,1488,1235],{"class":147},[43,1490,353],{"class":75},[43,1492,1240],{"class":115},[43,1494,1450],{"class":75},[43,1496,1498],{"class":45,"line":1497},80,[43,1499,1211],{"class":75},[43,1501,1503],{"class":45,"line":1502},81,[43,1504,103],{"emptyLinePlaceholder":102},[43,1506,1508,1510],{"class":45,"line":1507},82,[43,1509,1295],{"class":68},[43,1511,1409],{"class":75},[43,1513,1515],{"class":45,"line":1514},83,[43,1516,1303],{"class":75},[43,1518,1520,1522,1525,1527,1530,1532,1534],{"class":45,"line":1519},84,[43,1521,1178],{"class":75},[43,1523,1524],{"class":134},"error",[43,1526,138],{"class":68},[43,1528,1529],{"class":115}," AxiosError",[43,1531,790],{"class":75},[43,1533,793],{"class":68},[43,1535,119],{"class":75},[43,1537,1539,1541],{"class":45,"line":1538},85,[43,1540,1420],{"class":68},[43,1542,1543],{"class":75}," (error.config) {\n",[43,1545,1547,1549,1551],{"class":45,"line":1546},86,[43,1548,1200],{"class":75},[43,1550,1367],{"class":115},[43,1552,1553],{"class":75},"(error.config)\n",[43,1555,1557],{"class":45,"line":1556},87,[43,1558,1211],{"class":75},[43,1560,1562],{"class":45,"line":1561},88,[43,1563,103],{"emptyLinePlaceholder":102},[43,1565,1567,1569,1572,1575,1577,1580],{"class":45,"line":1566},89,[43,1568,1420],{"class":68},[43,1570,1571],{"class":75}," (error.message?.",[43,1573,1574],{"class":115},"includes",[43,1576,215],{"class":75},[43,1578,1579],{"class":82},"'timeout'",[43,1581,1582],{"class":75},")) {\n",[43,1584,1586,1589,1591,1594],{"class":45,"line":1585},90,[43,1587,1588],{"class":115},"          showWarning",[43,1590,215],{"class":75},[43,1592,1593],{"class":82},"'请求超时'",[43,1595,305],{"class":75},[43,1597,1599],{"class":45,"line":1598},91,[43,1600,1211],{"class":75},[43,1602,1604],{"class":45,"line":1603},92,[43,1605,103],{"emptyLinePlaceholder":102},[43,1607,1609,1611,1613,1615],{"class":45,"line":1608},93,[43,1610,1420],{"class":68},[43,1612,368],{"class":75},[43,1614,371],{"class":68},[43,1616,1617],{"class":75},"navigator.onLine) {\n",[43,1619,1621,1624,1626],{"class":45,"line":1620},94,[43,1622,1623],{"class":75},"          window.location.hash ",[43,1625,177],{"class":68},[43,1627,1628],{"class":82}," '/500'\n",[43,1630,1632],{"class":45,"line":1631},95,[43,1633,1211],{"class":75},[43,1635,1637],{"class":45,"line":1636},96,[43,1638,103],{"emptyLinePlaceholder":102},[43,1640,1642,1644,1646,1648,1650],{"class":45,"line":1641},97,[43,1643,1295],{"class":68},[43,1645,1235],{"class":147},[43,1647,353],{"class":75},[43,1649,1240],{"class":115},[43,1651,1321],{"class":75},[43,1653,1655],{"class":45,"line":1654},98,[43,1656,1657],{"class":75},"      }\n",[43,1659,1661],{"class":45,"line":1660},99,[43,1662,1327],{"class":75},[43,1664,1666],{"class":45,"line":1665},100,[43,1667,199],{"class":75},[43,1669,1671],{"class":45,"line":1670},101,[43,1672,103],{"emptyLinePlaceholder":102},[43,1674,1676,1679,1681,1684,1686,1689,1692,1694,1696,1698,1700,1702,1704,1706,1708],{"class":45,"line":1675},102,[43,1677,1678],{"class":115},"  request",[43,1680,144],{"class":75},[43,1682,1683],{"class":115},"T",[43,1685,347],{"class":68},[43,1687,1688],{"class":147}," any",[43,1690,1691],{"class":75},">(",[43,1693,218],{"class":134},[43,1695,138],{"class":68},[43,1697,223],{"class":115},[43,1699,226],{"class":75},[43,1701,138],{"class":68},[43,1703,1235],{"class":115},[43,1705,144],{"class":75},[43,1707,1683],{"class":115},[43,1709,1710],{"class":75},"> {\n",[43,1712,1714,1716,1718,1721,1724],{"class":45,"line":1713},103,[43,1715,264],{"class":68},[43,1717,350],{"class":147},[43,1719,1720],{"class":75},".service.",[43,1722,1723],{"class":115},"request",[43,1725,359],{"class":75},[43,1727,1729],{"class":45,"line":1728},104,[43,1730,199],{"class":75},[43,1732,1734],{"class":45,"line":1733},105,[43,1735,608],{"class":75},[610,1737,1738],{"id":1738},"请求共享或缓存响应",[13,1740,1741,1742],{},"不过比起手动实现我更加推荐使用 ",[782,1743,1747],{"href":1744,"rel":1745},"https://alova.js.org/zh-CN/about/comparison#alova-%E4%B8%BA-axios-%E6%8F%90%E4%BE%9B%E5%93%8D%E5%BA%94%E6%95%B0%E6%8D%AE%E7%BC%93%E5%AD%98",[1746],"nofollow","Alova",[13,1749,1750],{},[1751,1752,1753,1754,1757],"del",{},"仍然记得发现这个库时的惊喜，当时颇有天下苦",[17,1755,1756],{},"Axios","久矣的感觉",[1759,1760,1761],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":39,"searchDepth":65,"depth":65,"links":1763},[1764,1765],{"id":612,"depth":65,"text":612},{"id":1738,"depth":65,"text":1738},"2026-04-15","记录了我从入门到现今对于请求封装的看法","md",{},"/thinking/avoid-duplicate-requests",{"title":5,"description":1767},"thinking/避免重复请求的思考",[1774,1756,1747],"JavaScript","DMgbN0pt0grE_NNSczp_68bMN5X3p4bwNiv70gOZFS0",1776757930433]