日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

JavaScript 異步時序問題

瀏覽:85日期:2023-10-07 16:15:39

場景

死后我們必升天堂,因為活時我們已在地獄。

不知你是否遇到過,向后臺發送了多次異步請求,結果最后顯示的數據卻并不正確 ? 是舊的數據。

具體情況:

用戶觸發事件,發送了第 1 次請求 用戶觸發事件,發送了第 2 次請求 第 2 次請求成功,更新頁面上的數據 第 1 次請求成功,更新頁面上的數據

嗯?是不是感覺到異常了?這便是多次異步請求時會遇到的異步回調順序與調用順序不同的問題。

思考

為什么會出現這種問題? 出現這種問題怎么解決?

為什么會出現這種問題?

JavaScript 隨處可見異步,但實際上并不是那么好控制。用戶與 UI 交互,觸發事件及其對應的處理函數,函數執行異步操作(網絡請求),異步操作得到結果的時間(順序)是不確定的,所以響應到 UI 上的時間就不確定,如果觸發事件的頻率較高/異步操作的時間過長,就會造成前面的異步操作結果覆蓋后面的異步操作結果。

關鍵點

異步操作得到結果的時間(順序)是不確定的 如果觸發事件的頻率較高/異步操作的時間過長

出現這種問題怎么解決?

既然關鍵點由兩個要素組成,那么,只要破壞了任意一個即可。

手動控制異步返回結果的順序 降低觸發頻率并限制異步超時時間

手動控制返回結果的順序

根據對異步操作結果處理情況的不同也有三種不同的思路

后面異步操作得到結果后等待前面的異步操作返回結果 后面異步操作得到結果后放棄前面的異步操作返回結果 依次處理每一個異步操作,等待上一個異步操作完成之后再執行下一個

這里先引入一個公共的 wait 函數

/** * 等待指定的時間/等待指定表達式成立 * 如果未指定等待條件則立刻執行 * 注: 此實現在 nodejs 10- 會存在宏任務與微任務的問題,切記 async-await 本質上還是 Promise 的語法糖,實際上并非真正的同步函數!!!即便在瀏覽器,也不要依賴于這種特性。 * @param param 等待時間/等待條件 * @returns Promise 對象 */function wait(param) { return new Promise(resolve => { if (typeof param === ’number’) { setTimeout(resolve, param) } else if (typeof param === ’function’) { const timer = setInterval(() => { if (param()) { clearInterval(timer) resolve() } }, 100) } else { resolve() } })}

1. 后面異步操作得到結果后等待前面的異步操作返回結果

為每一次的異步調用都聲稱一個唯一 id 使用列表記錄所有的異步 id 在真正調用異步操作后,添加一個唯一 id 判斷上一個正在執行的異步操作是否完成 如果未完成等待上一個異步操作完成,否則直接跳過 從列表中刪除掉當前的 id 最后等待異步操作然后返回結果

