手写 Ajax 与 Promise:从底层原理到实际应用

在前端开发中,异步请求Promise是绕不开的核心知识点。无论是获取数据、提交表单,还是处理复杂的业务逻辑,我们都需要与异步操作打交道。本文将通过手写 Ajax 请求和解析 Promise 的底层原理,结合生活中的实际案例,带你深入理解这些技术的本质。


一、Ajax 的本质:异步通信的基石

1.1 什么是 Ajax?

Ajax(Asynchronous JavaScript and XML)是一种通过 JavaScript 与服务器进行异步通信的技术。它允许页面在不刷新的情况下动态更新数据,极大提升了用户体验。

生活类比

想象你在点外卖。你下单后,不需要一直盯着手机等外卖送到,而是继续做其他事情。外卖送达时,系统会通知你。这个"异步等待"的过程,就是 Ajax 的核心思想。


1.2 手写 Ajax 请求

步骤一:创建 XMLHttpRequest 对象

javascript 复制代码
function createAjaxRequest() {
  if (window.XMLHttpRequest) {
    return new XMLHttpRequest(); // 现代浏览器
  } else {
    return new ActiveXObject("Microsoft.XMLHTTP"); // 兼容 IE6-IE11
  }
}

步骤二:发送 GET 请求

javascript 复制代码
function getWeather(city) {
  const xhr = createAjaxRequest();
  xhr.open("GET", `https://api.example.com/weather?city=${city}`, true);
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
      console.log("成功获取天气数据:", JSON.parse(xhr.responseText));
    } else if (xhr.readyState === 4) {
      console.error("请求失败,状态码:", xhr.status);
    }
  };
  xhr.send();
}

// 调用示例
getWeather("北京");

代码解析

  • open():初始化请求,指定方法(GET/POST)、URL 和是否异步。
  • onreadystatechange:监听请求状态变化。
  • send():发送请求。

1.3 手写 Ajax 的痛点

上述代码虽然能工作,但存在以下问题:

  1. 重复代码:每次请求都要手动处理状态判断和错误处理。
  2. 回调地狱:多个异步操作嵌套会导致代码难以维护。
  3. 缺乏统一接口:不同浏览器的兼容性处理复杂。

生活类比

这就像每次点外卖都要自己跑厨房、打包、配送。效率低且容易出错。


二、Promise 的底层原理:优雅处理异步的"乐高积木"

2.1 Promise 的核心思想

Promise 是一种异步编程的容器,它将异步操作的结果(成功或失败)封装成一个对象,通过链式调用和统一的接口管理异步流程。

生活类比

Promise 像是一张"承诺书"。你告诉服务器:"我需要数据",服务器承诺在某个时间点给你结果。无论成功还是失败,你都可以通过 .then().catch() 处理。


2.2 手写 Promise 的核心逻辑

步骤一:定义 Promise 的状态

javascript 复制代码
class MyPromise {
  constructor(executor) {
    this.status = "pending"; // 初始状态
    this.value = undefined; // 成功值
    this.reason = undefined; // 失败原因
    this.onFulfilledCallbacks = []; // 成功回调队列
    this.onRejectedCallbacks = []; // 失败回调队列

    const resolve = (value) => {
      if (this.status === "pending") {
        this.status = "fulfilled";
        this.value = value;
        // 触发所有成功回调
        this.onFulfilledCallbacks.forEach((fn) => fn());
      }
    };

    const reject = (reason) => {
      if (this.status === "pending") {
        this.status = "rejected";
        this.reason = reason;
        // 触发所有失败回调
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    try {
      executor(resolve, reject); // 执行用户传入的函数
    } catch (error) {
      reject(error); // 捕获同步错误
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === "fulfilled") {
      onFulfilled(this.value);
    } else if (this.status === "rejected") {
      onRejected(this.reason);
    } else {
      // 异步情况下,先将回调存入队列
      this.onFulfilledCallbacks.push(() => onFulfilled(this.value));
      this.onRejectedCallbacks.push(() => onRejected(this.reason));
    }
  }
}

代码解析

  • 状态管理 :Promise 有三种状态(pendingfulfilledrejected),状态一旦改变不可逆。
  • 回调队列 :当 Promise 处于 pending 状态时,先将回调函数暂存,等到状态改变后再执行。
  • 错误处理 :通过 try-catch 捕获同步错误,避免程序崩溃。

2.3 手写 Promise 的优化:链式调用

javascript 复制代码
class MyPromise {
  // ... 上述代码省略 ...

  then(onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === "fulfilled") {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolve(x); // 返回新 Promise
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else if (this.status === "rejected") {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolve(x);
          } catch (e) {
            reject(e);
          }
        }, 0);
      } else {
        // 异步情况下,先将回调存入队列
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolve(x);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolve(x);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    return promise2;
  }
}

代码解析

  • 链式调用 :每个 .then() 返回一个新的 Promise 实例,实现链式调用。
  • 微任务队列 :使用 setTimeout(fn, 0) 将回调放入微任务队列,确保异步执行顺序正确。

三、结合 Ajax 的 Promise 封装

