JavaScript如何使用Promise實(shí)現(xiàn)分批處理接口請(qǐng)求
前言
不知你項(xiàng)目中有沒(méi)有遇到過(guò)這樣的情況,反正我的實(shí)際工作項(xiàng)目中真的遇到了這種玩意,一個(gè)接口獲取一份列表,列表中的每一項(xiàng)都有一個(gè)屬性需要通過(guò)另一個(gè)請(qǐng)求來(lái)逐一賦值,然后就有了這份封裝
真的是很多功能都是被逼出來(lái)的
這份功能中要提醒一下:批量請(qǐng)求最關(guān)鍵的除了分批功能之外,適當(dāng)?shù)萌∠蝿?wù)和繼續(xù)任務(wù)也很重要,比如用戶到了這個(gè)頁(yè)面后,正在發(fā)起百條數(shù)據(jù)請(qǐng)求,但是這些批量請(qǐng)求還沒(méi)完全執(zhí)行完,用戶離開(kāi)了這個(gè)頁(yè)面,此時(shí)就需要取消剩下正在發(fā)起的請(qǐng)求了,而且如果你像我的遇到的項(xiàng)目一樣,頁(yè)面還會(huì)被緩存,那么為了避免用戶回到這個(gè)頁(yè)面,所有請(qǐng)求又重新發(fā)起一遍的話,就需要實(shí)現(xiàn)繼續(xù)任務(wù)的功能,其實(shí)這個(gè)繼續(xù)任務(wù)比斷點(diǎn)續(xù)傳簡(jiǎn)單多了,就是過(guò)濾到那些已經(jīng)賦值的數(shù)據(jù)項(xiàng)就行了
如果看我啰啰嗦嗦一堆爛東西沒(méi)看明白的話,就直接看下面的源碼吧
代碼改進(jìn)說(shuō)明
這里需要特別感謝下網(wǎng)友熱心檢查,我原先寫(xiě)的代碼中確實(shí)存在問(wèn)題
(原先的代碼邏輯確實(shí)只是對(duì)響應(yīng)做了分批處理,只是我的實(shí)際的老項(xiàng)目代碼中也存在一定問(wèn)題,導(dǎo)致讓我反而也能實(shí)現(xiàn)分批發(fā)起請(qǐng)求的目的,但是還是需要改進(jìn)的)
感謝熱心網(wǎng)友的檢查: @倔犟小名 @月凡丶
以下內(nèi)容是改進(jìn)后的代碼
源碼在此
【注】:這里的 httpRequest 請(qǐng)根據(jù)自己項(xiàng)目而定,比如我的項(xiàng)目是uniapp,里面的http請(qǐng)求是 uni.request,若你的項(xiàng)目是 axios 或者 ajax,那就根據(jù)它們來(lái)對(duì) BatchHttp 中的某些部分進(jìn)行相應(yīng)的修改
比如:其中的 cancelAll() 函數(shù),若你的 http 取消請(qǐng)求的方式不同,那么這里取消請(qǐng)求的功能就需要相應(yīng)的修改,若你使用的是 fetch 請(qǐng)求,那除了修改 cancelAll 功能之外,singleRequest 中收集請(qǐng)求任務(wù)的方式也要修改,因?yàn)?fetch 是不可取消的,需要借助 AbortController 來(lái)實(shí)現(xiàn)取消請(qǐng)求的功能,
提示一下,不管你用的是什么請(qǐng)求框架,你都可以自己二次封裝一個(gè) request.js,功能就仿照 axios 這種,返回的對(duì)象中包含一個(gè) abort() 函數(shù)即可,那么這份 BatchHttp 也就能適用啦
簡(jiǎn)單案例測(cè)試 -- batch-promise-test.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> </body> <script> /** * 批量請(qǐng)求封裝 */ class BatchHttp { /** * 構(gòu)造函數(shù) * */ constructor() { } /** * 單個(gè)數(shù)據(jù)項(xiàng)請(qǐng)求 * @private * @param {Object} reqOptions - 請(qǐng)求配置 * @param {Object} item - 數(shù)據(jù)項(xiàng) * @returns {Promise} 請(qǐng)求Promise */ #singleRequest(item) { return new Promise((resolve, _reject) => { // 模擬異步請(qǐng)求 console.log(`發(fā)起模擬異步請(qǐng)求 padding...: 【${item}】`) setTimeout(() => { console.log(`模擬異步請(qǐng)求 success -- 【 ${item}】`) resolve() }, 200 + Math.random() * 800) }) } #chunk(array, size) { const chunks = [] let index = 0 while (index < array.length) { chunks.push(array.slice(index, size + index)) index += size } return chunks } /** * 批量請(qǐng)求控制 * @private * @async * @returns {Promise} */ async #batchRequest() { const promiseArray = [] let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100] data.forEach((item, index) => { // 原來(lái)的錯(cuò)誤邏輯(原來(lái)的邏輯,導(dǎo)致所有的 Promise 回調(diào)函數(shù)都會(huì)被直接執(zhí)行,那么就只有對(duì) response 進(jìn)行分批的功能了) // const requestPromise = this.#singleRequest(item) // promiseArray.push(requestPromise) // -- 修改為: promiseArray.push(index) }) const promiseChunks = this.#chunk(promiseArray, 10) // 切分成 n 個(gè)請(qǐng)求為一組 let groupIndex = 1 for (let ckg of promiseChunks) { // -- 修改后新增邏輯(在發(fā)起一組請(qǐng)求時(shí),收集該組對(duì)應(yīng)的 Promiise 成員) const ck = ckg.map(idx => this.#singleRequest(data[idx])) // 發(fā)起一組請(qǐng)求 const ckRess = await Promise.all(ck) // 控制并發(fā)數(shù) console.log(`------ 第${groupIndex}組分批發(fā)起完畢 --------`) groupIndex += 1 } } /** * 執(zhí)行批量請(qǐng)求操作 */ exec(options) { this.#batchRequest(options) } } const batchHttp = new BatchHttp() setTimeout(() => { batchHttp.exec() }, 2000) </script> </html>
BatchHttp.js
// 注:這里的 httpRequest 請(qǐng)根據(jù)自己項(xiàng)目而定,比如我的項(xiàng)目是uniapp,里面的http請(qǐng)求是 uni.request,若你的項(xiàng)目是 axios 或者 ajax,那就根據(jù)它們來(lái)對(duì) BatchHttp 中的某些部分 import httpRequest from './httpRequest.js' /** * 批量請(qǐng)求封裝 */ export class BatchHttp { /** * 構(gòu)造函數(shù) * @param {Object} http - http請(qǐng)求對(duì)象(該http請(qǐng)求攔截器里切勿帶有任何有關(guān)ui的功能,比如加載對(duì)話框、彈窗提示框之類),用于發(fā)起請(qǐng)求,該http請(qǐng)求對(duì)象必須滿足:返回一個(gè)包含取消請(qǐng)求函數(shù)的對(duì)象,因?yàn)樵?this.cancelAll() 函數(shù)中會(huì)使用到 * @param {string} [passFlagProp=null] - 用于識(shí)別是否忽略某些數(shù)據(jù)項(xiàng)的字段名(借此可實(shí)現(xiàn)“繼續(xù)上一次完成的批量請(qǐng)求”);如:passFlagProp='url' 時(shí),在執(zhí)行 exec 時(shí),會(huì)過(guò)濾掉 items['url'] 不為空的數(shù)據(jù),借此可以實(shí)現(xiàn)“繼續(xù)上一次完成的批量請(qǐng)求”,避免每次都重復(fù)所有請(qǐng)求 */ constructor(http=httpRequest, passFlagProp=null) { /** @private @type {Object[]} 請(qǐng)求任務(wù)數(shù)組 */ this.resTasks = [] /** @private @type {Object} uni.request對(duì)象 */ this.http = http /** @private @type {boolean} 取消請(qǐng)求標(biāo)志 */ this.canceled = false /** @private @type {string|null} 識(shí)別跳過(guò)數(shù)據(jù)的屬性 */ this.passFlagProp = passFlagProp } /** * 將數(shù)組拆分成多個(gè) size 長(zhǎng)度的小數(shù)組 * 常用于批量處理控制并發(fā)等場(chǎng)景 * @param {Array} array - 需要拆分的數(shù)組 * @param {number} size - 每個(gè)小數(shù)組的長(zhǎng)度 * @returns {Array} - 拆分后的小數(shù)組組成的二維數(shù)組 */ #chunk(array, size) { const chunks = [] let index = 0 while(index < array.length) { chunks.push(array.slice(index, size + index)) index += size; } return chunks } /** * 單個(gè)數(shù)據(jù)項(xiàng)請(qǐng)求 * @private * @param {Object} reqOptions - 請(qǐng)求配置 * @param {Object} item - 數(shù)據(jù)項(xiàng) * @returns {Promise} 請(qǐng)求Promise */ #singleRequest(reqOptions, item) { return new Promise((resolve, _reject) => { const task = this.http({ url: reqOptions.url, method: reqOptions.method || 'GET', data: reqOptions.data, success: res => { resolve({sourceItem:item, res}) } }) this.resTasks.push(task) }) } /** * 批量請(qǐng)求控制 * @private * @async * @param {Object} options - 函數(shù)參數(shù)項(xiàng) * @param {Array} options.items - 數(shù)據(jù)項(xiàng)數(shù)組 * @param {Object} options.reqOptions - 請(qǐng)求配置 * @param {number} [options.concurrentNum=10] - 并發(fā)數(shù) * @param {Function} [options.chunkCallback] - 分塊回調(diào) * @returns {Promise} */ async #batchRequest({items, reqOptions, concurrentNum = 10, chunkCallback=(ress)=>{}}) { const promiseArray = [] let data = [] const passFlagProp = this.passFlagProp if(!passFlagProp) { data = items } else { // 若設(shè)置獨(dú)立 passFlagProp 值,則篩選出對(duì)應(yīng)屬性值為空的數(shù)據(jù)(避免每次都重復(fù)請(qǐng)求所有數(shù)據(jù),實(shí)現(xiàn)“繼續(xù)未完成的批量請(qǐng)求任務(wù)”) data = items.filter(d => !Object.hasOwnProperty.call(d, passFlagProp) || !d[passFlagProp]) } // -- if(data.length === 0) return data.forEach((item,index) => { // 原來(lái)的錯(cuò)誤邏輯(原來(lái)的邏輯,導(dǎo)致所有的 Promise 回調(diào)函數(shù)都會(huì)被直接執(zhí)行,那么就只有對(duì) response 進(jìn)行分批的功能了) // const requestPromise = this.#singleRequest(reqOptions, item) // promiseArray.push(requestPromise) // -- 修改為:這里暫時(shí)只記錄下想對(duì)應(yīng)的 data 的數(shù)組索引,以便分組用,當(dāng)然這部分有關(guān)分組代碼還可以進(jìn)行精簡(jiǎn),比如直接使用 data.map進(jìn)行收集等方式,但是為了與之前錯(cuò)誤邏輯形成對(duì)比,這篇文章里還是這樣寫(xiě)比較完整 promiseArray.push(index) }) const promiseChunks = this.#chunk(promiseArray, concurrentNum) // 切分成 n 個(gè)請(qǐng)求為一組 for (let ckg of promiseChunks) { // -- 修改后新增邏輯(在發(fā)起一組請(qǐng)求時(shí),收集該組對(duì)應(yīng)的 Promiise 成員) const ck = ckg.map(idx => this.#singleRequest(data[idx])) // 若當(dāng)前處于取消請(qǐng)求狀態(tài),則直接跳出 if(this.canceled) break // 發(fā)起一組請(qǐng)求 const ckRess = await Promise.all(ck) // 控制并發(fā)數(shù) chunkCallback(ckRess) // 每完成組請(qǐng)求,都進(jìn)行回調(diào) } } /** * 設(shè)置用于識(shí)別忽略數(shù)據(jù)項(xiàng)的字段名 * (借此參數(shù)可實(shí)現(xiàn)“繼續(xù)上一次完成的批量請(qǐng)求”); * 如:passFlagProp='url' 時(shí),在執(zhí)行 exec 時(shí),會(huì)過(guò)濾掉 items['url'] 不為空的數(shù)據(jù),借此可以實(shí)現(xiàn)“繼續(xù)上一次完成的批量請(qǐng)求”,避免每次都重復(fù)所有請(qǐng)求 * @param {string} val */ setPassFlagProp(val) { this.passFlagProp = val } /** * 執(zhí)行批量請(qǐng)求操作 * @param {Object} options - 函數(shù)參數(shù)項(xiàng) * @param {Array} options.items - 數(shù)據(jù)項(xiàng)數(shù)組 * @param {Object} options.reqOptions - 請(qǐng)求配置 * @param {number} [options.concurrentNum=10] - 并發(fā)數(shù) * @param {Function} [options.chunkCallback] - 分塊回調(diào) */ exec(options) { this.canceled = false this.#batchRequest(options) } /** * 取消所有請(qǐng)求任務(wù) */ cancelAll() { this.canceled = true for(const task of this.resTasks) { task.abort() } this.resTasks = [] } }
調(diào)用案例在此
由于我的項(xiàng)目是uni-app這種,方便起見(jiàn),我就直接貼上在 uni-app 的頁(yè)面 vue 組件中的使用案例
案例代碼僅展示關(guān)鍵部分,所以比較粗糙,看懂參考即可
<template> <view v-for="item of list" :key="item.key"> <image :src="item.url"></image> </view> </template> <script> import { BatchHttp } from '@/utils/BatchHttp.js' export default { data() { return { isLoaded: false, batchHttpInstance: null, list:[] } }, onLoad(options) { this.queryList() }, onShow() { // 第一次進(jìn)頁(yè)面時(shí),onLoad 和 onShow 都會(huì)執(zhí)行,onLoad 中 getList 已調(diào)用 batchQueryUrl,這里僅對(duì)緩存頁(yè)面后再次進(jìn)入該頁(yè)面有效 if(this.isLoaded) { // 為了實(shí)現(xiàn)繼續(xù)請(qǐng)求上一次可能未完成的批量請(qǐng)求,再次進(jìn)入該頁(yè)面時(shí),會(huì)檢查是否存在未完成的任務(wù),若存在則繼續(xù)發(fā)起批量請(qǐng)求 this.batchQueryUrl(this.dataList) } this.isLoaded = true }, onHide() { // 頁(yè)面隱藏時(shí),會(huì)直接取消所有批量請(qǐng)求任務(wù),避免占用資源(下次進(jìn)入該頁(yè)面會(huì)檢查未完成的批量請(qǐng)求任務(wù)并執(zhí)行繼續(xù)功能) this.cancelBatchQueryUrl() }, onUnload() { // 頁(yè)面銷毀時(shí),直接取消批量請(qǐng)求任務(wù) this.cancelBatchQueryUrl() }, onBackPress() { // 路由返回時(shí),直接取消批量請(qǐng)求任務(wù)(雖然路由返回也會(huì)執(zhí)行onHide事件,但是無(wú)所胃都寫(xiě)上,會(huì)判斷當(dāng)前有沒(méi)有任務(wù)的) this.cancelBatchQueryUrl() }, methods: { async queryList() { // 接口不方法直接貼的,這里是模擬的列表接口 const res = await mockHttpRequest() this.list = res.data // 發(fā)起批量請(qǐng)求 // 用 nextTick 也行,只要確保批量任務(wù)在列表dom已掛載完成之后執(zhí)行即可 setTimeout(()=>{this.batchQueryUrl(resData)},0) }, /** * 批量處理圖片url的接口請(qǐng)求 * @param {*} data */ batchQueryUrl(items) { let batchHttpInstance = this.batchHttpInstance // 判定當(dāng)前是否有正在執(zhí)行的批量請(qǐng)求任務(wù),有則直接全部取消即可 if(!!batchHttpInstance) { batchHttpInstance.cancelAll() this.batchHttpInstance = null batchHttpInstance = null } // 實(shí)例化對(duì)象 batchHttpInstance = new BatchHttp() // 設(shè)置過(guò)濾數(shù)據(jù)的屬性名(用于實(shí)現(xiàn)繼續(xù)任務(wù)功能) batchHttpInstance.setPassFlagProp('url') // 實(shí)現(xiàn)回到該緩存頁(yè)面是能夠繼續(xù)批量任務(wù)的關(guān)鍵一步 <----- const reqOptions = { url: '/api/product/url' } batchHttpInstance.exec({items, reqOptions, chunkCallback:(ress)=>{ let newDataList = this.dataList for(const r of ress) { newDataList = newDataList.map(d => d.feId === r['sourceItem'].feId ? {...d,url:r['res'].msg} : d) } this.dataList = newDataList }}) this.batchHttpInstance = batchHttpInstance }, /** * 取消批量請(qǐng)求 */ cancelBatchQueryUrl() { if(!!this.batchHttpInstance) { this.batchHttpInstance.cancelAll() this.batchHttpInstance = null } }, } } </script>
以上就是JavaScript如何使用Promise實(shí)現(xiàn)分批處理接口請(qǐng)求的詳細(xì)內(nèi)容,更多關(guān)于JavaScript Promise分批處理接口請(qǐng)求的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS如何將秒數(shù)轉(zhuǎn)化為時(shí)分秒的形式
在實(shí)際工作中經(jīng)常會(huì)遇見(jiàn)把秒數(shù)轉(zhuǎn)化為時(shí)分秒的問(wèn)題,如何處理呢?下面這篇文章主要給大家介紹了關(guān)于JS如何將秒數(shù)轉(zhuǎn)化為時(shí)分秒形式的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12JavaScript制作簡(jiǎn)單網(wǎng)頁(yè)計(jì)算器
這篇文章主要為大家詳細(xì)介紹了JavaScript制作簡(jiǎn)單網(wǎng)頁(yè)計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08uni-app禁用返回按鈕/返回鍵的具體實(shí)現(xiàn)
今天在使用uni-app開(kāi)發(fā)登錄頁(yè)面時(shí)遇到一個(gè)需求,需要禁用返回按鈕,下面這篇文章主要給大家介紹了關(guān)于uni-app禁用返回按鈕/返回鍵的具體實(shí)現(xiàn),需要的朋友可以參考下2022-11-11JavaScript事件監(jiān)聽(tīng)器詳細(xì)介紹
這篇文章主要介紹了JavaScript事件監(jiān)聽(tīng)器詳細(xì)介紹,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-09-09一個(gè)javascript參數(shù)的小問(wèn)題
2008-03-03js實(shí)現(xiàn)網(wǎng)頁(yè)檢測(cè)是否安裝了 Flash Player 插件
js實(shí)現(xiàn)網(wǎng)頁(yè)檢測(cè)是否安裝了 Flash Player 插件...2007-08-08javascript結(jié)合Flexbox簡(jiǎn)單實(shí)現(xiàn)滑動(dòng)拼圖游戲
本文給大家分享的是一則使用javascript結(jié)合Flexbox簡(jiǎn)單實(shí)現(xiàn)滑動(dòng)拼圖游戲的代碼,雖然沒(méi)有實(shí)現(xiàn)完整的功能,但是還是推薦給大家,喜歡的朋友可以繼續(xù)做完2016-02-02JS+canvas畫(huà)布實(shí)現(xiàn)炫酷的旋轉(zhuǎn)星空效果示例
這篇文章主要介紹了JS+canvas畫(huà)布實(shí)現(xiàn)炫酷的旋轉(zhuǎn)星空效果,結(jié)合實(shí)例形式分析了js結(jié)合HTML5 canvas圖形繪制與數(shù)值計(jì)算相關(guān)操作技巧,需要的朋友可以參考下2019-02-02利用JScript中運(yùn)算符"||"和"&&"的特殊特性實(shí)現(xiàn)代碼精
利用JScript中運(yùn)算符"||"和"&&"的特殊特性實(shí)現(xiàn)代碼精簡(jiǎn)...2007-03-03