1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        你說你會(huì)Promise?那你解決一下項(xiàng)目中的這五個(gè)難題

        共 11606字,需瀏覽 24分鐘

         ·

        2021-12-01 21:02

        作者:Sunshine_Lin

        簡(jiǎn)介:「前端之神」的號(hào)主江湖人稱林三心,現(xiàn)已有100+篇原創(chuàng)文章,全網(wǎng)粉絲高達(dá)1w+,面試過超過100+個(gè)前端程序員,全網(wǎng)獲贊2w+,全網(wǎng)閱讀量播放量超過60w,更是B站「面試進(jìn)階成為大佬」系列視頻的Up主。喜歡分享Vue,React,Typescript等高級(jí)前端知識(shí)。

        來源:SegmentFault  思否社區(qū)


        前言


        大家好,我是林三心,用最通俗易懂的話講最難的知識(shí)點(diǎn)是我的座右銘,基礎(chǔ)是進(jìn)階的前提是我的初心,眾所周知哈, Promise 在咱們的開發(fā)中是相當(dāng)?shù)闹匾?,我覺得對(duì)于 Promise 的使用等級(jí),可以分為三個(gè)等級(jí)。


        • 1、掌握 Promise 的基本使用
        • 2、掌握 Promise 的基本原理
        • 3、在項(xiàng)目中能靈活運(yùn)用 Promise 解決一些問題

        第一點(diǎn)的話,其實(shí)就是能掌握 Promise 的一些基本使用方法以及一些方法,如 then、catch、all、race、finally、allSettled、any、resolve 等等。

        第二點(diǎn)的話,就是要能簡(jiǎn)單實(shí)現(xiàn)一下 Promise 的原理,這能使我們對(duì) Promise 的那些常用方法有更好的理解。

        第三點(diǎn)的話,就是要能靈活 Promise 解決咱們開發(fā)中的一些問題,今天我就給大家說一下我用 Promise 在項(xiàng)目開發(fā)中解決了什么問題吧!

        接口請(qǐng)求超時(shí)


        顧名思義,就是給定一個(gè)時(shí)間,如果接口請(qǐng)求超過這個(gè)時(shí)間的話就報(bào)錯(cuò)。

        1、自己實(shí)現(xiàn)


        實(shí)現(xiàn)思路就是: 接口請(qǐng)求 和 延時(shí)函數(shù) 賽跑,并使用一個(gè) Promise 包著,由于 Promise 的狀態(tài)是不可逆的,所以如果 接口請(qǐng)求 先跑完則說明 未超時(shí) 且 Promise 的狀態(tài)是 fulfilled ,反之, 延時(shí)函數(shù) 先跑完則說明 超時(shí)了 且 Promise 的狀態(tài)是 rejetced ,最后根據(jù) Promise 的狀態(tài)來判斷有無超時(shí)。


        /**
         * 模擬延時(shí)
         * @param {number} delay 延遲時(shí)間
         * @returns {Promise<any>}
         */
        function sleep(delay) {
          return new Promise((_, reject) => {
            setTimeout(() => reject('超時(shí)嘍'), delay)
          })
        }

        /**
         * 模擬請(qǐng)求
         */
        function request() {
          // 假設(shè)請(qǐng)求需要 1s
          return new Promise(resolve => {
            setTimeout(() => resolve('成功嘍'), 1000)
          })
        }

        /**
         * 判斷是否超時(shí)
         * @param {() => Promise<any>} requestFn 請(qǐng)求函數(shù)
         * @param {number} delay 延遲時(shí)長(zhǎng)
         * @returns {Promise<any>}
         */
        function timeoutPromise(requestFn, delay) {
          return new Promise((resolve, reject) => {
            const promises = [requestFn(), sleep(delay)]
            for (const promise of promises) {
              // 超時(shí)則執(zhí)行失敗,不超時(shí)則執(zhí)行成功
              promise.then(res => resolve(res), err => reject(err))
            }
          })
        }

        2、Promise.race


        其實(shí) timeoutPromise 中的代碼可以使用 Promise.race 來代替,是同樣的效果。

        function timeoutPromise(requestFn, delay) {
           // 如果先返回的是延遲Promise則說明超時(shí)了
           return Promise.race([requestFn(), sleep(delay)])
        }

        3、測(cè)試


        // 超時(shí)
        timeoutPromise(request, 500).catch(err => console.log(err)) // 超時(shí)嘍

        // 不超時(shí)
        timeoutPromise(request, 2000).then(res => console.log(res)) // 成功嘍


        轉(zhuǎn)盤抽獎(jiǎng)


        我們平時(shí)在轉(zhuǎn)盤抽獎(jiǎng)時(shí),一般都是開始轉(zhuǎn)動(dòng)的同時(shí)也發(fā)起接口請(qǐng)求,所以有兩種可能。

        • 1、轉(zhuǎn)盤轉(zhuǎn)完,接口還沒請(qǐng)求回來,這是不正常的
        • 2、轉(zhuǎn)盤轉(zhuǎn)完前,接口就請(qǐng)求完畢,這是正常的,但是需要保證 請(qǐng)求回調(diào) 跟 轉(zhuǎn)盤轉(zhuǎn)完回調(diào) 同時(shí)執(zhí)行


        1、轉(zhuǎn)盤轉(zhuǎn)完,接口還沒請(qǐng)求回來


        主要問題就是,怎么判斷 接口請(qǐng)求時(shí)間 是否超過 轉(zhuǎn)盤轉(zhuǎn)完所需時(shí)間 ,咱們其實(shí)可以用到上一個(gè)知識(shí)點(diǎn) 接口請(qǐng)求超時(shí) ,都是一樣的道理。如果 轉(zhuǎn)盤轉(zhuǎn)完所需時(shí)間 是 2500ms ,那咱們可以限定 接口請(qǐng)求 需要提前 1000ms 請(qǐng)求回來,也就是 接口請(qǐng)求 的超時(shí)時(shí)間為 2500ms - 1000ms = 1500ms。

        /**
         * 模擬延時(shí)
         * @param {number} delay 延遲時(shí)間
         * @returns {Promise<any>}
         */
        function sleep(delay) {
          return new Promise((_, reject) => {
            setTimeout(() => reject('超時(shí)嘍'), delay)
          })
        }

        /**
         * 模擬請(qǐng)求
         */
        function request() {
          return new Promise(resolve => {
            setTimeout(() => resolve('成功嘍'), 1000)
          })
        }

        /**
         * 判斷是否超時(shí)
         * @param {() => Promise<any>} requestFn 請(qǐng)求函數(shù)
         * @param {number} delay 延遲時(shí)長(zhǎng)
         * @returns {Promise<any>}
         */
        function timeoutPromise(requestFn, delay) {
           return Promise.race([requestFn(), sleep(delay)])
        }

        2、轉(zhuǎn)盤轉(zhuǎn)完前,接口就請(qǐng)求完畢


        咱們確保了 接口請(qǐng)求 可以在 轉(zhuǎn)盤轉(zhuǎn)完 之前請(qǐng)求回來,但是還有一個(gè)問題,就是需要保證 請(qǐng)求回調(diào) 跟 轉(zhuǎn)盤轉(zhuǎn)完回調(diào) 同時(shí)執(zhí)行,因?yàn)殡m然 接口請(qǐng)求 請(qǐng)求回來的時(shí)候,轉(zhuǎn)盤還在轉(zhuǎn)著,咱們需要等轉(zhuǎn)盤轉(zhuǎn)完時(shí),再一起執(zhí)行這兩個(gè)回調(diào)。

        聽到這個(gè)描述,相信很多同學(xué)就會(huì)想到 Promise.all 這個(gè)方法。

        // ...上面代碼

        /**
         * 模擬轉(zhuǎn)盤旋轉(zhuǎn)到停止的延時(shí)
         * @param {number} delay 延遲時(shí)間
         * @returns {Promise<any>}
         */
         function turntableSleep(delay) {
          return new Promise(resolve => {
            setTimeout(() => resolve('停止轉(zhuǎn)動(dòng)嘍'), delay)
          })
        }

        /**
         * 判斷是否超時(shí)
         * @param {() => Promise<any>} requestFn 請(qǐng)求函數(shù)
         * @param {number} turntableDelay 轉(zhuǎn)盤轉(zhuǎn)多久
         * @param {number} delay 請(qǐng)求超時(shí)時(shí)長(zhǎng)
         * @returns {Promise<any>}
         */

        function zhuanpanPromise(requsetFn, turntableDelay, delay) {
          return Promise.all([timeoutPromise(requsetFn, delay), turntableSleep(turntableDelay)])
        }

        3、測(cè)試


        // 不超時(shí),且先于轉(zhuǎn)盤停止前請(qǐng)求回?cái)?shù)據(jù)
        zhuanpanPromise(request, 2500, 1500).then(res => console.log(res), err => console.log(err))


        控制并發(fā)的Promise的調(diào)度器


        想象一下,有一天你突然一次性發(fā)了10個(gè)請(qǐng)求,但是這樣的話并發(fā)量是很大的,能不能控制一下,就是一次只發(fā)2個(gè)請(qǐng)求,某一個(gè)請(qǐng)求完了,就讓第3個(gè)補(bǔ)上,又請(qǐng)求完了,讓第4個(gè)補(bǔ)上,以此類推,讓最高并發(fā)量變成可控的。

        addTask(1000,"1");
        addTask(500,"2");
        addTask(300,"3");
        addTask(400,"4");
        的輸出順序是:2 3 1 4

        整個(gè)的完整執(zhí)行流程:

        一開始1、2兩個(gè)任務(wù)開始執(zhí)行
        500ms時(shí),2任務(wù)執(zhí)行完畢,輸出2,任務(wù)3開始執(zhí)行
        800ms時(shí),3任務(wù)執(zhí)行完畢,輸出3,任務(wù)4開始執(zhí)行
        1000ms時(shí),1任務(wù)執(zhí)行完畢,輸出1,此時(shí)只剩下4任務(wù)在執(zhí)行
        1200ms時(shí),4任務(wù)執(zhí)行完畢,輸出4

        實(shí)現(xiàn)


        class Scheduler {
          constructor(limit) {
            this.queue = []
            this.limit = limit
            this.count = 0
          }
          

          add(time, order) {
            const promiseCreator = () => {
              return new Promise((resolve, reject) => {
                setTimeout(() => {
                  console.log(order)
                  resolve()
                }, time)
              })
            }
            this.queue.push(promiseCreator)
          }

          taskStart() {
            for(let i = 0; i < this.limit; i++) {
              this.request()
            }
          }

          request() {
            if (!this.queue.length || this.count >= this.limit) return
            this.count++
            this.queue.shift()().then(() => {
              this.count--
              this.request()
            })
          }
        }

        測(cè)試


        // 測(cè)試
        const scheduler = new Scheduler(2);
        const addTask = (time, order) => {
          scheduler.add(time, order);
        };
        addTask(1000, "1");
        addTask(500, "2");
        addTask(300, "3");
        addTask(400, "4");
        scheduler.taskStart();

        取消重復(fù)請(qǐng)求


        舉個(gè)例子,咱們?cè)谧霰韱翁峤粫r(shí),為了防止多次重復(fù)的提交,肯定會(huì)給按鈕的點(diǎn)擊事件加上 防抖措施 ,這確實(shí)是有效地避免了多次點(diǎn)擊造成的重復(fù)請(qǐng)求,但是其實(shí)還是有弊端的。

        眾所周知,為了用戶更好地體驗(yàn), 防抖 的延時(shí)是不能太長(zhǎng)的,一般在我的項(xiàng)目中都是 300ms ,但是這只能管到 請(qǐng)求時(shí)間 < 300ms 的接口請(qǐng)求,如果有一個(gè)接口請(qǐng)求需要 2000ms ,那么此時(shí) 防抖 也做不到完全限制 重復(fù)請(qǐng)求 ,所以咱們需要額外做一下 取消重復(fù)請(qǐng)求 的處理。

        實(shí)現(xiàn)


        實(shí)現(xiàn)思路:簡(jiǎn)單說就是,利用 Promise.race 方法,給每一次請(qǐng)求的身邊安裝一顆雷,如果第一次請(qǐng)求后,又接了第二次重復(fù)請(qǐng)求,那么就執(zhí)行第一次請(qǐng)求身邊的雷,把第一次請(qǐng)求給炸掉,以此類推。

        class CancelablePromise {
          constructor() {
            this.pendingPromise = null
            this.reject = null
          }

          request(requestFn) {
            if (this.pendingPromise) {
              this.cancel('取消重復(fù)請(qǐng)求')
            }

            const promise = new Promise((_, reject) => (this.reject = reject))
            this.pendingPromise = Promise.race([requestFn(), promise])
            return this.pendingPromise
          }

          cancel(reason) {
            this.reject(reason)
            this.pendingPromise = null
          }
        }

        function request(delay) {
          return () => 
            new Promise(resolve => {
              setTimeout(() => {
                resolve('最后贏家是我')
              }, delay)
            })
        }

        測(cè)試


        const cancelPromise = new CancelablePromise()

        // 模擬頻繁請(qǐng)求5次
        for (let i = 0; i < 5; i++) {
          cancelPromise
            .request(request(2000))
            .then((res) => console.log(res)) // 最后一個(gè) 最后贏家是我
            .catch((err) => console.error(err)); // 前四個(gè) 取消重復(fù)請(qǐng)求
        }

        全局請(qǐng)求loading


        比如一個(gè)頁(yè)面中,或者多個(gè)組件中都需要請(qǐng)求并且展示 loading狀態(tài) ,此時(shí)我們不想要每個(gè)頁(yè)面或者組件都寫一遍 loading ,那我們可以統(tǒng)一管理 loading , loading 有兩種情況。

        • 1、全局只要有一個(gè)接口還在請(qǐng)求中,就展示 loading
        • 2、全局所有接口都不在請(qǐng)求中,就隱藏 loading

        那我們?cè)趺床拍苤廊纸涌诘恼?qǐng)求狀態(tài)呢?其實(shí)咱們可以利用 Promise ,只要某個(gè) 接口請(qǐng)求Promise 的狀態(tài)不是 pending 那就說明他請(qǐng)求完成了,無論請(qǐng)求成功或者失敗,既然是無論成功失敗,那咱們就會(huì)想到 Promise.prototype.finally 這個(gè)方法。


        實(shí)現(xiàn)


        class PromiseManager {
          constructor() {
            this.pendingPromise = new Set()
            this.loading = false
          }

          generateKey() {
            return `${new Date().getTime()}-${parseInt(Math.random() * 1000)}`
          }

          push(...requestFns) {
            for (const requestFn of requestFns) {
              const key = this.generateKey()
              this.pendingPromise.add(key)
              requestFn().finally(() => {
                this.pendingPromise.delete(key)
                this.loading = this.pendingPromise.size !== 0
              })
            }
          }
        }

        測(cè)試


        // 模擬請(qǐng)求
        function request(delay) {
          return () => {
            return new Promise(resolve => {
              setTimeout(() => resolve('成功嘍'), delay)
            })
          }
        }

        const manager = new PromiseManager()

        manager.push(request(1000), request(2000), request(800), request(2000), request(1500))

        const timer = setInterval(() => {
           // 輪詢查看loading狀態(tài)
           console.log(manager.loading)
        }, 300)

        參考


        https://juejin.cn/post/6993296099331014669




        點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動(dòng)和交流,掃描下方”二維碼“或在“公眾號(hào)后臺(tái)回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

        - END -


        瀏覽 16
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            精品操逼 | 午夜偷拍视频 | 精品无码久久久久久国产牛牛影视 | 一级AAAA毛片 | 大雞巴弄得我好舒服黃片 | 精品人伦一区二区三区蜜桃网站 | 嗯啊做爱 | 欧美操逼视频免费播放网站 | 美女高潮视频在线观看 | 日日摸夜夜添夜夜躁好吊 |