大厂面试必刷:从手写 Ajax 到优雅的 Sleep 函数
前言:在前端面试中,异步编程(Asynchronous Programming)永远是“王炸”级别的考点。
很多同学只会用 fetch 或 axios,但一旦面试官要求 “用 Promise 封装一个原生的 Ajax” 或 “实现一个 sleep 函数” ,往往就会卡壳。这篇文章不讲废话,直接上干货,带你 3 分钟搞定这两个高频手写题。
为什么有了 Fetch 还要懂 Ajax?
在 ES6 之前,我们处理异步请求主要靠 XMLHttpRequest (Ajax)。那时候的代码充满了回调地狱(Callback Hell),逻辑极其难维护。
fetch 的出现改变了这一切,它基于 Promise 实现,天生支持链式调用,代码清晰优雅:
JavaScript
// Fetch: 优雅的链式调用
fetch('https://api.github.com/users/shunwuyu')
.then(res => res.json()) // 数据流转
.then(data => console.log(data))
.catch(err => console.log(err));
面试官的考点在于:你是否理解 fetch 内部是如何工作的?你能不能利用 Promise 将传统的基于回调的 API(如 XMLHttpRequest)改造成现代的 Promise API?
核心实战 1:手写 getJSON (Promise 封装 Ajax)
这是面试中出现频率极高的题目。核心在于理解 Promise 的 Executor(执行器) 机制。
实现思路
- new Promise(executor) 时,executor 函数是同步执行的。
- 我们将耗时的异步任务(Ajax 请求)放入 executor 中。
- 利用 onreadystatechange 监听状态。
- 当请求成功拿到数据时,调用 resolve() 改变 Promise 状态为 Fulfilled。
- 当请求失败时,调用 reject() 改变 Promise 状态为 Rejected。
代码实现
JavaScript
const getJSON = url => {
// 返回一个 Promise 实例
return new Promise((resolve, reject) => {
// Executor 执行器函数是同步运行的
// 在这里启动异步任务
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true); // true 代表异步
xhr.send();
// 监听状态变化
xhr.onreadystatechange = function() {
// readyState 4 代表下载完成
// status 200 代表请求成功
if (xhr.readyState === 4 && xhr.status === 200) {
try {
// 处理返回的 JSON 数据
const data = JSON.parse(xhr.responseText);
// 核心:任务成功,触发 resolve,将状态推向 fulfilled
resolve(data);
} catch (e) {
reject('JSON解析失败');
}
}
};
// 监听网络错误
xhr.onerror = function() {
// 核心:任务失败,触发 reject,将状态推向 rejected
reject(new Error('网络请求出错'));
};
});
};
// 使用演示
getJSON('https://api.github.com/users/shunwuyu')
.then(data => {
console.log('获取成功:', data);
})
.catch(err => {
console.error('获取失败:', err);
});
核心实战 2:手写 Sleep 函数
在 Java 等语言中,暂停程序只需 Thread.sleep(1000)。但在 JS 单线程模型中,我们通常使用 setTimeout。
如何让 JS 也能像其他语言一样“睡一会儿”再执行下一步?答案就是 Promise + setTimeout。
版本一:原理演示版 (便于理解)
JavaScript
function sleep(n) {
return new Promise((resolve, reject) => {
// 设定定时器
setTimeout(() => {
// n 毫秒后,将 Promise 状态改为成功
// 此时 .then() 中的回调才会被执行
resolve();
}, n);
});
}
// 调用
sleep(3000).then(() => {
console.log('睡了3秒后醒来,执行业务逻辑 ///');
});
版本二:极简一行代码版 (面试加分项)
如果面试官让你手写,这一行代码能体现你对 ES6 箭头函数的娴熟运用:
codeJavaScript
// 极简写法:直接利用隐式返回
const sleep = n => new Promise(resolve => setTimeout(resolve, n));
// 配合 async/await 食用更佳
(async () => {
console.log('开始');
await sleep(3000); // 暂停 3 秒
console.log('3秒后继续执行'); // 111
})();
理论复习:Promise 的三种状态
在手写过程中,我们必须牢记 Promise 状态流转的铁律(基于 Promise/A+ 规范):
- Pending (等待态) : Promise 实例创建后的初始状态。
- Fulfilled (成功态) : 当调用 resolve() 时,状态由 Pending 变为 Fulfilled。
- Rejected (失败态) : 当调用 reject() 或执行出错时,状态由 Pending 变为 Rejected。
注意:状态流转是不可逆的。一旦从 Pending 变为 Fulfilled 或 Rejected,状态就固定了(Settled),无法再次改变。
总结
手写 getJSON 和 sleep 本质上考的不是 API 的背诵,而是你对 “异步变同步” 流程控制的理解。
- getJSON: 展示了如何生产一个 Promise(封装异步 IO)。
- sleep: 展示了如何利用 Promise 控制时间维度的流程。
掌握这两个 Demo,面试遇到异步编程手写题,心里就稳了一大半!
NEXT06