初學者嘗試封裝原生 `XMLHttpRequest` 實現通用 AJAX 函數 `asynSubmit`,期望函數直接返回 `responseText`。但因 `onreadystatechange` 是異步回調,**外層函數在請求發出後立即返回**,無法等待回調完成,導致返回 `undefined` 或拋出「無法獲取返回值」的困惑。
function asynSubmit(sData, action, method = "POST") {
var httpRequest = new XMLHttpRequest();
httpRequest.open(method, action);
httpRequest.setRequestHeader("content-type", "application/x-www-form-urlencoded");
httpRequest.send(sData);
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
return httpRequest.responseText; // ❌ 這是匿名函數的返回值,非 asynSubmit 的
}
}
};
// 函數這裡就結束了,沒有 return,調用者得到 undefined
}
function asynSubmitSync(sData, action, method = "POST") {
var httpRequest = new XMLHttpRequest();
httpRequest.open(method, action, false); // false = 同步
httpRequest.setRequestHeader("content-type", "application/x-www-form-urlencoded");
httpRequest.send(sData);
if (httpRequest.status === 200) return httpRequest.responseText;
throw new Error("HTTP " + httpRequest.status);
}
function asynSubmitCallback(sData, action, method, callback) {
var httpRequest = new XMLHttpRequest();
httpRequest.open(method, action, true); // 異步
httpRequest.setRequestHeader("content-type", "application/x-www-form-urlencoded");
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState !== 4) return;
if (httpRequest.status === 200) {
callback(null, httpRequest.responseText);
} else {
callback(new Error("HTTP " + httpRequest.status));
}
};
httpRequest.onerror = function() { callback(new Error("Network Error")); };
httpRequest.send(sData);
}
// 使用
asynSubmitCallback("id=1", "/api/user", "POST", function(err, data) {
if (err) return console.error(err);
console.log("成功:", data);
});
function asynSubmitPromise(sData, action, method = "POST") {
return new Promise(function(resolve, reject) {
var httpRequest = new XMLHttpRequest();
httpRequest.open(method, action, true);
httpRequest.setRequestHeader("content-type", "application/x-www-form-urlencoded");
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState !== 4) return;
if (httpRequest.status >= 200 && httpRequest.status < 300) {
resolve(httpRequest.responseText);
} else {
reject(new Error("HTTP " + httpRequest.status + ": " + httpRequest.statusText));
}
};
httpRequest.onerror = function() { reject(new Error("Network Error")); };
httpRequest.ontimeout = function() { reject(new Error("Timeout")); };
httpRequest.send(sData);
});
}
// 使用:鏈式 then/catch
asynSubmitPromise("id=1", "/api/user")
.then(data => console.log("成功:", data))
.catch(err => console.error("失敗:", err));
async function fetchUser(id) {
try {
const data = await asynSubmitPromise("id=" + id, "/api/user");
console.log("成功:", data);
return JSON.parse(data);
} catch (err) {
console.error("失敗:", err);
throw err;
}
}
// 調用
fetchUser(123);
async function fetchJson(url, options = {}) {
const resp = await fetch(url, {
method: options.method || 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', ...options.headers },
body: options.body,
credentials: 'include' // 攜帶 Cookie
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.text(); // 或 resp.json()
}
// 使用
try {
const text = await fetchJson('/api/user', { body: 'id=1' });
console.log(text);
} catch (e) {
console.error(e);
}
whatwg-fetch)。| 模式 | 返回值 | 錯誤處理 | 適用場景 |
|---|---|---|---|
| 同步 XHR | 直接返回 | try/catch | ❌ 生產環境禁用 |
| 回調函數 | 無(通過參數傳出) | 回調第一參數 | 舊項目、簡單場景 |
| Promise | Promise 對象 | .catch() | 現代標準、鏈式邏輯 |
| async/await | 解包後的值 | try/catch | 最佳可讀性、複雜流程 |
| Fetch API | Promise | .catch() | 新項目首選、功能最豐富 |
whatwg-fetch 兼容舊瀏覽器。AbortController (Fetch) 或 xhr.abort()。異步函數**無法**像同步函數那樣直接 `return` 異步操作的結果。必須通過**回調、Promise 或 async/await** 將結果「傳遞」給後續邏輯。現代 JavaScript 開發中,**`fetch` + `async/await` 是標準寫法**,既符合語言設計哲學,又提供最佳的可讀性與錯誤處理能力。理解「異步非阻塞」與「值傳遞機制」的區別,是從初學者進階為合格前端工程師的關鍵門檻。