首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

JavaScript 異步編程核心:XMLHttpRequest 封裝、回調地獄與 Promise/async-await 現代寫法

     編輯:程式設計疑難解答

問題場景

初學者嘗試封裝原生 `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
}

三種解決路徑對比

1. 同步請求(強烈不推薦)

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);
}
  • ❌ **阻塞主線程**,頁面假死,用戶體驗極差。
  • ❌ 主流瀏覽器主線程中已棄用/限制同步 XHR。
  • ❌ 無法處理超時、取消等高級場景。

2. 回調函數模式(傳統、兼容性好)

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);
});
  • ✅ 非阻塞,符合異步本質。
  • ⚠️ 多層嵌套導致「回調地獄」,錯誤處理分散。

3. Promise 封裝(現代標準,鏈式調用)

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

4. async/await(語法糖,同步寫法異步執行)

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

進階:Fetch API 替代 XHR(現代項目首選)

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);
}
  • 基於 Promise 原生實現,代碼更簡潔。
  • 支援流式讀取、Request/Response 對象、Service Worker 整合。
  • IE 需 Polyfill(如 whatwg-fetch)。

核心概念總結

模式返回值錯誤處理適用場景
同步 XHR直接返回try/catch❌ 生產環境禁用
回調函數無(通過參數傳出)回調第一參數舊項目、簡單場景
PromisePromise 對象.catch()現代標準、鏈式邏輯
async/await解包後的值try/catch最佳可讀性、複雜流程
Fetch APIPromise.catch()新項目首選、功能最豐富

最佳實踐建議

  1. 新項目直接用 Fetch + async/await,引入 whatwg-fetch 兼容舊瀏覽器。
  2. 舊項目維護:將 XHR 封裝為 Promise 版本,逐步遷移至 async/await。
  3. 統一錯誤處理:攔截器/封裝層統一處理 401/403/500、網絡異常、超時。
  4. 取消請求:使用 AbortController (Fetch) 或 xhr.abort()。
  5. 避免手寫 XHR:除非極特殊環境,否則無理由在 2024+ 年手寫原生 XHR 邏輯。

總結

異步函數**無法**像同步函數那樣直接 `return` 異步操作的結果。必須通過**回調、Promise 或 async/await** 將結果「傳遞」給後續邏輯。現代 JavaScript 開發中,**`fetch` + `async/await` 是標準寫法**,既符合語言設計哲學,又提供最佳的可讀性與錯誤處理能力。理解「異步非阻塞」與「值傳遞機制」的區別,是從初學者進階為合格前端工程師的關鍵門檻。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved