《大厂面试:从手写 Ajax 到封装 getJSON,再到理解 Promise 与 sleep》

大厂面试必考:从手写 Ajax 到封装 getJSON,再到理解 Promise 与 sleep

在前端工程师的求职过程中,尤其是冲击一线大厂(如阿里、腾讯、字节等)时,手写代码题几乎是绕不开的一环。这些题目看似基础,实则考察候选人对 JavaScript 核心机制的理解深度------包括异步编程、事件循环、内存模型以及浏览器原生 API 的掌握程度。

本文将焦三个经典手写题:

  1. 手写原生 Ajax
  2. 封装支持 Promise 的 getJSON 函数
  3. 手写 sleep 函数

我们将逐层深入,不仅写出代码,更要讲清楚"为什么这么写",帮助你在面试中不仅能写出来,还能讲明白。


一、手写 Ajax:回调地狱的起点

虽然现代开发中我们早已习惯使用 fetchaxios,但 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 是一个立即执行的函数;
  • 它接收两个参数:resolvereject,都是由 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 与定时器的创造性结合;

当你不仅能写出这些代码,还能清晰解释其背后的原理时,你就已经超越了大多数候选人。

记住:面试不是考你会不会用库,而是考你知不知道库为什么存在。

相关推荐
Lee川1 天前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川1 天前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i1 天前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有1 天前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有1 天前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫1 天前
Looper.loop() 循环机制
面试
AAA梅狸猫1 天前
Handler基本概念
面试
Wect1 天前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼1 天前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼1 天前
Next.js 企业级落地
前端·javascript·面试