前言
在撰寫前/後端服務的時候,通常會很自然的使用axios
或者是fetch
來發起api請求,但其中的差異是如何?
Axios
無論在前端還是後端服務,都可以直接使用axios
來發起請求,這是因為axios本身對瀏覽器和Node環境各自做了處理與封裝,然後透過單一接口暴露出去。在瀏覽器端,axios是使用XMLHttpRequest(XHR)
來處理請求、而在Node環境下使用http/https
library。以下是基於axios原始碼的簡化版demo
瀏覽器端的處理—
xhrAdapter
函數,返回一個基於XMLHttpRequest
的Promiseexport async function xhrAdapter(config) { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open(config.method.toUpperCase(), config.url, true); Object.keys(config.headers).forEach((key) => { request.setRequestHeader(key, config.headers[key]); }); request.onload = function () { const response = { data: JSON.parse(request.responseText), status: request.status, statusText: request.getAllResponseHeaders(), config: config, request: request, }; resolve(response); }; request.onerror = function () { reject(new Error("Network Error")); }; request.ontimeout = function () { reject(new Error(`Timeout of ${config.timeout} ms exceeded`)); }; request.send(config.data); }); }
Node環境端處理—
httpAdpater
函數,返回一個基於http
或者是https
的Promiseimport http from "http"; import https from "https"; export async function httpAdpater(config) { return new Promise((resolve, reject) => { const url = new URL(config.url); const isHttps = url.protocol === "https"; const options = { method: config.method, headers: config.headers, }; const request = (isHttps ? https : http).request( url, options, (response) => { let data = ""; response.on("data", (chunk) => { data += chunk; }); response.on("end", () => { resolve({ data: JSON.parse(data), status: response.statusCode, statusText: response.statusMessage, headers: response.headers, config: config, request: response, }); }); }, ); request.on("error", (error) => { reject(error); }); if (config.data) { request.write(config.data); } request.end(); }); }
最後,在對全局的環境進行判別—假如為Browser環境,便返回
xhrAdapter
;如果是Node環境,則返回httpAdpater
。async function getDefaultAdapter() { if (typeof XMLHttpRequest !== "undefined") { // for browser, using XHR adapter const { xhrAdapter } = await import("./xhrAdapter.js"); return xhrAdapter; } else if (typeof process !== "undefined") { // for Node env, using HTTP Adpater const { httpAdpater } = await import("./httpAdpater.js"); return httpAdpater; } throw new Error("No suitable adapter found"); }
完整mock axios程式碼在 link
fetch
2015年之後,Browser 環境下推出了新的Fetch API,成為Web 應用程式的異步呼叫標準;然而,在 Node.js v21 之前,並沒有相對應的Fetch API,因此許多SDK會使用node-fetch
這個library來對Node環境添加Fetch API。
基本上,也是對全局環境的判別—如果是Browser環境,就直接用原生的fetch;如果是Node環境,就動態載入node-fetch
依賴(如果直接import的話,在browser環境會有編譯錯誤),然後將fetch掛載到全局
export async function initFetch() {
let fetch;
if (typeof window !== "undefined" && typeof window.fetch === "function") {
fetch = window.fetch;
} else {
const nodeFetch = await import("node-fetch");
fetch = nodeFetch.default;
}
if (typeof global !== "undefined") {
global.fetch = fetch;
} else if (typeof window !== "undefined") {
window.fetch = fetch;
}
}
然而,近期 node-fetch
更新版本成只支持 ESM ,所以在使用 commonjs 編譯的NodeJS專案中並沒辦法使用。這時,可以使用 http/https 依賴來替換—
import http from "http";
import https from "https";
import { URL } from "url";
function fetch(url, options = {}) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const isHttps = urlObj.protocol === "https:";
const { method = "GET", headers = {}, body } = options;
const requestOptions = {
method,
headers,
};
const request = (isHttps ? https : http).request(
urlObj,
requestOptions,
(response) => {
let data = "";
response.on("data", (chunk) => {
data += chunk;
});
response.on("end", () => {
const responseData = {
ok:
response.statusCode >= 200 &&
response.statusCode < 300,
status: response.statusCode,
statusText: response.statusMessage,
headers: response.headers,
url: response.url || url,
json: () => Promise.resolve(JSON.parse(data)),
text: () => Promise.resolve(data),
blob: () => Promise.resolve(Buffer.from(data)),
};
resolve(responseData);
});
},
);
request.on("error", (error) => {
reject(error);
});
if (body) {
request.write(body);
}
request.end();
});
}
export default fetch;
然後再將全局的fetch指向該 fetch 函數
- 完整mock fetch程式碼在 link
小結
許多第三方依賴、SDK等在涉及網路請求的時候,不太會用到Axios
,主要是考慮到減少其他的依賴、減少打包體積;同時,由於Node.js 原生的Fetch API是 v21後才有的,如果考慮到向前兼容的話,還是需要手動封裝一下http
/https
、或者直接使用node-fetch
。
完整程式碼在github js-fetch_vs_axios
Ref
ChangeLog
- 20240609-初稿