《大厂面试:从手写 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 与定时器的创造性结合;

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

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

相关推荐
yoke菜籽5 小时前
面试150——二叉树
面试·职场和发展
程序员小寒5 小时前
前端高频面试题之Vuex篇
前端·javascript·面试
程序员爱钓鱼6 小时前
Python 编程实战 · 实用工具与库 — Django 项目结构简介
后端·python·面试
许强0xq15 小时前
Q3: create 和 create2 有什么区别?
面试·web3·区块链·智能合约·solidity·dapp·evm
han_16 小时前
前端高频面试题之Vuex篇
前端·vue.js·面试
被瞧不起的神17 小时前
校招面经(一)入门篇
面试
hygge99919 小时前
JVM GC 垃圾回收体系完整讲解
java·开发语言·jvm·经验分享·面试
hygge99919 小时前
MySQL 全体系深度解析(存储引擎、事务、日志、MVCC、锁、索引、执行计划、复制、调优)
数据库·经验分享·mysql·adb·面试
拉不动的猪20 小时前
CSS 像素≠物理像素:0.5px 效果的核心密码是什么?
前端·css·面试