如何使用 JavaScript Fetch API 執行 HTTP 請求
如何使用 JavaScript fetch API 在瀏覽器和 Node.js 中執行 HTTP 請求。
開始使用
對於本教程,我們將使用 CheatCode Next.js 樣板來展示 02
的用法 在客戶端和 CheatCode Node.js Server Boilerplate 上展示 13
的用法 在服務器上。
首先,讓我們克隆 Next.js 樣板:
終端
git clone https://github.com/cheatcode/nextjs-boilerplate client
接下來,28
進入項目並安裝它的依賴項:
終端
cd client && npm install
之後,繼續啟動開發服務器:
終端
npm run dev
接下來,在另一個選項卡或終端窗口中,我們要克隆 Node.js 樣板:
終端
git clone https://github.com/cheatcode/nodejs-server-boilerplate server
接下來,36
進入項目並安裝依賴項:
終端
cd server && npm install
在我們啟動開發服務器之前,我們需要安裝兩個額外的依賴:45
和 54
:
終端
npm i isomorphic-fetch faker
安裝這兩個後,繼續啟動服務器:
終端
npm run dev
有了這個,我們就可以開始了。
在 Node.js 中使用 Fetch API
雖然看起來有點落後,但對於本教程,我們將從服務器端開始工作,然後轉到客戶端。原因是我們要設置一些我們可以執行的測試路線 68
針對客戶端的請求。在此期間,我們將快速了解如何使用 77
在 Node.js 服務器環境中。
/server/api/index.js
import graphql from "./graphql/server";
export default (app) => {
graphql(app);
app.get("/users", (req, res) => {
// We'll implement an HTTP GET test route here...
});
app.post("/users", (req, res) => {
// We'll implement an HTTP POST test route here...
});
app.get("/photos", (req, res) => {
// We'll implement a server-side fetch request here...
});
};
在我們上面克隆的 Node.js 樣板中,已經為我們配置了 Express.js 服務器。在上面的文件中,樣板文件設置了它支持的各種 API(默認情況下,只是一個 GraphQL API)。傳遞到從該文件導出的函數中的是 Express 81
在 96
中為我們設置的實例 項目中的文件。
在這裡,在我們設置 GraphQL 服務器 100
的函數調用下方 (我們不會使用這個,我們只是為了避免混淆而把它叫出來),我們定義了三個路由:
112
使用121
它創建了一個只接受 HTTP GET 請求的 Express.js 路由。133
使用144
它創建了一個只接受 HTTP POST 請求的 Express.js 路由。156
使用161
這是一個 Express.js 路由,它只接受 HTTP GET 請求,將是我們使用171
的地方 從第三方 API 獲取數據。
/server/api/index.js
import faker from "faker";
import graphql from "./graphql/server";
export default (app) => {
graphql(app);
app.get("/users", (req, res) => {
const users = [...Array(50)].map(() => {
return {
name: {
first: faker.name.firstName(),
last: faker.name.lastName(),
},
emailAddress: faker.internet.email(),
address: {
street: faker.address.streetAddress(),
city: faker.address.city(),
state: faker.address.state(),
zip: faker.address.zipCode(),
},
};
});
res.status(200).send(JSON.stringify(users, null, 2));
});
app.post("/users", (req, res) => {
// We'll implement an HTTP POST test route here...
});
app.get("/photos", (req, res) => {
// We'll implement a server-side fetch request here...
});
};
添加 183
195
的頂部 我們之前安裝的依賴,這裡我們填寫204
我們的 219
版本 路線。在內部,我們的目標是返回一些測試數據(我們將執行 222
稍後從客戶端請求並期望此數據作為回報)。對於我們的數據,我們使用了一點 JavaScript 技巧。
236
我們在這裡映射的意思是“在內存中創建一個新的 JavaScript 數組,其中包含 50 個元素(這些將只是 240
值),然後使用 253
'傳播'或'解壓'該數組 展開操作符——放入包裹該語句的數組中。”我們的目標是獲得 50 個“佔位符”,我們可以使用 JavaScript 262
替換它們 方法。
我們看到這裡發生了這種情況,為 50 個佔位符元素中的每一個返回一個描述虛構用戶的對象。反過來,這將返回一個包含 50 個用戶對象的數組。為了“彌補”這些用戶,我們使用 270
庫——一個用於創建虛假測試數據的工具——為我們地圖的每次迭代創建一個真實的測試用戶(在此處了解有關 Faker 的 API 的更多信息)。
最後,在我們創建了 286
數組之後 ,我們採用該變量並使用 298
來自 Express.js 的對象(這是作為我們路由的回調函數的第二個參數傳遞的),並做兩件事:
- 設置HTTP狀態碼為
308
使用318
方法(這是“成功”的標準 HTTP 代碼)。 - 使用“鏈接”方法的能力,調用
329
設置336
後的方法 在345
,傳入我們的356
的字符串化版本 變量(包含我們的用戶數組)。
在這裡,使用 362
是必要的,因為只能發送字符串來響應 HTTP 請求。稍後,在客戶端,我們將學習如何將該字符串轉換回 JavaScript 數組。
/server/api/index.js
import faker from "faker";
import graphql from "./graphql/server";
export default (app) => {
graphql(app);
app.get("/users", (req, res) => {
...
res.status(200).send(JSON.stringify(users, null, 2));
});
app.post("/users", (req, res) => {
console.log(req.body);
res.status(200).send(`User created!`);
});
app.get("/photos", (req, res) => {
// We'll implement a server-side fetch request here...
});
};
接下來,對於 374
我們的 384
版本 路線,我們保持簡單。因為 HTTP POST 請求的目的是創建 或插入 一些數據到數據庫中(或將其交給另一個數據源),在這裡,我們只是註銷 397
的內容 這是通過請求發送給我們的解析內容。這將在稍後派上用場,因為我們將看到我們如何傳遞給 408
的選項 request 判斷我們傳遞給客戶端的 body 是否到達服務器。
最後,在這裡,我們重複我們在 419
中看到的相同模式 427
的版本 , 調用 432
,設置445
到 453
, 並返回一個字符串響應(這裡只是一個表示用戶收到的純字符串)。
/server/api/index.js
import faker from "faker";
import fetch from "isomorphic-fetch";
import graphql from "./graphql/server";
export default (app) => {
graphql(app);
app.get("/users", (req, res) => {
...
res.status(200).send(JSON.stringify(users, null, 2));
});
app.post("/users", (req, res) => {
console.log(req.body);
res.status(200).send(`User created!`);
});
app.get("/photos", (req, res) => {
fetch("https://jsonplaceholder.typicode.com/photos").then(
async (response) => {
const data = await response.json();
res.status(200).send(JSON.stringify(data.slice(0, 50)));
}
);
});
};
對於我們的最終路線,我們創建另一個 460
路線,這次使用路線 477
.對於這條路線,我們將使用服務器端 487
調用第三方 API 並將我們獲取的數據發送回應用程序的客戶端。在頂部,您可以看到我們已經導入了 491
我們之前安裝的依賴項為 507
.
在這裡,我們調用 517
免費 JSON 佔位符 API 上的端點,該 API 向我們返回一個對像數組,其中包含指向股票照片的指針。
在我們調用 520
之後 ,我們鏈接在 532
回調——這表示我們期望 546
返回一個 JavaScript Promise——將一個函數傳遞給那個 559
方法。在該函數內部,我們採用 566
作為參數添加到我們的請求中,同時添加一個 574
我們函數之前的關鍵字。
我們這樣做是因為在下一行,我們調用 589
在調用 593
之前 .這裡的想法是 603
不是由 618
交給我們的 以任何特定格式。相反,我們採用原始的 625
並在該 638
上使用幾種方法之一 對象,我們將響應轉換為我們想要/需要的格式。
這裡,645
是說要轉換 656
轉換成 JSON 格式。我們使用 661
這裡是因為我們期望 676
(以及它的兄弟方法,如 688
) 返回一個 JavaScript Promise。使用 697
,我們說“等到這個函數返回一個值,我們可以設置為我們的 701
變量然後繼續下一行。”
在下一行,我們看到了對 710
的熟悉調用 ,確保 725
我們的數據,然後將其發送回應用程序客戶端發出的請求。
這對服務器來說是這樣的!接下來,我們要跳到客戶端,看看739
如何 在瀏覽器中工作。
在瀏覽器中使用 Fetch API
進入我們之前克隆的 Next.js 樣板,首先,我們將使用 Next.js 的基於頁面的路由功能在客戶端上創建一個新路由,我們可以在其中測試我們的 744代碼> 來電:
/client/pages/index.js
import React, { useState } from "react";
const Index = () => {
const [data, setData] = useState([]);
const getRequestWithFetch = (resource = "") => {
// We'll make our GET requests using fetch here...
};
const postRequestWithFetch = () => {
// We'll make a our POST request using fetch here...
};
return (
<div>
<button
className="btn btn-primary"
style={{ marginRight: "10px" }}
onClick={() => getRequestWithFetch("users")}
>
GET Request (Users)
</button>
<button
className="btn btn-primary"
style={{ marginRight: "10px" }}
onClick={() => getRequestWithFetch("photos")}
>
GET Request (Photos)
</button>
<button className="btn btn-primary" onClick={postRequestWithFetch}>
POST Request
</button>
<pre style={{ background: "#eee", marginTop: "20px", padding: "20px" }}>
<code>{data}</code>
</pre>
</div>
);
};
export default Index;
在 Next.js 中,頁面(自動轉換為路由或 URL)是使用 React.js 組件定義的。在這裡,我們使用基於函數的方法在 React 中定義一個組件,該組件由一個普通的 JavaScript 函數組成,該函數返回一些 JSX 標記(為在 React 中編寫組件而構建的標記語言)。
在該函數的主體中,我們也可以定義其他函數並調用 React 獨有的一種特殊類型的函數,稱為鉤子。
從函數體內部開始,我們可以看到對這些鉤子函數之一的調用 752
(從頂部導入)這將允許我們設置一個動態狀態值,然後在我們的 JSX 標記中訪問該值以及在我們的函數組件主體中定義的其他函數(一個稱為“閉包函數”的概念,或者在函數中定義的函數在 JavaScript 中)。
這裡,761
正在說“創建狀態值的實例,將默認值設置為空數組 774
。”
對於該調用的返回值,我們期望返回一個包含兩個值的數組:第一個是當前值 787
第二個是我們可以用來更新的函數 該值 790
.在這裡,我們使用 JavaScript 數組解構來訪問數組的內容,同時將變量分配給數組中這些位置的值。
為了澄清這一點,如果我們將這一行寫成 804
,我們需要遵循這條線:
const data = state[0];
const setData = state[1];
使用數組解構,我們可以完全避免這種情況。
跳過我們的佔位符函數,接下來查看我們從 811
返回的 JSX 標記 組件函數(Next.js 將為我們的頁面呈現的內容),我們可以看到我們的實際 UI 非常簡單:我們正在呈現三個按鈕和一個 823
塊。
這裡的想法是我們的每個 838
都有一個按鈕 請求類型,後跟一個代碼塊,我們在其中呈現對每個請求的響應(由單擊按鈕觸發)。在這裡,我們可以看到 847
我們使用數組解構從我們對 852
的調用中“提取”的變量 被傳遞到 863
標籤嵌套在我們的 879
中 標籤。這是我們最終存儲來自 883
的響應數據的地方 請求(並在屏幕上查看該數據)。
查看每個按鈕,我們可以看到 894
屬性被賦值。對於前兩個按鈕——我們將負責執行我們的 906
請求示例——我們調用上面定義的函數913
,傳入一個描述我們想要調用的資源或路徑的字符串(這會更有意義)。
對於最後一個按鈕,我們只需傳遞函數 920
直接,因為我們在調用該函數時不需要傳遞任何參數。
/client/pages/index.js
import React, { useState } from "react";
const Index = () => {
const [data, setData] = useState([]);
const getRequestWithFetch = (resource = "") => {
fetch(`http://localhost:5001/${resource}`, {
credentials: "include",
}).then(async (response) => {
const data = await response.json();
// NOTE: Doing JSON.stringify here for presentation below. This is not required.
setData(JSON.stringify(data, null, 2));
});
};
const postRequestWithFetch = () => {
// We'll make a our POST request using fetch here...
};
return (
<div>
...
</div>
);
};
export default Index;
查看931
我們在下面提示的函數,我們可以看到我們為資源名稱傳遞的字符串被定義為參數 941
關於我們的功能。在該函數內部,我們設置了對 951
的調用 .您會注意到,與服務器不同的是,我們沒有導入 962
從任何地方。
這是因為 977
作為 global 內置於現代瀏覽器中 值(意味著它會在瀏覽器的任何地方自動定義)。
查看我們的調用,就像我們之前看到的那樣,我們調用 988
傳遞一個 URL 作為第一個參數。在這種情況下,我們傳遞 999
之一的 URL 我們之前在服務器上定義的路由。這將根據為 1005
傳遞的值動態變化 , 到 1018
或 1027
.
作為 1030
的第二個參數 ,我們傳遞一個選項對象。在這裡,我們只傳遞了一個屬性 1041
.正如我們在實現 POST 請求時將看到的,我們在此處傳遞的內容決定了我們的請求的實際行為方式。在這種情況下,我們告訴 1052
在發送請求時將瀏覽器的 cookie 包含在請求標頭中。雖然我們沒有在服務器上驗證我們的請求,但如果您期望 1060
,請務必注意這一點 表現得像一個瀏覽器(它會自動發送帶有自己請求的 cookie)。
最後,在這裡,在 1075
回調(記住,1088
將返回給我們一個 JavaScript Promise),我們使用 async/await 模式到 1096
以 JavaScript 友好的格式(數組或對象)獲取返回數據,然後調用 1103
我們從 1110
得到的函數 鉤子函數來設置響應數據以顯示在我們的 1120
標記。
/client/pages/index.js
import React, { useState } from "react";
const Index = () => {
const [data, setData] = useState([]);
const getRequestWithFetch = (resource = "") => {
...
};
const postRequestWithFetch = () => {
fetch(`http://localhost:5001/users`, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "test",
}),
}).then(async (response) => {
const data = await response.text();
setData(data);
});
};
return (
<div>
...
</div>
);
};
export default Index;
接下來,對於我們的 1134
函數,我們重複與我們的 GET 請求類似的過程。但是,在這裡,我們硬編碼了我們的 URL(我們在服務器上只有一個 POST 路由),並且因為我們正在執行一個不是 GET 的請求,所以我們設置了一個 1140
1151
的選項 .如果我們這樣做不 這樣做,1163
將假設我們正在嘗試執行 GET 請求或“獲取”一些數據。
在此下方,我們可以看到相同的 1173
作為我們的 GET 請求(再次,這裡純粹是為了提高意識)。接下來是重要的部分,因為這是一個POST請求,我們添加一個1189
選項設置為帶有一些測試數據的字符串化 JavaScript 對象。請記住,HTTP 請求只能來回傳遞字符串。為了使這項工作,在 1192
選項,我們添加 HTTP 1207
標頭,將其設置為 1217
.這個很重要。這會通知服務器,我們在正文中發送的數據應該被解析為 JSON 數據。
/server/middleware/bodyParser.js
import bodyParser from "body-parser";
export default (req, res, next) => {
const contentType = req.headers["content-type"];
if (contentType && contentType === "application/x-www-form-urlencoded") {
return bodyParser.urlencoded({ extended: true })(req, res, next);
}
return bodyParser.json()(req, res, next);
};
為了快速理解這一點,在我們應用程序的服務器端,我們使用的 Node.js 樣板文件有一個稱為 1222
的東西 每當請求進入服務器時運行的函數,就在它被移交給我們的 Express.js 路由之前。在這裡,我們可以在底部看到將HTTP請求體解析為JSON格式的中間件函數。
如果我們沒有 設置 1234
1246
中的標頭 請求返回客戶端,我們的請求正文 (1252
在我們服務器上的路由處理程序中)將是一個空對象。然而,一旦我們設置了這個標頭,響應我們請求的服務器就知道“做什麼”並按預期接收我們的請求正文。
/client/pages/index.js
import React, { useState } from "react";
const Index = () => {
const [data, setData] = useState([]);
const getRequestWithFetch = (resource = "") => {
...
};
const postRequestWithFetch = () => {
fetch(`http://localhost:5001/users`, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "test",
}),
}).then(async (response) => {
const data = await response.text();
setData(data);
});
};
return (
<div>
...
</div>
);
};
export default Index;
回到我們的 1265
客戶端上的函數,在 1271
回調,我們使用與之前看到的 async/await 類似的流程,但是這一次,而不是 1286
我們使用 1299
.這是因為我們從服務器發回的 POST 請求響應只是一個純字符串(與我們其他請求中的字符串化對象相反)。一旦我們得到我們的 1304
,我們將其彈出以聲明 1319
.
而已!現在我們準備試一試:
總結
在本教程中,我們學習瞭如何使用 JavaScript 1322
執行 HTTP 請求 API。我們從服務器開始,定義從客戶端發送請求的路由,同時學習如何使用 1330
通過 1344
Node.js 中的庫。接下來,在客戶端,我們學習瞭如何運行 HTTP GET 和 POST 請求,了解傳遞的正確選項以確保我們的服務器能夠理解我們的請求。