<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>

          解決前端常見(jiàn)問(wèn)題:競(jìng)態(tài)條件

          共 6099字,需瀏覽 13分鐘

           ·

          2022-05-21 17:27

          | 導(dǎo)語(yǔ)?競(jìng)態(tài)條件一詞翻譯自英語(yǔ) "race conditions"。當(dāng)我們?cè)陂_(kāi)發(fā)前端 web 時(shí),最常見(jiàn)的邏輯就是從后臺(tái)服務(wù)器獲取并處理數(shù)據(jù)然后渲染到瀏覽器頁(yè)面上,過(guò)程中有不少的細(xì)節(jié)需要注意,其中一個(gè)就是數(shù)據(jù)競(jìng)態(tài)條件問(wèn)題,本文會(huì)基于 React 并結(jié)合一個(gè)小 demo 來(lái)解釋何為競(jìng)態(tài)條件,以及循序漸進(jìn)地介紹解決競(jìng)態(tài)條件方法。框架不同解決的方式會(huì)不一樣,但不影響理解競(jìng)態(tài)條件。

          獲取數(shù)據(jù)

          下面是一個(gè)小 demo:前端獲取文章數(shù)據(jù),并渲染到頁(yè)面上

          App.tsx

          import React from 'react';
          import { Routes, Route } from 'react-router-dom';
          import Article from './Article';

          function App() {
          return (
          <Routes>
          <Route path="/articles/:articleId" element={<Article />} />
          Routes>

          );
          }

          export default App;

          Article.tsx

          import React from 'react';
          import useArticleLoading from './useArticleLoading';

          const Article = () => {
          const { article, isLoading } = useArticleLoading();

          if (!article || isLoading) {
          return<div>Loading...div>;
          }

          return (
          <div>
          <p>{article.id}p>

          <p>{article.title}p>
          <p>{article.body}p>
          div>
          );
          };

          export default Article;

          在上述的 Article 組件中,我們把相關(guān)的數(shù)據(jù)請(qǐng)求封裝到了自定義 hook "useArticleLoading" 中,為了頁(yè)面的使用體驗(yàn),我們要么顯示獲取的數(shù)據(jù),要么顯示加載中。這里加上了加載態(tài)的判斷。

          useArticleLoading.tsx

          import { useParams } from 'react-router-dom';
          import { useEffect, useState } from 'react';

          interface Article {
          ?id: number;
          ?title: string;
          ?body: string;
          }

          function useArticleLoading() {
          ?const { articleId } = useParams<{ articleId: string }>();
          ?const [isLoading, setIsLoading] = useState(false);
          ?const [article, setArticle] = useState
          null>(null);

          ?useEffect(() => {
          setIsLoading(true);
          fetch(`https://get.a.article.com/articles/${articleId}`)
          ? ? ?.then((response) => {
          ? ? ? ?if (response.ok) {
          ? ? ? ? ?return response.json();
          ? ? ? ?}
          ? ? ? ?return Promise.reject();
          ? ? ?})
          ? ? ?.then((fetchedArticle: Article) => {
          ? ? ? ?setArticle(fetchedArticle);
          ? ? ?})
          ? ? ?.finally(() => {
          ? ? ? ?setIsLoading(false);
          ? ? ?});
          ?}, [articleId]);

          ?return {
          ? ?article,
          ? ?isLoading,
          ?};
          }

          export default useArticleLoading;

          在這個(gè)自定義 hook 中,我們管理了加載態(tài)以及數(shù)據(jù)請(qǐng)求

          當(dāng)我們 url 訪問(wèn) /articles/1 時(shí),會(huì)發(fā)出 get 請(qǐng)求獲取對(duì)應(yīng) articleId 為 1 的文章內(nèi)容

          競(jìng)態(tài)條件出現(xiàn)場(chǎng)景

          上面是我們非常常見(jiàn)的獲取數(shù)據(jù)的方法,但是讓我們考慮以下情況(時(shí)間順序):

          • 訪問(wèn) articles/1 查看第一個(gè)文章內(nèi)容

            • 瀏覽器開(kāi)始請(qǐng)求后臺(tái)服務(wù)器,獲取文章 1 的內(nèi)容

            • 網(wǎng)絡(luò)連接出現(xiàn)問(wèn)題

            • articles/1 請(qǐng)求未響應(yīng),數(shù)據(jù)未渲染到頁(yè)面中

          • 不等待 articles/1 了,訪問(wèn) articles/2

            • 瀏覽器開(kāi)始請(qǐng)求后臺(tái)服務(wù)器,獲取文章 2 的內(nèi)容

            • 網(wǎng)絡(luò)連接沒(méi)有問(wèn)題

            • articles/2 請(qǐng)求立即響應(yīng)了,數(shù)據(jù)渲染到頁(yè)面中

          • articles/1 的請(qǐng)求響應(yīng)了

            • 通過(guò) setArticles (fetchedArticles) 覆蓋了當(dāng)前的文章內(nèi)容

            • 當(dāng)前 url 應(yīng)該顯示 articles/2,卻顯示了 articles/1

          需要理解的一點(diǎn)就是,網(wǎng)絡(luò)請(qǐng)求的過(guò)程是復(fù)雜的,且響應(yīng)時(shí)間是不確定的,訪問(wèn)同一個(gè)目的地址,請(qǐng)求經(jīng)過(guò)的網(wǎng)絡(luò)鏈路不一定是一樣的路徑。所以先發(fā)出的請(qǐng)求不一定先響應(yīng),如果前端以先發(fā)請(qǐng)求先響應(yīng)的規(guī)則來(lái)開(kāi)發(fā)的話,那么就可能會(huì)導(dǎo)致錯(cuò)誤的數(shù)據(jù)使用,這就是競(jìng)態(tài)條件問(wèn)題。

          解決

          解決方法也很簡(jiǎn)單,當(dāng)收到響應(yīng)后,只要判斷當(dāng)前數(shù)據(jù)是否需要,如果不是則忽略即可。

          在 React 中可以很巧妙的通過(guò) useEffect 的執(zhí)行機(jī)制來(lái)簡(jiǎn)潔、方便地做到這點(diǎn):

          useArticlesLoading.tsx

          useEffect(() => {
          ?let didCancel = false;

          ?setIsLoading(true);
          ?fetch(`https://get.a.article.com/articles/${articleId}`)
          ? ?.then((response) => {
          ? ? ?if (response.ok) {
          ? ? ? ?return response.json();
          ? ? ?}
          ? ? ?return Promise.reject();
          ? ?})
          ? ?.then((fetchedArticle: Article) => {
          ? ? ?if (!didCancel) {
          ? ? ? ?setArticle(fetchedArticle);
          ? ? ?}
          ? ?})
          ? ?.finally(() => {
          ? ? ?setIsLoading(false);
          ? ?});

          ?return () => {
          ? ?didCancel = true;
          ?}
          }, [articleId]);

          根據(jù) hook 的執(zhí)行機(jī)制:每次切換獲取新文章時(shí),執(zhí)行 useEffect 返回的函數(shù),然后再重新執(zhí)行 hook,重新渲染。

          現(xiàn)在 bug 不會(huì)再出現(xiàn)了:

          • 訪問(wèn) articles/1 查看第一個(gè)文章內(nèi)容

            • 瀏覽器開(kāi)始請(qǐng)求后臺(tái)服務(wù)器,獲取文章 1 的內(nèi)容

            • 網(wǎng)絡(luò)連接出現(xiàn)問(wèn)題

            • articles/1 請(qǐng)求未響應(yīng),數(shù)據(jù)未渲染到頁(yè)面中

          • 不等待 articles/1 了,訪問(wèn) articles/2

            • useArticleLoading 重新渲染執(zhí)行,重新渲染前執(zhí)行了上一次的 useEffect 返回函數(shù),把 didCancel 設(shè)置為 true

            • 網(wǎng)絡(luò)連接沒(méi)有問(wèn)題

            • articles/2 請(qǐng)求立即響應(yīng)了,數(shù)據(jù)渲染到頁(yè)面中

          • articles/1 的請(qǐng)求響應(yīng)了

            • 由于 didCancel 變量,setArticles (fetchedArticles) 沒(méi)有執(zhí)行。

          處理完后,當(dāng)我們?cè)俅吻袚Q文章時(shí),didCancel 為 true,就不會(huì)再處理上一個(gè)文章的數(shù)據(jù),以及 setArticles。

          AbortController 解決

          雖然上述通過(guò)變量的解決方案解決了問(wèn)題,但它并不是最優(yōu)的。瀏覽器仍然等待請(qǐng)求完成,但忽略其結(jié)果。這樣仍然浪費(fèi)占用著資源。為了改進(jìn)這一點(diǎn),我們可以使用 AbortController。

          通過(guò) AbortController,我們可以中止一個(gè)或多個(gè)請(qǐng)求。使用方法很簡(jiǎn)單,創(chuàng)建 AbortController 實(shí)例,并在發(fā)出請(qǐng)求時(shí)使用它:

          useEffect(() => {
          const abortController = new AbortController();

          setIsLoading(true);
          fetch(`https://get.a.rticle.com/articles/${articleId}`, {
          signal: abortController.signal,
          })
          .then((response) => {
          if (response.ok) {
          return response.json();
          }
          return Promise.reject();
          })
          .then((fetchedArticle: Article) => {
          setArticle(fetchedArticle);
          })
          .finally(() => {
          setIsLoading(false);
          });

          return () => {
          abortController.abort();
          };
          }, [articleId]);

          通過(guò)傳遞 abortController.signal,我們可以很容易的使用 abortController.abort() 來(lái)終止請(qǐng)求(也可以使用相同的 signal 傳遞給多個(gè)請(qǐng)求,這樣可以終止多個(gè)請(qǐng)求)

          使用 abortController 后,再來(lái)看看效果:

          • 訪問(wèn) articles/1

            • 請(qǐng)求服務(wù)器獲取 articles/1 數(shù)據(jù)

          • 不等待響應(yīng),再訪問(wèn) articles/2

            • 重新渲染 hook,useEffect 執(zhí)行返回函數(shù),執(zhí)行 abortController.abort ()

            • 請(qǐng)求服務(wù)器獲取 articles/2 數(shù)據(jù)

            • 獲取到 articles/2 數(shù)據(jù)并渲染到頁(yè)面上

          • 第一個(gè)文章從未完成加載,因?yàn)槲覀兪謩?dòng)終止了請(qǐng)求

          可以在開(kāi)發(fā)工具中查看手動(dòng)中斷的請(qǐng)求:

          調(diào)用 abortController.abort () 有一個(gè)問(wèn)題,就是其會(huì)導(dǎo)致 promise 被拒絕,可能會(huì)導(dǎo)致未捕獲的錯(cuò)誤:

          為了避免,我們可以加個(gè)捕獲錯(cuò)誤處理:

          useEffect(() => {
          ?const abortController = new AbortController();

          ?setIsLoading(true);
          ?fetch(`https://get.a.article.com/articles/${articleId}`, {
          ? ?signal: abortController.signal,
          ?})
          ? ?.then((response) => {
          ? ? ?if (response.ok) {
          ? ? ? ?return response.json();
          ? ? ?}
          ? ? ?return Promise.reject();
          ? ?})
          ? ?.then((fetchedArticle: Article) => {
          ? ? ?setArticle(fetchedArticle);
          ? ?})
          ? ?.catch(() => {
          ? ? ?if (abortController.signal.aborted) {
          ? ? ? ?console.log('The user aborted the request');
          ? ? ?} else {
          ? ? ? ?console.error('The request failed');
          ? ? ?}
          ? ?})
          ? ?.finally(() => {
          ? ? ?setIsLoading(false);
          ? ?});

          ?return () => {
          ? ?abortController.abort();
          ?};
          }, [articleId]);

          停止其他 promises

          AbortController 不止可以停止異步請(qǐng)求,在函數(shù)中也是可以使用的:

          function wait(time: number) {
          return new Promise<void>((resolve) => {
          setTimeout(() => {
          resolve();
          }, time);
          });
          }
          wait(5000).then(() => {
          console.log('5 seconds passed');
          });
          function wait(time: number, signal?: AbortSignal) {
          return new Promise<void>((resolve, reject) => {
          const timeoutId = setTimeout(() => {
          resolve();
          }, time);
          signal?.addEventListener('abort', () => {
          clearTimeout(timeoutId);
          reject();
          });
          });
          }
          const abortController = new AbortController();

          setTimeout(() => {
          abortController.abort();
          }, 1000);

          wait(5000, abortController.signal)
          .then(() => {
          console.log('5 seconds passed');
          })
          .catch(() => {
          console.log('Waiting was interrupted');
          });

          傳遞 signal 給 wait 來(lái)終止 promise。

          其他

          關(guān)于 AbortController 兼容性:

          除了 IE,其他可以放心使用。

          總結(jié)

          本文討論了 React 中的競(jìng)態(tài)條件,解釋了競(jìng)態(tài)條件問(wèn)題。為了解決這個(gè)問(wèn)題,我們學(xué)習(xí)了 AbortController 背后的思想,并擴(kuò)展了解決方案。除此之外,我們還學(xué)習(xí)了如何將 AbortController 用于其他目的。它需要我們更深入地挖掘并更好地理解 AbortController 是如何工作的。對(duì)于前端,可以選擇自己最合適的解決方案。


          往期推薦


          我?guī)鸵慌笥阎貥?gòu)了點(diǎn)代碼,他直呼牛批,但基操勿六
          阿里前端:我的老婆失業(yè)了,周?chē)乱苍诓粩啾徊?/a>
          前端JS 燒腦面試題大賞

          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專(zhuān)業(yè)的技術(shù)人...

          點(diǎn)個(gè)在看支持我吧
          瀏覽 71
          點(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>
                  玩弄奶水刚产少妇 | 东北操逼网 | 免费看18禁网站 | 国产视频99 | 亚州无码在线 |