/** * 將一個異步函數包裝為具有時序的異步函數 * 注: 該函數會按照調用順序依次返回結果,后面的調用的結果需要等待前面的,所以如果不關心過時的結果,請使用 {@link switchMap} 函數 * @param fn 一個普通的異步函數 * @returns 包裝后的函數 */function mergeMap(fn) { // 當前執行的異步操作 id let id = 0 // 所執行的異步操作 id 列表 const ids = new Set() return new Proxy(fn, { async apply(_, _this, args) { const prom = Reflect.apply(_, _this, args) const temp = id ids.add(temp) id++ await wait(() => !ids.has(temp - 1)) ids.delete(temp) return await prom }, })}

測試一下

;(async () => { // 模擬一個異步請求,接受參數并返回它,然后等待指定的時間 async function get(ms) { await wait(ms) return ms } const fn = mergeMap(get) let last = 0 let sum = 0 await Promise.all([ fn(30).then(res => { last = res sum += res }), fn(20).then(res => { last = res sum += res }), fn(10).then(res => { last = res sum += res }), ]) console.log(last) // 實際上確實執行了 3 次,結果也確實為 3 次調用參數之和 console.log(sum)})()

2. 后面異步操作得到結果后放棄前面的異步操作返回結果

為每一次的異步調用都聲稱一個唯一 id 記錄最新得到異步操作結果的 id 記錄最新得到的異步操作結果 執行并等待返回結果 判斷本次異步調用后面是否已經有調用出現結果了

是的話就直接返回后面的異步調用結果 否則將本地異步調用 id 及其結果最為[最后的] 返回這次的異步調用結果

/** * 將一個異步函數包裝為具有時序的異步函數 * 注: 該函數會丟棄過期的異步操作結果,這樣的話性能會稍稍提高(主要是響應比較快的結果會立刻生效而不必等待前面的響應結果) * @param fn 一個普通的異步函數 * @returns 包裝后的函數 */function switchMap(fn) { // 當前執行的異步操作 id let id = 0 // 最后一次異步操作的 id,小于這個的操作結果會被丟棄 let last = 0 // 緩存最后一次異步操作的結果 let cache return new Proxy(fn, { async apply(_, _this, args) { const temp = id id++ const res = await Reflect.apply(_, _this, args) if (temp < last) { return cache } cache = res last = temp return res }, })}

測試一下

;(async () => { // 模擬一個異步請求,接受參數并返回它,然后等待指定的時間 async function get(ms) { await wait(ms) return ms } const fn = switchMap(get) let last = 0 let sum = 0 await Promise.all([ fn(30).then(res => { last = res sum += res }), fn(20).then(res => { last = res sum += res }), fn(10).then(res => { last = res sum += res }), ]) console.log(last) // 實際上確實執行了 3 次,然而結果并不是 3 次調用參數之和,因為前兩次的結果均被拋棄,實際上返回了最后一次發送請求的結果 console.log(sum)})()

3. 依次處理每一個異步操作,等待上一個異步操作完成之后再執行下一個

為每一次的異步調用都聲稱一個唯一 id 使用列表記錄所有的異步 id 向列表中添加一個唯一 id 判斷上一個正在執行的異步操作是否完成 如果未完成等待上一個異步操作完成,否則直接跳過 真正調用異步操作 從列表中刪除掉當前的 id 最后等待異步操作然后返回結果

/** * 將一個異步函數包裝為具有時序的異步函數 * 注: 該函數會按照調用順序依次返回結果,后面的執行的調用(不是調用結果)需要等待前面的,此函數適用于異步函數的內里執行也必須保證順序時使用,否則請使用 {@link mergeMap} 函數 * 注: 該函數其實相當于調用 {@code asyncLimiting(fn, {limit: 1})} 函數 * 例如即時保存文檔到服務器,當然要等待上一次的請求結束才能請求下一次,不然數據庫保存的數據就存在謬誤了 * @param fn 一個普通的異步函數 * @returns 包裝后的函數 */function concatMap(fn) { // 當前執行的異步操作 id let id = 0 // 所執行的異步操作 id 列表 const ids = new Set() return new Proxy(fn, { async apply(_, _this, args) { const temp = id ids.add(temp) id++ await wait(() => !ids.has(temp - 1)) const prom = Reflect.apply(_, _this, args) ids.delete(temp) return await prom }, })}

測試一下

;(async () => { // 模擬一個異步請求,接受參數并返回它,然后等待指定的時間 async function get(ms) { await wait(ms) return ms } const fn = concatMap(get) let last = 0 let sum = 0 await Promise.all([ fn(30).then(res => { last = res sum += res }), fn(20).then(res => { last = res sum += res }), fn(10).then(res => { last = res sum += res }), ]) console.log(last) // 實際上確實執行了 3 次,然而結果并不是 3 次調用參數之和,因為前兩次的結果均被拋棄,實際上返回了最后一次發送請求的結果 console.log(sum)})()

小結

雖然三個函數看似效果都差不多,但還是有所不同的。

是否允許異步操作并發?否: concatMap, 是: 到下一步 是否需要處理舊的的結果?否: switchMap, 是: mergeMap

降低觸發頻率并限制異步超時時間

思考一下第二種解決方式,本質上其實是 限流 + 自動超時,首先實現這兩個函數。

限流: 限制函數調用的頻率,如果調用的頻率過快則不會真正執行調用而是返回舊值 自動超時: 如果到了超時時間,即便函數還未得到結果,也會自動超時并拋出錯誤

下面來分別實現它們

限流實現

具體實現思路可見: JavaScript 防抖和節流

/** * 函數節流 * 節流 (throttle) 讓一個函數不要執行的太頻繁,減少執行過快的調用,叫節流 * 類似于上面而又不同于上面的函數去抖, 包裝后函數在上一次操作執行過去了最小間隔時間后會直接執行, 否則會忽略該次操作 * 與上面函數去抖的明顯區別在連續操作時會按照最小間隔時間循環執行操作, 而非僅執行最后一次操作 * 注: 該函數第一次調用一定會執行,不需要擔心第一次拿不到緩存值,后面的連續調用都會拿到上一次的緩存值 * 注: 返回函數結果的高階函數需要使用 {@link Proxy} 實現,以避免原函數原型鏈上的信息丟失 * * @param {Number} delay 最小間隔時間,單位為 ms * @param {Function} action 真正需要執行的操作 * @return {Function} 包裝后有節流功能的函數。該函數是異步的,與需要包裝的函數 {@link action} 是否異步沒有太大關聯 */const throttle = (delay, action) => { let last = 0 let result return new Proxy(action, { apply(target, thisArg, args) { return new Promise(resolve => { const curr = Date.now() if (curr - last > delay) { result = Reflect.apply(target, thisArg, args) last = curr resolve(result) return } resolve(result) }) }, })}

自動超時

注: asyncTimeout 函數實際上只是為了避免一種情況,異步請求時間超過節流函數最小間隔時間導致結果返回順序錯亂。

/** * 為異步函數添加自動超時功能 * @param timeout 超時時間 * @param action 異步函數 * @returns 包裝后的異步函數 */function asyncTimeout(timeout, action) { return new Proxy(action, { apply(_, _this, args) { return Promise.race([ Reflect.apply(_, _this, args), wait(timeout).then(Promise.reject), ]) }, })}

結合使用

測試一下

;(async () => { // 模擬一個異步請求,接受參數并返回它,然后等待指定的時間 async function get(ms) { await wait(ms) return ms } const time = 100 const fn = asyncTimeout(time, throttle(time, get)) let last = 0 let sum = 0 await Promise.all([ fn(30).then(res => { last = res sum += res }), fn(20).then(res => { last = res sum += res }), fn(10).then(res => { last = res sum += res }), ]) // last 結果為 10,和 switchMap 的不同點在于會保留最小間隔期間的第一次,而拋棄掉后面的異步結果,和 switchMap 正好相反! console.log(last) // 實際上確實執行了 3 次,結果也確實為第一次次調用參數的 3 倍 console.log(sum)})()

起初吾輩因為好奇實現了這種方式,但原以為會和 concatMap 類似的函數卻變成了現在這樣 ? 更像倒置的 switchMap 了。不過由此看來這種方式的可行性并不大,畢竟,沒人需要舊的數據。

總結

其實第一種實現方式屬于 rxjs 早就已經走過的道路,目前被 Angular 大量采用(類比于 React 中的 Redux)。但 rxjs 實在太強大也太復雜了,對于吾輩而言,僅僅需要一只香蕉,而不需要拿著香蕉的大猩猩,以及其所處的整個森林(此處原本是被人吐槽面向對象編程的隱含環境,這里吾輩稍微藉此吐槽一下動不動就上庫的開發者)。

可以看到吾輩在這里大量使用了 Proxy,那么,原因是什么呢?這個疑問就留到下次再說吧!

以上就是JavaScript 異步時序問題的詳細內容,更多關于JavaScript 異步時序的資料請關注好吧啦網其它相關文章!

標簽: JavaScript
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
亚洲久久视频| 日本免费一区二区三区四区| 亚洲福利一区| 亚洲不卡系列| 久久精品国内一区二区三区水蜜桃| 中文字幕成在线观看| 精精国产xxxx视频在线野外| 日韩不卡视频在线观看| 久久久精品日韩| 日韩av二区| av最新在线| 啪啪国产精品| 美女网站一区| 免费在线观看日韩欧美| 亚洲免费专区| 国产精品一区二区av交换| 麻豆成人av在线| 久久久久久色| 国产精品地址| 国产精品成人a在线观看| www在线观看黄色| 999久久久精品国产| 午夜精品网站| 亚州国产精品| 国产精品亚洲产品| zzzwww在线看片免费| 99视频精品全部免费在线视频| 91精品国产乱码久久久久久久| 国产亚洲精品久久久久婷婷瑜伽| 亚洲天堂日韩在线| 欧美视频二区| 久草免费在线视频| 欧美日韩激情在线一区二区三区| 爽好多水快深点欧美视频| 国产日韩一区二区三区在线播放| 精品久久99| 婷婷亚洲综合| 免费人成黄页网站在线一区二区| 日韩精品91亚洲二区在线观看| 国产精品久久久久久久久久齐齐| 欧美日韩国产观看视频| 99在线|亚洲一区二区| 欧美亚洲色图校园春色| 国产一区二区三区久久| 欧美在线网站| 国产精品一区二区三区美女| 日韩中文首页| 亚洲精品美女91| 国产suv精品一区| 中文日韩欧美| 国产极品一区| 亚洲欧美不卡| 九九九精品视频| 五月天久久久| 91成人小视频| 日韩毛片视频| 香蕉久久一区| 人人精品亚洲| 91亚洲无吗| 亚洲欧洲一区二区天堂久久| 国产日本精品| 亚洲欧美日本视频在线观看| 国产精品久久久久久久久久10秀 | 婷婷五月色综合香五月| 日本强好片久久久久久aaa| 国产欧美激情| 欧美香蕉视频| 亚洲精品无播放器在线播放| 在线天堂中文资源最新版| 视频一区欧美精品| 精品国产三区在线| 久久国产99| av免费不卡国产观看| 亚洲人成亚洲精品| 亚洲91久久| 麻豆中文一区二区| 水野朝阳av一区二区三区| www.九色在线| 国产日韩视频在线| 先锋亚洲精品| 亚洲爱爱视频| 国产欧美日韩一区二区三区四区| 不卡一区2区| 国产一区精品福利| 日本欧美大码aⅴ在线播放| 欧美精品一区二区久久| 精品国内亚洲2022精品成人| 亚洲另类黄色| 免费视频一区三区| 国产aⅴ精品一区二区三区久久| 日韩美女国产精品| 在线综合视频| 色婷婷久久久| 国产成人精品一区二区三区免费| 日韩黄色免费网站| 日韩亚洲国产欧美| 日韩av网站在线免费观看| 国产不卡av一区二区| 亚洲网站视频| 美女免费视频一区| 麻豆成人在线| 久久黄色影院| 久久久久伊人| 欧美天堂一区| 精品99久久| 日韩欧美午夜| 麻豆久久一区二区| 五月精品视频| 久久精品一本| 青草综合视频| 久久精品免费一区二区三区| 99热国内精品| 香蕉精品视频在线观看| 四虎影视精品| 日韩欧美字幕| 91亚洲人成网污www| 国产一区二区三区天码| 国产经典一区| 麻豆视频观看网址久久| 国产精品毛片视频| 国产精品一区二区美女视频免费看 | 美女久久久久| 激情久久婷婷| 亚洲高清激情| 日韩国产欧美| 四虎8848精品成人免费网站| 久久av综合| 国产一区二区三区不卡av | 欧美一区91| 国产九九精品| 国产精品一区二区99| 日本91福利区| 国产亚洲一区| 日本精品在线播放| 国产精品毛片久久久| 国产探花在线精品一区二区| 日本不卡视频在线| 国产精品15p| 国产精品一区高清| 欧美一级鲁丝片| 神马午夜在线视频| 精品香蕉视频| 日韩av在线播放网址| 不卡专区在线| 91精品国产福利在线观看麻豆| 精品一区在线| 另类亚洲自拍| 日韩高清一区二区| 国产精品一区二区精品视频观看| 美女视频黄久久| 成人羞羞视频在线看网址| 久久久久蜜桃| 久久亚洲影院| 色综合视频一区二区三区日韩| 日本免费一区二区视频| 欧美黄色一区| 日韩久久电影| 性色一区二区| 欧美永久精品| 成人精品国产亚洲| 欧美高清不卡| 日韩精品视频一区二区三区| 久久精品亚洲一区二区| 1000部精品久久久久久久久| 免费在线成人网| 欧美激情福利| 久久在线免费| 丝袜美腿亚洲色图| 国产伦精品一区二区三区千人斩| 激情综合婷婷| 合欧美一区二区三区| 国产欧美三级| 伊人久久大香线蕉av不卡| 亚洲精品少妇| 国产精品xx| 综合激情网...| 国产精品中文字幕制服诱惑| 亚洲一级大片| 久久中文亚洲字幕| 国产成人免费精品| 久久国产日韩欧美精品| 久久精品二区亚洲w码| 精品91久久久久| 国产一区二区亚洲| 日韩av中文字幕一区| 精品福利久久久| 91精品一区二区三区综合| 国产成人精品999在线观看| 午夜影院一区| 最新亚洲一区| 亚洲专区一区| 婷婷成人综合| 日韩一区二区在线免费| 亚洲伊人影院| 国产精品成人一区二区不卡| 丝袜脚交一区二区| 日韩精品专区| 国产精品中文字幕亚洲欧美| 中文字幕系列一区| 国产精品美女午夜爽爽|