XMLHttpRequest 不该直接裸用,因其易致回调地狱、错误处理分散、不支持 async/await,且默认不拒绝 404/500 等错误状态码,需手动判断 status;推荐封装为 Promise,监听 onload/onerror,避免 onreadystatechange,设置 responseType、timeout 和 AbortController 统一取消逻辑。

为什么 XMLHttpRequest 不该直接裸用
直接写 XMLHttpRequest 容易陷入回调地狱,错误处理分散,且无法用 await、Promise.all 等现代语法。更关键的是,它默认不 reject 错误状态码(比如 404、500),需要手动判断 status,一不留神就让失败请求“静默成功”。
最简可行的 Promise 封装函数
核心就三件事:实例化、监听 load 和 error、根据状态码决定 resolve 还是 reject。别用 onreadystatechange——它触发多次,容易误判;load 更干净。
示例封装:
function request(url, options = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', url);
if (options.headers) {
Object.entries(options.headers).forEach(([k, v]) => xhr.setRequestHeader(k, v));
}
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status reject(new Error('Network error'));
xhr.send(options.body);
});
}
用法:await request('/api/user') 或 request('/api/data').then(...)。
常见坑:responseType、timeout 和取消请求
XMLHttpRequest 的 responseType 默认是 ''(即字符串),但后端返回 JSON 时,你得手动 JSON.parse();设成 'json' 更安全,不过注意 IE11 不支持,若需兼容得降级处理。
超时和取消必须显式设置:
-
xhr.timeout = 5000后监听ontimeout并reject - 取消请求靠
xhr.abort(),但 Promise 本身不可取消——所以实际中建议配合AbortController(见下条)
另外,responseType: 'json' 在出错时可能让 xhr.response 为 null,别直接解构,先判空。
进阶:用 AbortController 替代原生 abort,统一取消逻辑
现代浏览器已支持 AbortController,它能和 fetch 原生协同,也能桥接到 XMLHttpRequest。虽然 XMLHttpRequest 不原生认 signal,但你可以监听 signal.aborted 后主动 xhr.abort()。
关键点:
- 在 Promise 内部监听
signal.addEventListener('abort', ...) - abort 后必须调用
xhr.abort(),否则连接还挂着 - abort 不会自动 reject Promise,得手动
reject(new DOMException('Aborted', 'AbortError'))
这步看似绕,但能让取消行为和 fetch 保持一致,后续迁移到 fetch 时改动最小。
真正难的不是写这个 Promise,而是把散落在各处的 onload、onerror、setTimeout 超时逻辑、重复的 header 设置,全收束到一个可控入口里——漏掉任意一环,都可能让错误流进 UI 层。










