大厂面试必考:从手写 Ajax 到封装 getJSON,再到理解 Promise 与 sleep
在前端工程师的求职过程中,尤其是冲击一线大厂(如阿里、腾讯、字节等)时,手写代码题几乎是绕不开的一环。这些题目看似基础,实则考察候选人对 JavaScript 核心机制的理解深度------包括异步编程、事件循环、内存模型以及浏览器原生 API 的掌握程度。
本文将焦三个经典手写题:
- 手写原生 Ajax
- 封装支持 Promise 的 getJSON 函数
- 手写 sleep 函数
我们将逐层深入,不仅写出代码,更要讲清楚"为什么这么写",帮助你在面试中不仅能写出来,还能讲明白。
一、手写 Ajax:回调地狱的起点
虽然现代开发中我们早已习惯使用 fetch 或 axios,但 Ajax 是所有网络请求的基石。面试官让你手写 Ajax,不是为了让你重复造轮子,而是检验你是否真正理解 HTTP 请求在浏览器中的实现方式。 ajax 基于回调函数实现,代码复杂,这正是其痛点所在。
手写一个基础版 Ajax
js
function ajax(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true); // 异步请求
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { // 请求完成
if (xhr.status >= 200 && xhr.status < 300) {
// 成功:调用回调并传入响应数据
callback(null, JSON.parse(xhr.responseText));
} else {
// 失败:传入错误
callback(new Error(`HTTP ${xhr.status}`), null);
}
}
};
xhr.send();
}
问题在哪?
- 强依赖回调函数 :调用方必须传入
callback,无法链式操作; - 错误处理分散:成功和失败逻辑耦合在同一个函数里;
- 无法组合多个异步操作:比如"先请求 A,再根据 A 的结果请求 B",代码会迅速变得嵌套混乱------即所谓的"回调地狱"。
这正是为什么我们需要 Promise。
二、封装 getJSON:用 Promise 改造 Ajax
"如何封装一个 getJSON 函数。使用 ajax,支持 Promise,get 请求方法,返回是 JSON"
这其实是一个典型的"将传统回调式 API 转为 Promise 化"的过程。
封装思路
- 创建一个返回
Promise的函数; - 在
Promise构造函数内部执行 Ajax; - 成功时调用
resolve(data),失败时调用reject(error); - 自动解析 JSON 响应体。
实现代码
js
function getJSON(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const data = JSON.parse(xhr.responseText);
resolve(data);
} catch (e) {
reject(new Error('Invalid JSON response'));
}
} else {
reject(new Error(`Request failed with status ${xhr.status}`));
}
};
xhr.onerror = function () {
reject(new Error('Network error'));
};
xhr.send();
});
}
使用方式(对比 fetch)
js
// 使用我们封装的 getJSON
getJSON('/api/user')
.then(user => console.log(user))
.catch(err => console.error('Failed:', err));
// 等价于 fetch 写法(但 fetch 不自动抛出 HTTP 错误)
fetch('/api/user')
.then(res => {
if (!res.ok) throw new Error('HTTP error');
return res.json();
})
.then(user => console.log(user))
.catch(err => console.error(err));
为什么 Promise 更好?
"fetch 简单易用,基于 Promise 实现,(then)无需回调函数"
Promise 的核心优势在于:
- 状态机模型 :初始为
pending,只能变为fulfilled(通过resolve)或rejected(通过reject),且状态不可逆; - 链式调用 :
.then().catch()形成清晰的流程控制; - 统一错误处理 :任意环节出错,都会被最近的
.catch捕获。
这使得异步代码更接近同步逻辑的阅读体验。
三、深入 Promise:不只是语法糖
"promise 类 ,为异步变同步而(流程控制)实例化,事实标准。接收一个函数,函数有两个参数,resolve reject,他们也是函数。"
new Promise(executor)中的executor是一个立即执行的函数;- 它接收两个参数:
resolve和reject,都是由 Promise 内部提供的函数; - 调用
resolve(value)会将 Promise 状态转为fulfilled,并将value传递给下一个.then; - 调用
reject(reason)则转为rejected,触发.catch。
例如:
js
const p = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve('ok') : reject('fail');
}, 1000);
});
p.then(console.log).catch(console.error);
这种设计让开发者能主动控制异步结果的"成功"或"失败"路径,是构建可靠异步系统的基础。
四、手写 sleep:Promise
实现
js
function sleep(n){
let p;
p = new Promise ((resolve,reject)=>{
setTimeout(()=>{
// pending 等待
console.log(p);
//resolve();
reject();
// fulfilled 成功
console.log(p);
}
,n);
})
return p;
}
sleep(3000)
.then(()=>{
console.log('3s后执行');
})
.catch(()=>{
console.log('3s后执行失败');
})
// promise 状态改变 就会执行
.finally(()=>{
console.log('finally');
})
手写题的本质是理解机制
大厂面试之所以反复考察这些"老掉牙"的手写题,是因为它们像一面镜子,照出你对 JavaScript 运行机制的理解深度:
- Ajax → 浏览器网络 API + 回调模型;
- Promise 封装 → 异步流程控制范式升级;
- sleep → Promise 与定时器的创造性结合;
当你不仅能写出这些代码,还能清晰解释其背后的原理时,你就已经超越了大多数候选人。
记住:面试不是考你会不会用库,而是考你知不知道库为什么存在。