在前端开发中,异步请求 和Promise是绕不开的核心知识点。无论是获取数据、提交表单,还是处理复杂的业务逻辑,我们都需要与异步操作打交道。本文将通过手写 Ajax 请求和解析 Promise 的底层原理,结合生活中的实际案例,带你深入理解这些技术的本质。
一、Ajax 的本质:异步通信的基石
1.1 什么是 Ajax?
Ajax(Asynchronous JavaScript and XML)是一种通过 JavaScript 与服务器进行异步通信的技术。它允许页面在不刷新的情况下动态更新数据,极大提升了用户体验。
生活类比 :
想象你在点外卖。你下单后,不需要一直盯着手机等外卖送到,而是继续做其他事情。外卖送达时,系统会通知你。这个"异步等待"的过程,就是 Ajax 的核心思想。
1.2 手写 Ajax 请求
步骤一:创建 XMLHttpRequest 对象
javascript
function createAjaxRequest() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest(); // 现代浏览器
} else {
return new ActiveXObject("Microsoft.XMLHTTP"); // 兼容 IE6-IE11
}
}
步骤二:发送 GET 请求
javascript
function getWeather(city) {
const xhr = createAjaxRequest();
xhr.open("GET", `https://api.example.com/weather?city=${city}`, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("成功获取天气数据:", JSON.parse(xhr.responseText));
} else if (xhr.readyState === 4) {
console.error("请求失败,状态码:", xhr.status);
}
};
xhr.send();
}
// 调用示例
getWeather("北京");
代码解析:
open()
:初始化请求,指定方法(GET/POST)、URL 和是否异步。onreadystatechange
:监听请求状态变化。send()
:发送请求。
1.3 手写 Ajax 的痛点
上述代码虽然能工作,但存在以下问题:
- 重复代码:每次请求都要手动处理状态判断和错误处理。
- 回调地狱:多个异步操作嵌套会导致代码难以维护。
- 缺乏统一接口:不同浏览器的兼容性处理复杂。
生活类比 :
这就像每次点外卖都要自己跑厨房、打包、配送。效率低且容易出错。
二、Promise 的底层原理:优雅处理异步的"乐高积木"
2.1 Promise 的核心思想
Promise 是一种异步编程的容器,它将异步操作的结果(成功或失败)封装成一个对象,通过链式调用和统一的接口管理异步流程。
生活类比 :
Promise 像是一张"承诺书"。你告诉服务器:"我需要数据",服务器承诺在某个时间点给你结果。无论成功还是失败,你都可以通过
.then()
或.catch()
处理。
2.2 手写 Promise 的核心逻辑
步骤一:定义 Promise 的状态
javascript
class MyPromise {
constructor(executor) {
this.status = "pending"; // 初始状态
this.value = undefined; // 成功值
this.reason = undefined; // 失败原因
this.onFulfilledCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
const resolve = (value) => {
if (this.status === "pending") {
this.status = "fulfilled";
this.value = value;
// 触发所有成功回调
this.onFulfilledCallbacks.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.status === "pending") {
this.status = "rejected";
this.reason = reason;
// 触发所有失败回调
this.onRejectedCallbacks.forEach((fn) => fn());
}
};
try {
executor(resolve, reject); // 执行用户传入的函数
} catch (error) {
reject(error); // 捕获同步错误
}
}
then(onFulfilled, onRejected) {
if (this.status === "fulfilled") {
onFulfilled(this.value);
} else if (this.status === "rejected") {
onRejected(this.reason);
} else {
// 异步情况下,先将回调存入队列
this.onFulfilledCallbacks.push(() => onFulfilled(this.value));
this.onRejectedCallbacks.push(() => onRejected(this.reason));
}
}
}
代码解析:
- 状态管理 :Promise 有三种状态(
pending
、fulfilled
、rejected
),状态一旦改变不可逆。 - 回调队列 :当 Promise 处于
pending
状态时,先将回调函数暂存,等到状态改变后再执行。 - 错误处理 :通过
try-catch
捕获同步错误,避免程序崩溃。
2.3 手写 Promise 的优化:链式调用
javascript
class MyPromise {
// ... 上述代码省略 ...
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === "fulfilled") {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x); // 返回新 Promise
} catch (e) {
reject(e);
}
}, 0);
} else if (this.status === "rejected") {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
}, 0);
} else {
// 异步情况下,先将回调存入队列
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
}
代码解析:
- 链式调用 :每个
.then()
返回一个新的 Promise 实例,实现链式调用。 - 微任务队列 :使用
setTimeout(fn, 0)
将回调放入微任务队列,确保异步执行顺序正确。
三、结合 Ajax 的 Promise 封装
3.1 用 Promise 封装 Ajax 请求
javascript
function getWeather(city) {
return new MyPromise((resolve, reject) => {
const xhr = createAjaxRequest();
xhr.open("GET", `https://api.example.com/weather?city=${city}`, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(`请求失败,状态码: ${xhr.status}`);
}
}
};
xhr.send();
});
}
// 使用示例
getWeather("北京")
.then((data) => {
console.log("成功获取天气数据:", data);
return getWeather("上海"); // 链式调用
})
.then((data) => {
console.log("成功获取上海天气数据:", data);
})
.catch((error) => {
console.error("请求失败:", error);
});
代码解析:
- 封装逻辑:将 Ajax 请求封装为 Promise,统一处理成功和失败。
- 链式调用 :通过
.then()
实现多个异步操作的串联。 - 错误捕获 :通过
.catch()
统一处理所有错误,避免嵌套回调。
3.2 生活类比:从"点外卖"到"智能配送"
- 未使用 Promise:每次点外卖都要手动查看配送状态,代码嵌套复杂。
- 使用 Promise:系统自动通知你配送结果,你可以专注于其他事情,无需频繁检查。
四、Promise 的底层原理详解
4.1 Promise 的状态管理
状态 | 描述 |
---|---|
pending |
初始状态,既不是成功也不是失败 |
fulfilled |
操作成功完成 |
rejected |
操作失败 |
关键点:状态一旦改变,不可逆;Promise 只能有一个最终结果。
4.2 微任务队列与事件循环
Promise 的 .then()
和 .catch()
方法会在微任务队列中执行,优先级高于宏任务(如 setTimeout
)。这是 Promise 实现异步流程控制的关键。
javascript
console.log("Start"); // 同步代码
new MyPromise((resolve) => {
console.log("Promise executor"); // 同步代码
resolve();
}).then(() => {
console.log("Promise then"); // 微任务
});
setTimeout(() => {
console.log("Timeout"); // 宏任务
}, 0);
console.log("End"); // 同步代码
// 输出顺序:
// Start
// Promise executor
// End
// Promise then
// Timeout
生活类比:
- 同步代码:像排队点餐,必须等前面的人处理完才能轮到你。
- 微任务:像餐厅的快速通道,优先处理。
- 宏任务:像普通排队,需等待其他任务完成。
4.3 链式调用的实现原理
每个 .then()
返回一个新的 Promise 实例,形成链式结构。通过递归调用 then
,可以实现异步操作的串联。
javascript
new MyPromise((resolve) => {
resolve(1);
})
.then((value) => {
console.log(value); // 1
return value + 1;
})
.then((value) => {
console.log(value); // 2
return value + 1;
})
.then((value) => {
console.log(value); // 3
});
五、总结:从"手写"到"理解"
5.1 核心收获
- Ajax 的本质 :通过
XMLHttpRequest
实现异步通信,但存在代码冗余和回调地狱的问题。 - Promise 的优势:通过状态管理和链式调用,简化异步代码,提升可维护性。
- 底层原理:Promise 通过微任务队列和回调队列管理异步流程,状态不可逆且只能有一个结果。
5.2 实际应用建议
- 封装 Ajax:将重复逻辑抽离为 Promise,提高代码复用性。
- 避免回调地狱 :使用
.then()
和.catch()
替代嵌套回调。 - 理解事件循环:掌握微任务和宏任务的执行顺序,避免异步逻辑错误。
六、结语
手写 Ajax 和 Promise 不仅是面试高频考点,更是深入理解前端异步编程的关键。通过本文的实践和解析,希望你能真正掌握这些技术的核心思想,并在实际项目中灵活运用。如果觉得内容对你有帮助,欢迎点赞、收藏,或分享给更多开发者!