《大厂面试题:手写 Promise 版 Ajax 与 Sleep》

4 阅读3分钟

大厂面试必刷:从手写 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(执行器)  机制。

实现思路

  1. new Promise(executor) 时,executor 函数是同步执行的。
  2. 我们将耗时的异步任务(Ajax 请求)放入 executor 中。
  3. 利用 onreadystatechange 监听状态。
  4. 当请求成功拿到数据时,调用 resolve() 改变 Promise 状态为 Fulfilled。
  5. 当请求失败时,调用 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+ 规范):

  1. Pending (等待态) : Promise 实例创建后的初始状态。
  2. Fulfilled (成功态) : 当调用 resolve() 时,状态由 Pending 变为 Fulfilled。
  3. Rejected (失败态) : 当调用 reject() 或执行出错时,状态由 Pending 变为 Rejected。

注意:状态流转是不可逆的。一旦从 Pending 变为 Fulfilled 或 Rejected,状态就固定了(Settled),无法再次改变。


总结

手写 getJSON 和 sleep 本质上考的不是 API 的背诵,而是你对  “异步变同步”  流程控制的理解。

  • getJSON: 展示了如何生产一个 Promise(封装异步 IO)。
  • sleep: 展示了如何利用 Promise 控制时间维度的流程。

掌握这两个 Demo,面试遇到异步编程手写题,心里就稳了一大半!

NEXT06