3.1 用 Promise 封装 Ajax 请求

javascript 复制代码
function getWeather(city) {
  return new MyPromise((resolve, reject) => {
    const xhr = createAjaxRequest();
    xhr.open("GET", `https://api.example.com/weather?city=${city}`, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(JSON.parse(xhr.responseText));
        } else {
          reject(`请求失败,状态码: ${xhr.status}`);
        }
      }
    };
    xhr.send();
  });
}

// 使用示例
getWeather("北京")
  .then((data) => {
    console.log("成功获取天气数据:", data);
    return getWeather("上海"); // 链式调用
  })
  .then((data) => {
    console.log("成功获取上海天气数据:", data);
  })
  .catch((error) => {
    console.error("请求失败:", error);
  });

代码解析

  • 封装逻辑:将 Ajax 请求封装为 Promise,统一处理成功和失败。
  • 链式调用 :通过 .then() 实现多个异步操作的串联。
  • 错误捕获 :通过 .catch() 统一处理所有错误,避免嵌套回调。

3.2 生活类比:从"点外卖"到"智能配送"

  • 未使用 Promise:每次点外卖都要手动查看配送状态,代码嵌套复杂。
  • 使用 Promise:系统自动通知你配送结果,你可以专注于其他事情,无需频繁检查。

四、Promise 的底层原理详解

4.1 Promise 的状态管理

状态 描述
pending 初始状态,既不是成功也不是失败
fulfilled 操作成功完成
rejected 操作失败

关键点:状态一旦改变,不可逆;Promise 只能有一个最终结果。


4.2 微任务队列与事件循环

Promise 的 .then().catch() 方法会在微任务队列中执行,优先级高于宏任务(如 setTimeout)。这是 Promise 实现异步流程控制的关键。

javascript 复制代码
console.log("Start"); // 同步代码

new MyPromise((resolve) => {
  console.log("Promise executor"); // 同步代码
  resolve();
}).then(() => {
  console.log("Promise then"); // 微任务
});

setTimeout(() => {
  console.log("Timeout"); // 宏任务
}, 0);

console.log("End"); // 同步代码

// 输出顺序:
// Start
// Promise executor
// End
// Promise then
// Timeout

生活类比

  • 同步代码:像排队点餐,必须等前面的人处理完才能轮到你。
  • 微任务:像餐厅的快速通道,优先处理。
  • 宏任务:像普通排队,需等待其他任务完成。

4.3 链式调用的实现原理

每个 .then() 返回一个新的 Promise 实例,形成链式结构。通过递归调用 then,可以实现异步操作的串联。

javascript 复制代码
new MyPromise((resolve) => {
  resolve(1);
})
  .then((value) => {
    console.log(value); // 1
    return value + 1;
  })
  .then((value) => {
    console.log(value); // 2
    return value + 1;
  })
  .then((value) => {
    console.log(value); // 3
  });

五、总结:从"手写"到"理解"

5.1 核心收获

  1. Ajax 的本质 :通过 XMLHttpRequest 实现异步通信,但存在代码冗余和回调地狱的问题。
  2. Promise 的优势:通过状态管理和链式调用,简化异步代码,提升可维护性。
  3. 底层原理:Promise 通过微任务队列和回调队列管理异步流程,状态不可逆且只能有一个结果。

5.2 实际应用建议

  • 封装 Ajax:将重复逻辑抽离为 Promise,提高代码复用性。
  • 避免回调地狱 :使用 .then().catch() 替代嵌套回调。
  • 理解事件循环:掌握微任务和宏任务的执行顺序,避免异步逻辑错误。

六、结语

手写 Ajax 和 Promise 不仅是面试高频考点,更是深入理解前端异步编程的关键。通过本文的实践和解析,希望你能真正掌握这些技术的核心思想,并在实际项目中灵活运用。如果觉得内容对你有帮助,欢迎点赞、收藏,或分享给更多开发者!

相关推荐
鱼樱前端20 分钟前
今天介绍下最新更新的Vite7
前端·vue.js
coder_pig1 小时前
跟🤡杰哥一起学Flutter (三十四、玩转Flutter手势✋)
前端·flutter·harmonyos
万少1 小时前
01-自然壁纸实战教程-免费开放啦
前端
独立开阀者_FwtCoder1 小时前
【Augment】 Augment技巧之 Rewrite Prompt(重写提示) 有神奇的魔法
前端·javascript·github
yuki_uix1 小时前
AI辅助网页设计:从图片到代码的实践探索
前端
我想说一句1 小时前
事件机制与委托:从冒泡捕获到高效编程的奇妙之旅
前端·javascript
陈随易1 小时前
MoonBit助力前端开发,加密&性能两不误,斐波那契测试提高3-4倍
前端·后端·程序员
小飞悟1 小时前
你以为 React 的事件很简单?错了,它暗藏玄机!
前端·javascript·面试
中微子2 小时前
JavaScript 事件机制:捕获、冒泡与事件委托详解
前端·javascript
Whoisshutiao2 小时前
网安-XSS-pikachu
前端·安全·网络安全