解鎖 Fetch 請求:開啟高效網(wǎng)絡(luò)數(shù)據(jù)交互之旅
2024-12-23 09:12:49
一、Fetch 請求:前端通信的新利器

在前端開發(fā)領(lǐng)域,網(wǎng)絡(luò)請求一直是非常關(guān)鍵的一環(huán),早期我們主要依賴 XMLHttpRequest(XHR)來實(shí)現(xiàn)客戶端與服務(wù)器之間的異步數(shù)據(jù)交換。它允許瀏覽器向服務(wù)器發(fā)送 HTTP 請求,獲取數(shù)據(jù)并更新部分網(wǎng)頁內(nèi)容,無需刷新整個頁面,在 Web 開發(fā)中廣泛用于動態(tài)內(nèi)容加載、表單提交、數(shù)據(jù)交互等功能,像實(shí)現(xiàn) AJAX 請求就常借助它,也正因如此,它為用戶帶來了更加流暢的使用體驗(yàn)。不過,XHR 也存在一些不足,其使用起來相對復(fù)雜,需要手動創(chuàng)建、配置和發(fā)送請求,還要監(jiān)聽事件來處理響應(yīng),例如通過設(shè)置 onreadystatechange 事件監(jiān)聽器或使用 addEventListener 方法來處理異步請求的狀態(tài)變化。并且,由于 XHR 可以跨域請求數(shù)據(jù),可能會涉及到安全性問題,所以在使用時得謹(jǐn)慎處理跨域請求以及防止 XSS 攻擊等情況。隨著技術(shù)的發(fā)展,F(xiàn)etch 請求應(yīng)運(yùn)而生,它是較新的瀏覽器提供的 API,旨在簡化和優(yōu)化前端開發(fā)中的網(wǎng)絡(luò)請求過程。與傳統(tǒng)的 XMLHttpRequest 相比,F(xiàn)etch 使用起來更加簡潔和易用,它返回一個 Promise 對象,能使用鏈?zhǔn)秸{(diào)用來處理請求和響應(yīng),像我們可以使用 then 和 catch 方法輕松處理請求的成功和失敗情況,避免了像 XHR 那樣陷入較為復(fù)雜的回調(diào)設(shè)置中。舉個簡單的例子,如果我們想要請求一個 URL 地址,獲取響應(yīng)數(shù)據(jù)并將數(shù)據(jù)轉(zhuǎn)換成 JSON 格式,使用 XMLHttpRequest 來實(shí)現(xiàn)需要設(shè)置兩個監(jiān)聽函數(shù),分別處理成功和失敗的情況,同時還得依次調(diào)用 open () 和 send () 方法才能完成請求;而使用 Fetch 實(shí)現(xiàn)的話,像這樣:fetch('./api/some.json').then(function(res) {if (res.status!==200) {console.log('Looks like there was a problem. Status Code: ' + res.status);return;}// 處理響應(yīng)中的文本信息 res.json().then(function(data) {console.log(data);});}).catch(function(err) {console.log('Fetch Error : %S', err);}),代碼邏輯清晰簡潔不少。而且,F(xiàn)etch 還具有更好的靈活性,支持流處理、跨域請求等高級功能,在處理大數(shù)據(jù)和文件上傳時會更加方便,也可以通過 Request 和 Response 對象進(jìn)行擴(kuò)展和自定義,讓開發(fā)者擁有更高的可控性。雖然 Fetch 是標(biāo)準(zhǔn)中的新 API,在一些舊版本的瀏覽器中可能不被完全支持,但為了兼容舊版本瀏覽器,我們可以使用 fetch 的 polyfill 或者選擇其他庫(如 Axios)來進(jìn)行網(wǎng)絡(luò)請求??傊?,在當(dāng)下的前端開發(fā)中,F(xiàn)etch 請求憑借其自身優(yōu)勢,已然成為了前端通信的新利器,為開發(fā)者處理網(wǎng)絡(luò)請求提供了更優(yōu)的選擇。
二、Fetch 基本使用全解析
(一)語法結(jié)構(gòu)與參數(shù)說明
Fetch 的基本語法為 fetch(url, options)。其中,url 是要獲取資源的地址,可以是絕對路徑或相對路徑。例如,'https://example.com/api/data' 或者 './data.json'。options 是一個可選參數(shù),于配置請求的各種細(xì)節(jié),它是一個對象,包含以下常用屬性::表示請求方法,如 GET、POST、PUT、delete 等。默認(rèn)是 GET。headers:用于設(shè)置請求頭信息,是一個對象。例如 { 'Content-Type': 'application/json' } 用于指定請求體數(shù)據(jù)為 JSON 格式。body:通常在 POST 或 PUT 請求中使用,用于傳遞請求體數(shù)據(jù)。它可以是字符串、FormData 對象、Blob 對象等。例如,JSON.stringify({ key: 'value' }) 可將一個 JavaScript 對象轉(zhuǎn)換為 JSON 字符串作為請求體數(shù)據(jù)。
(二)GET 請求示例
在這個示例中,首先使用 fetch 發(fā)送一個 GET 請求到指定的 URL。然后,通過 then 方法處理響應(yīng)。先檢查 response.ok 屬性,如果請求不成功(狀態(tài)碼不是 200 - 299 之間),則拋出一個錯誤。接著,使用 response.json() 方法將響應(yīng)體數(shù)據(jù)解析為 JSON 格式,并在后續(xù)的 then 方法中處理解析后的數(shù)據(jù),將其打印到控制臺。如果請求過程中出現(xiàn)錯誤,catch 方法會捕獲并打印錯誤信息。
(三)POST 請求示例
在這個例子中,先創(chuàng)建了一個包含數(shù)據(jù)的對象 postData。然后,在 fetch 請求中,將 method 設(shè)置為 POST,并通過 headers 設(shè)置請求頭為 'Content-Type': 'application/json',表示請求體數(shù)據(jù)為 JSON 格式。接著,使用 JSON.stringify 方法將 postData 對象轉(zhuǎn)換為 JSON 字符串,并賦值給 body 屬性。最后,像 GET 請求示例一樣,處理響應(yīng)數(shù)據(jù),將服務(wù)器返回的數(shù)據(jù)解析為 JSON 格式并打印到控制臺,如果有錯誤則捕獲并打印錯
三、深入理解 Fetch 的特性與坑
(一)響應(yīng)處理的細(xì)節(jié)
在 Fetch 請求中,response.ok 屬性是一個非常重要的指示符,它用于判斷 HTTP 狀態(tài)碼是否處于 200 - 299 這個成功的區(qū)間范圍內(nèi)。當(dāng)我們發(fā)起一個 Fetch 請求后,服務(wù)器會返回一個響應(yīng),這個響應(yīng)包含了各種信息,其中 HTTP 狀態(tài)碼就反映了請求的處理結(jié)果。如果 response.ok 的值為 true,則表示請求成功,例如常見的 200 狀態(tài)碼表示請求已成功處理并返回了預(yù)期的數(shù)據(jù)。然而,如果 response.ok 為 false,這就意味著請求出現(xiàn)了問題,可能是 404 表示資源未找到,或者 500 表示服務(wù)器內(nèi)部錯誤等情況。與傳統(tǒng)的一些請求方式不同,F(xiàn)etch 對于非網(wǎng)絡(luò)錯誤的 HTTP 狀態(tài)碼(如 404、500)不會自動 reject Promise。這是因?yàn)?Fetch 的設(shè)計理念是將網(wǎng)絡(luò)請求的成功與具體業(yè)務(wù)邏輯的成功分開處理。在很多情況下,即使服務(wù)器返回了一個表示錯誤的狀態(tài)碼,如 403 表示權(quán)限不足,前端應(yīng)用可能仍然需要根據(jù)這個信息進(jìn)行相應(yīng)的處理,而不是簡單地將其視為一個失敗的請求而中斷后續(xù)流程。所以,F(xiàn)etch 會在 then 方法中處理這些非網(wǎng)絡(luò)錯誤的情況,讓開發(fā)者能夠更靈活地根據(jù)不同的狀態(tài)碼進(jìn)行自定義的處理邏輯。例如,當(dāng)收到 401 狀態(tài)碼時,我們可以在 then 中提示用戶進(jìn)行登錄操作,以獲取正確的權(quán)限。但是,為了能夠統(tǒng)一處理錯誤情況,我們通常需要手動拋出錯誤。這樣做的好處是可以將錯誤處理邏輯集中在 catch 方法中,使代碼結(jié)構(gòu)更加清晰,易于維護(hù)。比如在一個數(shù)據(jù)獲取的請求中,如果服務(wù)器返回了 404 狀態(tài)碼,我們可以在 then 方法中檢查 response.ok 屬性,如果為 false,則拋出一個帶有詳細(xì)錯誤信息的自定義錯誤對象,像這樣:通過這種方式,無論是網(wǎng)絡(luò)錯誤還是業(yè)務(wù)邏輯錯誤,都能夠在 catch 方法中被統(tǒng)一捕獲和處理,方便我們進(jìn)行錯誤日志記錄、用戶提示等操作,提升應(yīng)用的穩(wěn)定性和用戶體驗(yàn)。
(二)跨域請求與 CORS
Fetch 請求遵循 CORS(跨域資源共享)策略。同源策略是瀏覽器的一種安全機(jī)制,它規(guī)定只有當(dāng)兩個 URL 的協(xié)議、域名和端口完全相同時,才被認(rèn)為是同源的。例如,https://www.example.com 和 https://www.example.com:8080 由于端口不同,就屬于非同源;http://www.example.com 和 https://www.example.com 因?yàn)閰f(xié)議不同,也不是同源的。在同源策略的限制下,瀏覽器默認(rèn)會阻止跨域請求,以防止惡意網(wǎng)站竊取用戶數(shù)據(jù)或進(jìn)行其他不安全的操作。而 CORS 機(jī)制則是為了在保證安全的前提下,允許跨域請求資源而設(shè)立的。當(dāng)我們使用 Fetch 進(jìn)行跨域請求時,瀏覽器會自動在請求頭中添加 Origin 字段,這個字段標(biāo)明了請求的源信息,即當(dāng)前頁面的協(xié)議、域名和端口。服務(wù)器在接收到請求后,會根據(jù)這個 Origin 字段以及自身的配置來決定是否允許該跨域請求。如果服務(wù)器允許,它會在響應(yīng)頭中添加相應(yīng)的 Access-Control-Allow-Origin 字段,告訴瀏覽器該請求被允許。例如,服務(wù)器端可以這樣設(shè)置響應(yīng)頭:這就表示允許來自 http://example.com 的跨域請求。在實(shí)際開發(fā)中,不同場景下可能會遇到各種跨域問題。在開發(fā)環(huán)境中,如果我們使用本地開發(fā)服務(wù)器(如 http://localhost:3000)進(jìn)行開發(fā),并且需要向另一個本地的后端服務(wù)(如 http://localhost:8080)發(fā)起跨域請求,就需要在后端服務(wù)中正確配置 CORS。一些常見的后端框架(如 Node.js 的 Express 框架)提供了簡單的方式來配置 CORS,例如:不過,在生產(chǎn)環(huán)境中,我們通常不能使用通配符 * 來允許所有源的跨域請求,而是需要明確指定允許的源,以提高安全性。例如:此外,如果跨域請求涉及到發(fā)送 cookies 等憑證信息,還需要在服務(wù)器端和客戶端都進(jìn)行相應(yīng)的設(shè)置。在服務(wù)器端,除了設(shè)置 Access-Control-Allow-Origin 外,還需要設(shè)置 Access-Control-Allow-Credentials 為 true,并且 Access-Control-Allow-Origin 不能為通配符 *,必須指定具體的源。在客戶端,使用 Fetch 發(fā)起請求時,需要設(shè)置 credentials 選項(xiàng)為 'include',如下所示:
(三)Cookie 傳遞問題
Fetch 默認(rèn)情況下不會自動發(fā)送或接收 cookies。這是因?yàn)樵谀承﹫鼍跋?,不發(fā)送 cookies 可以減少不必要的網(wǎng)絡(luò)流量和潛在的安全風(fēng)險。例如,對于一些公開的資源獲取請求,不需要攜帶用戶的身份認(rèn)證信息(cookies)。然而,在很多實(shí)際應(yīng)用中,特別是涉及到用戶認(rèn)證、會話保持等功能時,正確處理 cookies 是至關(guān)重要的。如果站點(diǎn)依賴于用戶 session,而 Fetch 又不發(fā)送 cookies,就會導(dǎo)致請求無法通過用戶認(rèn)證,從而返回未經(jīng)認(rèn)證的結(jié)果。為了改變這種情況,我們可以通過設(shè)置 credentials 選項(xiàng)來控制 cookies 的行為。當(dāng) credentials 選項(xiàng)設(shè)置為 'include' 時,F(xiàn)etch 會在請求中包含 cookies,并且在響應(yīng)中也會接收并保存服務(wù)器設(shè)置的 cookies。例如,在一個用戶登錄后進(jìn)行數(shù)據(jù)獲取的場景中:這樣,瀏覽器就會將與該域名相關(guān)的 cookies 發(fā)送到服務(wù)器,服務(wù)器根據(jù)這些 cookies 中的信息(如用戶 session ID)來識別用戶身份,并返回相應(yīng)的用戶信息。如果不設(shè)置 credentials 選項(xiàng)或者設(shè)置為其他值(如 'omit' 表示忽略 cookies),則可能導(dǎo)致服務(wù)器無法識別用戶,返回錯誤的結(jié)果或者要求用戶重新登錄。
四、Fetch 高級應(yīng)用與優(yōu)化技巧
(一)請求的封裝與復(fù)用
在實(shí)際的前端項(xiàng)目開發(fā)中,我們經(jīng)常會在多個地方發(fā)起相同類型的 Fetch 請求,例如獲取用戶信息、查詢數(shù)據(jù)列表等。如果每次請求都直接編寫 Fetch 代碼,不僅會導(dǎo)致代碼冗余,還會增加維護(hù)成本。因此,將 Fetch 請求封裝成一個函數(shù)是非常有必要的。下面是一個簡單的 Fetch 請求封裝函數(shù)示例:在上述代碼中,fetchData 函數(shù)接受 url、method、data 和 headers 等參數(shù),根據(jù)這些參數(shù)構(gòu)建 fetch 請求的配置對象 options,并發(fā)起請求。如果請求成功且響應(yīng)狀態(tài)碼在 200 - 299 之間,函數(shù)將解析響應(yīng)數(shù)據(jù)并返回;如果請求失敗或響應(yīng)狀態(tài)碼異常,函數(shù)將拋出錯誤。通過這樣的封裝,我們可以在項(xiàng)目中方便地復(fù)用 fetch 請求代碼。例如,在獲取用戶信息的組件中:在查詢數(shù)據(jù)列表的組件中:這樣,我們只需要維護(hù) fetchData 函數(shù)的代碼,就可以在整個項(xiàng)目中進(jìn)行統(tǒng)一的 fetch 請求處理,提高了代碼的可維護(hù)性和復(fù)用性。同時,我們還可以在封裝函數(shù)中添加更多的功能,如統(tǒng)一處理加載狀態(tài)、錯誤提示等。例如,我們可以在函數(shù)內(nèi)部添加一個 isLoading 變量來表示請求的加載狀態(tài),在請求發(fā)起前將其設(shè)置為 true,在請求完成后設(shè)置為 false,并通過回調(diào)函數(shù)或事件通知的方式將加載狀態(tài)傳遞給調(diào)用者,以便在界面上顯示相應(yīng)的加載提示。
(二)結(jié)合 Async/Await 使用
Async/Await 是 JavaScript 中處理異步操作的語法糖,它使得編寫和理解異步代碼更加簡潔和直觀。在使用 Fetch 請求時,結(jié)合 Async/Await 可以讓代碼看起來更像同步代碼,避免了傳統(tǒng)的 then 鏈嵌套帶來的回調(diào)地獄問題。在上述示例中,fetchUserData 函數(shù)被定義為一個異步函數(shù),使用 await 關(guān)鍵字等待 fetch 請求的完成。當(dāng) fetch 請求返回響應(yīng)后,繼續(xù)使用 await 等待 response.json() 方法將響應(yīng)數(shù)據(jù)解析為 JSON 格式。這樣的代碼結(jié)構(gòu)清晰,易于理解和調(diào)試,與同步代碼的執(zhí)行邏輯相似。與使用 then 鏈的方式相比,Async/Await 的優(yōu)勢在于減少了代碼的嵌套層級,使代碼更加扁平化。例如,使用 then 鏈實(shí)現(xiàn)相同功能的代碼如下:可以看到,then 鏈的方式會導(dǎo)致代碼嵌套,當(dāng)處理多個 then 回調(diào)時,代碼的可讀性會逐漸降低。而 Async/Await 則能夠以更線性的方式編寫異步代碼,提高了代碼的可維護(hù)性。此外,Async/Await 還能更好地與其他異步操作(如 Promise.all)結(jié)合使用,方便地實(shí)現(xiàn)并發(fā)請求等復(fù)雜邏輯在上述示例中,使用 Promise.all 并發(fā)發(fā)送兩個 Fetch 請求,然后使用 await 等待所有請求完成。接著,分別處理每個請求的響應(yīng)數(shù)據(jù)。這種方式使得并發(fā)請求的代碼更加簡潔明了,易于理解和維護(hù)。
五、項(xiàng)目實(shí)戰(zhàn):Fetch 在真實(shí)場景中的應(yīng)用
(一)案例介紹:從開放 API 獲取數(shù)據(jù)并展示
在這個實(shí)戰(zhàn)項(xiàng)目中,我們將構(gòu)建一個簡單的網(wǎng)頁應(yīng)用,從一個開放的 API 獲取數(shù)據(jù)并展示在頁面上。這里以獲取 GitHub 上某個特定用戶的公開倉庫信息為例。
(二)搭建項(xiàng)目環(huán)境
首先,創(chuàng)建一個 HTML 文件,作為項(xiàng)目的入口頁面。在 HTML 文件中,引入一個 JavaScript 文件,用于編寫我們的 Fetch 代碼。
(三)編寫 Fetch 代碼
在 script.js 文件中,使用 Fetch 來獲取 GitHub API 的數(shù)在上述代碼中,首先定義了要請求的 API 地址,即 https://api.github.com/users/octocat/repos,這將獲取 octocat 用戶的公開倉庫信息。然后使用 fetch 發(fā)起請求,獲取響應(yīng)后,先檢查響應(yīng)狀態(tài)碼,如果不是 200 - 299 之間,則拋出錯誤。接著將響應(yīng)數(shù)據(jù)解析為 JSON 格式,并在后續(xù)的 then 方法中遍歷數(shù)據(jù),為每個倉庫創(chuàng)建一個列表項(xiàng),并將其添加到頁面上的 repo-list 元素中。如果請求過程中出現(xiàn)錯誤,catch 方法會捕獲并打印錯誤信息。
(四)可能遇到的問題及解決方案
1. 跨域問題
如果在本地開發(fā)環(huán)境中直接運(yùn)行上述代碼,可能會遇到跨域問題,因?yàn)闉g覽器的同源策略限制了跨域請求。解決這個問題,可以在后端服務(wù)器上設(shè)置 CORS 頭信息,允許來自本地開發(fā)環(huán)境的跨域請求。如果是使用一些本地開發(fā)服務(wù)器,如 Node.js 的 Express 框架,可以添加如下代碼來允許跨域請求:或者,如果是使用 GitHub API,可以利用其支持 JSONP 的特性,通過 fetch-jsonp 庫來進(jìn)行跨域請求。首先安裝 fetch-jsonp:
2. 數(shù)據(jù)處理問題
獲取到的數(shù)據(jù)可能需要進(jìn)行進(jìn)一步的處理和轉(zhuǎn)換,才能滿足頁面展示的需求。例如,上述代碼中只是簡單地展示了倉庫的名稱,如果想要展示更多信息,如倉庫的描述、創(chuàng)建時間等,就需要對數(shù)據(jù)進(jìn)行相應(yīng)的處理和 HTML 模板的構(gòu)建。在這個修改后的代碼中,為每個倉庫創(chuàng)建了一個更詳細(xì)的列表項(xiàng),包括倉庫名稱、描述(如果有)和創(chuàng)建時間,并使用 innerHTML 來插入 HTML 模板字符串,以更好地展示倉庫信息。
六、總結(jié)與展望
通過對 Fetch 請求的詳細(xì)探討,我們可以看到它在前端開發(fā)中具有諸多優(yōu)勢。其簡潔的語法、基于 Promise 的異步處理機(jī)制、對現(xiàn)代網(wǎng)絡(luò)功能的支持以及豐富的 API ,都使得它成為了處理網(wǎng)絡(luò)請求的有力工具。在實(shí)際項(xiàng)目中,我們深入了解了它的基本使用方法、特性與坑以及高級應(yīng)用與優(yōu)化技巧,并通過從開放 API 獲取數(shù)據(jù)并展示的項(xiàng)目實(shí)戰(zhàn),進(jìn)一步掌握了其在真實(shí)場景中的應(yīng)用。然而,F(xiàn)etch 請求也并非十全十美,如兼容性問題、Cookie 處理、超時控制和請求進(jìn)度監(jiān)測等方面仍存在一些不足。但隨著技術(shù)的不斷發(fā)展,我們可以期待 Fetch 在未來會有更好的表現(xiàn)。例如,瀏覽器廠商可能會進(jìn)一步優(yōu)化其兼容性,使其能夠在更多的瀏覽器環(huán)境中穩(wěn)定運(yùn)行;社區(qū)也可能會開發(fā)出更多的工具和庫來彌補(bǔ)其功能上的短板,如更完善的 polyfill 來增強(qiáng)舊瀏覽器的支持,或者提供更方便的超時控制和請求進(jìn)度監(jiān)測的解決方案。在未來的前端開發(fā)中,F(xiàn)etch 請求有望繼續(xù)發(fā)揮重要作用,并與其他技術(shù)(如 Service Workers、WebSockets 等)更好地結(jié)合,為用戶帶來更加高效、流暢和功能豐富的 web 應(yīng)用體驗(yàn)。希望讀者們能夠在實(shí)際項(xiàng)目中積極運(yùn)用 Fetch 請求,不斷探索和實(shí)踐,提升自己的開發(fā)效率與應(yīng)用的用戶體驗(yàn)。