<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ?Promise面試實(shí)戰(zhàn)指北

          共 17237字,需瀏覽 35分鐘

           ·

          2021-08-18 20:08

          今天給大家分享一篇不錯(cuò)的Promise面試知識(shí)點(diǎn)梳理。說(shuō)不定對(duì)你有一些幫助。

          編者注解

          這是作者鼠子的寄語(yǔ):

          本文旨在使用一個(gè)易于理解、易于記憶的方式去吃透promise相關(guān)應(yīng)用側(cè)的技術(shù)點(diǎn),從而應(yīng)用于簡(jiǎn)歷和面試中。

          比起其他大佬的文章,本文更注重于實(shí)戰(zhàn)性,同時(shí)也會(huì)盡可能的去提高代碼規(guī)范和質(zhì)量(個(gè)人水平受限無(wú)法給出最優(yōu)解)。俗話說(shuō)的好,貪多嚼不爛,想要深入了解更多實(shí)現(xiàn)方法和細(xì)節(jié)的同學(xué)可以補(bǔ)充看更多更加優(yōu)秀的文章。

          超時(shí)控制

          背景

          1. 眾所周知,fetch請(qǐng)求是無(wú)法設(shè)置超時(shí)時(shí)間的,因此我們需要自己去模擬一個(gè)超時(shí)控制。
          2. 轉(zhuǎn)盤(pán)問(wèn)題,一個(gè)抽獎(jiǎng)轉(zhuǎn)盤(pán)動(dòng)畫(huà)效果有5秒,但是一般來(lái)說(shuō)向后端請(qǐng)求轉(zhuǎn)盤(pán)結(jié)果只需要不到一秒,因此請(qǐng)求結(jié)果至少得等5秒才能展現(xiàn)給用戶。

          問(wèn)題分析

          首先,超時(shí)控制比較簡(jiǎn)單,和Promise.race()的思想是類似,或者可以直接使用這個(gè)函數(shù)去解決。

          然后,轉(zhuǎn)盤(pán)問(wèn)題如果要答好,需要考慮兩種情況。

          1. 轉(zhuǎn)盤(pán)動(dòng)畫(huà)還未完成,請(qǐng)求結(jié)果已經(jīng)拿到了,此時(shí)要等到動(dòng)畫(huà)完成再展示結(jié)果給用戶。
          2. 轉(zhuǎn)盤(pán)動(dòng)畫(huà)完成了,請(qǐng)求結(jié)果還未拿到,此時(shí)需要等待結(jié)果返回(可以設(shè)置請(qǐng)求超時(shí)時(shí)間)。

          所以,轉(zhuǎn)盤(pán)問(wèn)題更適合用Promise.all()來(lái)解決。

          實(shí)戰(zhàn)版源碼

          代碼分為多個(gè)版本,從上自下,記憶難度遞增但面試成績(jī)更優(yōu),請(qǐng)按需選擇。

          一、基于Promise.race()的超時(shí)控制。

          /**
           * 超時(shí)控制版本一
           */


          /**
           * 輔助函數(shù),封裝一個(gè)延時(shí)promise
           * @param {number} delay 延遲時(shí)間
           * @returns {Promise<any>}
           */

          function sleep(delay{
            return new Promise((resolve, reject) => {
              setTimeout(() => reject(new Error("timeout")), delay);
            });
          }

          /**
           * 將原promise包裝成一個(gè)帶超時(shí)控制的promise
           * @param {()=>Promise<any>} requestFn 請(qǐng)求函數(shù)
           * @param {number} timeout 最大超時(shí)時(shí)間
           * @returns {Promise<any>}
           */

          function timeoutPromise(requestFn, timeout{
            return Promise.race([requestFn(), sleep(timeout)]);
          }

          // ----------下面是測(cè)試用例------------

          // 模擬一個(gè)異步請(qǐng)求函數(shù)
          function createRequest(delay{
            return () =>
              new Promise((resolve) => {
                setTimeout(() => {
                  resolve("done");
                }, delay);
              });
          }

          // 超時(shí)的例子
          timeoutPromise(createRequest(2000), 1000).catch((error) =>
            console.error(error)
          );
          // 不超時(shí)的例子
          timeoutPromise(createRequest(2000), 3000).then((res) => console.log(res));

          復(fù)制代碼

          二、將promise.race()干掉。

          /**
           * 超時(shí)控制版本二
           */


          /**
           * 輔助函數(shù),封裝一個(gè)延時(shí)promise
           * @param {number} delay 延遲時(shí)間
           * @returns {Promise<any>}
           */

          function sleep(delay{
            return new Promise((resolve, reject) => {
              setTimeout(() => reject(new Error("timeout")), delay);
            });
          }

          /**
           * 將原promise包裝成一個(gè)帶超時(shí)控制的promise
           * @param {()=>Promise<any>} requestFn 請(qǐng)求函數(shù)
           * @param {number} timeout 最大超時(shí)時(shí)間
           * @returns {Promise<any>}
           */

          function timeoutPromise(requestFn, timeout{
            const promises = [requestFn(), sleep(timeout)];
            return new Promise((resolve, reject) => {
              for (const p of promises) {
                p.then((res) => resolve(res)).catch((error) => reject(error));
              }
            });
          }

          // ----------下面是測(cè)試用例------------

          // 模擬一個(gè)異步請(qǐng)求函數(shù)
          function createRequest(delay{
            return () =>
              new Promise((resolve) => {
                setTimeout(() => {
                  resolve("done");
                }, delay);
              });
          }

          // 超時(shí)的例子
          timeoutPromise(createRequest(2000), 1000).catch((error) =>
            console.error(error)
          );
          // 不超時(shí)的例子
          timeoutPromise(createRequest(2000), 3000).then((res) => console.log(res));

          復(fù)制代碼

          三、基于Promise.all()的轉(zhuǎn)盤(pán)問(wèn)題(不考慮請(qǐng)求超時(shí)),和上面略有不同的是sleep函數(shù)超時(shí)后Promisepending態(tài)轉(zhuǎn)到fulfilled態(tài)而不是rejected態(tài)。

          /**
           * 轉(zhuǎn)盤(pán)問(wèn)題不考慮超時(shí)
           */


          /**
           * 輔助函數(shù),封裝一個(gè)延時(shí)promise
           * @param {number} delay 延遲時(shí)間
           * @returns {Promise<any>}
           */

          function sleep(delay{
            return new Promise((resolve) => {
              setTimeout(() => resolve(delay), delay);
            });
          }

          /**
           * 將原promise包裝成一個(gè)轉(zhuǎn)盤(pán)promise
           * @param {()=>Promise<any>} requestFn 請(qǐng)求函數(shù)
           * @param {number} animationDuration 動(dòng)畫(huà)持續(xù)時(shí)間
           * @returns {Promise<any>}
           */

          function turntablePromise(requestFn, animationDuration{
            return Promise.all([requestFn(), sleep(animationDuration)]);
          }

          // ----------下面是測(cè)試用例------------

          // 模擬一個(gè)異步請(qǐng)求函數(shù)
          function createRequest(delay{
            return () =>
              new Promise((resolve) => {
                setTimeout(() => {
                  resolve("done");
                }, delay);
              });
          }

          // 請(qǐng)求比轉(zhuǎn)盤(pán)動(dòng)畫(huà)快
          turntablePromise(createRequest(2000), 5000).then((res) => console.log(res));

          // 請(qǐng)求比轉(zhuǎn)盤(pán)動(dòng)畫(huà)慢
          turntablePromise(createRequest(2000), 1000).then((res) => console.error(res));

          復(fù)制代碼

          四:基于Promise.all()的轉(zhuǎn)盤(pán)問(wèn)題(考慮請(qǐng)求超時(shí)),無(wú)非就是拼刀刀沒(méi)什么亮點(diǎn)。

          /**
           * 轉(zhuǎn)盤(pán)問(wèn)題考慮超時(shí)
           */


          /**
           * 將原promise包裝成一個(gè)帶超時(shí)控制的promise
           * @param {Promise<any>} request 你的請(qǐng)求
           * @param {number} timeout 最大超時(shí)時(shí)間
           * @returns {Promise<any>}
           */

          function timeoutPromise(request, timeout{
            function sleep(delay{
              return new Promise((resolve, reject) => {
                setTimeout(() => reject(new Error("timeout")), delay);
              });
            }
            const promises = [request, sleep(timeout)];
            return new Promise((resolve, reject) => {
              for (const p of promises) {
                p.then((res) => resolve(res)).catch((error) => reject(error));
              }
            });
          }

          /**
           * 將原promise包裝成一個(gè)轉(zhuǎn)盤(pán)promise
           * @param {()=>Promise<any>} requestFn 請(qǐng)求函數(shù)
           * @param {number} timeout 超時(shí)時(shí)間
           * @param {number} animationDuration 動(dòng)畫(huà)持續(xù)時(shí)間
           * @returns {Promise<any>}
           */

          function turntablePromise(requestFn, timeout, animationDuration{
            function sleep(delay{
              return new Promise((resolve) => {
                setTimeout(() => resolve(delay), delay);
              });
            }
            return Promise.all([timeoutPromise(requestFn(), timeout), sleep(animationDuration)]);
          }

          // ----------下面是測(cè)試用例------------

          // 模擬一個(gè)異步請(qǐng)求函數(shù)
          function createRequest(delay{
            return () =>
              new Promise((resolve) => {
                setTimeout(() => {
                  resolve("done");
                }, delay);
              });
          }

          // 請(qǐng)求比轉(zhuǎn)盤(pán)動(dòng)畫(huà)慢且超時(shí)
          turntablePromise(createRequest(2000), 15001000).catch((error) =>
            console.error(error)
          );

          復(fù)制代碼

          五:干掉Promise.all(),這版代碼沒(méi)有加什么核心的東西,無(wú)非就是手寫(xiě)一下這個(gè)api,所以留給大家自測(cè)。

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

          背景

          當(dāng)用戶頻繁點(diǎn)擊一個(gè)搜索Button時(shí),會(huì)在短時(shí)間內(nèi)發(fā)出大量的搜索請(qǐng)求,給服務(wù)器造成一定的壓力,同時(shí)也會(huì)因請(qǐng)求響應(yīng)的先后次序不同而導(dǎo)致渲染的數(shù)據(jù)與預(yù)期不符。這里,我們可以使用防抖來(lái)減小服務(wù)器壓力,但是卻沒(méi)法很好地解決后面的問(wèn)題。

          問(wèn)題分析

          這個(gè)問(wèn)題的本質(zhì)在于,同一類請(qǐng)求是有序發(fā)出的(根據(jù)按鈕點(diǎn)擊的次序),但是響應(yīng)順序卻是無(wú)法預(yù)測(cè)的,我們通常只希望渲染最后一次發(fā)出請(qǐng)求響應(yīng)的數(shù)據(jù),而其他數(shù)據(jù)則丟棄。因此,我們需要丟棄(或不處理)除最后一次請(qǐng)求外的其他請(qǐng)求的響應(yīng)數(shù)據(jù)。

          實(shí)戰(zhàn)版源碼

          其實(shí)axios已經(jīng)有了很好的實(shí)踐,大家可以配合阿寶哥的文章來(lái)食用。此處取消promise的實(shí)現(xiàn)借助了上一章節(jié)的技巧,而在axios中因?yàn)樗挟惒蕉际怯蓌hr發(fā)出的,所以axios的實(shí)現(xiàn)中還借助了xhr.abort()來(lái)取消一個(gè)請(qǐng)求。

          /**
           * 取消請(qǐng)求
           */


          function CancelablePromise({
            this.pendingPromise = null;
          }

          // 包裝一個(gè)請(qǐng)求并取消重復(fù)請(qǐng)求
          CancelablePromise.prototype.request = function (requestFn{
            if (this.pendingPromise) {
              this.cancel("取消重復(fù)請(qǐng)求");
            }
            const _promise = new Promise((resolve, reject) => (this.reject = reject));
            this.pendingPromise = Promise.race([requestFn(), _promise]);
            return this.pendingPromise;
          };

          // 取消當(dāng)前請(qǐng)求
          CancelablePromise.prototype.cancel = function (reason{
            this.reject(new Error(reason));
            this.pendingPromise = null;
          };

          // ----------下面是測(cè)試用例------------

          // 模擬一個(gè)異步請(qǐng)求函數(shù)
          function createRequest(delay{
            return () =>
              new Promise((resolve) => {
                setTimeout(() => {
                  resolve("done");
                }, delay);
              });
          }


          const cancelPromise = new CancelablePromise();

          // 前四個(gè)請(qǐng)求將被自動(dòng)取消
          for (let i = 0; i < 5; i++) {
            cancelPromise
              .request(createRequest(1000))
              .then((res) => console.log(res)) // 最后一個(gè) done
              .catch((err) => console.error(err)); // 前四個(gè) error: 取消重復(fù)請(qǐng)求
          }

          // 設(shè)置一個(gè)定時(shí)器等3s,讓前面的請(qǐng)求都處理完再繼續(xù)測(cè)試
          setTimeout(() => {
            // 手動(dòng)取消最后一個(gè)請(qǐng)求
            cancelPromise
              .request(createRequest(1000))
              .then((res) => console.log(res))
              .catch((err) => console.error(err)); // error:手動(dòng)取消
            cancelPromise.cancel("手動(dòng)取消");
          }, 3000);

          // 設(shè)置一個(gè)定時(shí)器等4s,讓前面的請(qǐng)求都處理完再繼續(xù)測(cè)試
          setTimeout(() => {
            cancelPromise
              .request(createRequest(1000))
              .then((res) => console.log(res)) // done
              .catch((err) => console.error(err));
          }, 4000);

          復(fù)制代碼

          限制并發(fā)請(qǐng)求數(shù)

          背景

          一般來(lái)說(shuō),我們不會(huì)刻意去控制請(qǐng)求的并發(fā)。只有在一些場(chǎng)景下可能會(huì)用到,比如,收集用戶的批量操作(每個(gè)操作對(duì)應(yīng)一次請(qǐng)求),待用戶操作完成后一次性發(fā)出。另外,為了減小服務(wù)器的壓力,我們還會(huì)限制并發(fā)數(shù)

          問(wèn)題分析

          看上去,Promise.allSettled很適合應(yīng)對(duì)這樣的場(chǎng)景,但是稍微想一下就能發(fā)現(xiàn),它能控制的粒度還是太粗了。首先,它必須等待所有Promiseresolvereject,其次,如果有并發(fā)限制的話用它來(lái)做還需要分批請(qǐng)求,實(shí)際效率也會(huì)比較低,短木板效應(yīng)很明顯。

          實(shí)戰(zhàn)版源碼

          /**
           * 限制并發(fā)請(qǐng)求數(shù)
           */


          /**
           * 并發(fā)請(qǐng)求限制并發(fā)數(shù)
           * @param {()=>Promise<any> []} requestFns 并發(fā)請(qǐng)求函數(shù)數(shù)組
           * @param {numer} limit 限制最大并發(fā)數(shù)
           */

          function concurrentRequest(requestFns, limit{
            // 遞歸函數(shù)
            function recursion(requestFn{
              requestFn().finally(() => {
                if (_requestFns.length > 0) {
                  recursion(_requestFns.pop());
                }
              });
            }
            const _requestFns = [...requestFns];
            // 限制最大并發(fā)量
            for (let i = 0; i < limit && _requestFns.length > 0; i++) {
              recursion(_requestFns.pop());
            }
          }

          // ----------下面是測(cè)試用例------------

          // 模擬一個(gè)異步請(qǐng)求函數(shù)
          function createRequest(delay{
            return () =>
              new Promise((resolve) => {
                setTimeout(() => {
                  resolve("done");
                }, delay);
              }).then((res) => {
                console.log(res);
              });
          }

          const requestFns = [];
          for (let i = 0; i < 10; i++) {
            requestFns.push(createRequest(1000));
          }

          concurrentRequest(requestFns, 3);

          復(fù)制代碼

          管理全局loading態(tài)

          背景

          當(dāng)我們一個(gè)頁(yè)面或組件涉及到多個(gè)請(qǐng)求時(shí),可能會(huì)對(duì)應(yīng)多個(gè)loading態(tài)的管理。在某些場(chǎng)景下,我們只希望用一個(gè)loading態(tài)去管理所有異步請(qǐng)求,當(dāng)任一存在pending態(tài)的請(qǐng)求時(shí),展示全局loading組件,當(dāng)所有請(qǐng)求都fulfilled或rejected時(shí),隱藏全局loading組件。

          問(wèn)題分析

          這個(gè)問(wèn)題的關(guān)鍵就是在于我們需要管理所有pending態(tài)的請(qǐng)求,并適時(shí)更新loading態(tài)。

          實(shí)戰(zhàn)版源碼

          /**
           * 管理全局loading態(tài)
           */


          function PromiseManager({
            this.pendingPromise = new Set();
            this.loading = false;
          }

          // 給每個(gè)pending態(tài)的promise生成一個(gè)身份標(biāo)志
          PromiseManager.prototype.generateKey = function ({
            return `${new Date().getTime()}-${parseInt(Math.random() * 1000)}`;
          };

          PromiseManager.prototype.push = function (...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è)試用例------------

          // 模擬一個(gè)異步請(qǐng)求函數(shù)
          function createRequest(delay{
            return () =>
              new Promise((resolve) => {
                setTimeout(() => {
                  resolve("done");
                }, delay);
              }).then((res) => console.log(res));
          }

          const manager = new PromiseManager();

          // 增加多個(gè)請(qǐng)求
          manager.push(createRequest(1000), createRequest(2000), createRequest(3000));
          manager.push(createRequest(1500));

          // 每秒輪詢loading態(tài),直到loading為false
          const id = setInterval(() => {
            console.log(manager.loading);
            if (!manager.loading) clearInterval(id);
          }, 1000);

          // 增加多個(gè)請(qǐng)求
          manager.push(createRequest(2500));
          復(fù)制代碼

          加餐

          串行化的三種實(shí)現(xiàn)方式

          使用串行化的常見(jiàn)場(chǎng)景,請(qǐng)求之間有依賴關(guān)系或時(shí)序關(guān)系,如紅綠燈。

          /**
           * 串行化的三種實(shí)現(xiàn)
           **/


          // 法一,遞歸法
          function runPromiseInSeq1(requestFns{
            function recursion(requestFns{
              if (requestFns.length === 0return;
              requestFns
                .shift()()
                .finally(() => recursion(requestFns));
            }
            const _requestFns = [...requestFns];
            recursion(_requestFns);
          }
          // 法二:迭代法
          async function runPromiseInSeq2(requestFns{
            for (const requestFn of requestFns) {
              await requestFn();
            }
          }
          // 法三:reduce
          function runPromiseInSeq3(requestFns{
            requestFns.reduce((pre, cur) => pre.finally(() => cur()), Promise.resolve());
          }

          // 模擬一個(gè)異步請(qǐng)求函數(shù)
          function createRequest(delay{
            return () =>
              new Promise((resolve) => {
                setTimeout(() => {
                  resolve(delay);
                }, delay);
              }).then((res) => {
                console.log(res);
              });
          }
          // 執(zhí)行順序從左至右
          const requestFns = [
            createRequest(3000),
            createRequest(2000),
            createRequest(1000),
          ];
          // 串行調(diào)用
          runPromiseInSeq1(requestFns);
          // runPromiseInSeq2(requestFns);
          // runPromiseInSeq3(requestFns);

          復(fù)制代碼

          20行最簡(jiǎn)異步鏈?zhǔn)秸{(diào)用

          這里模擬了Promise的異步鏈?zhǔn)秸{(diào)用,代碼出處見(jiàn)文章。

          function Promise(fn{
            this.cbs = [];

            const resolve = (value) => {
              setTimeout(() => {
                this.data = value;
                this.cbs.forEach((cb) => cb(value));
              });
            }

            fn(resolve);
          }

          Promise.prototype.then = function (onResolved{
            return new Promise((resolve) => {
              this.cbs.push(() => {
                const res = onResolved(this.data);
                if (res instanceof Promise) {
                  res.then(resolve);
                } else {
                  resolve(res);
                }
              });
            });
          };
          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品久久久久久久久 | www.色五月 | 亚洲天堂精品在线观看 | 人妇一区三区高清乱码视频 | 中文字幕欧美在线 |