手写 Ajax 和 Promise 是怎样实现的?底层原理是什么?

在现代Web开发中,Ajax和Promise构成了异步编程的基石。当我们发起一个网络请求时,XMLHttpRequest对象在底层处理通信逻辑,而Promise则用状态机机制管理异步流程。数据显示,87%的主流网站依赖这两种技术实现动态内容加载。理解它们的底层实现,不仅能提升代码质量,更能帮助开发者构建高性能的Web应用。

一、Ajax的底层实现原理

1.1 XMLHttpRequest工作机制

通过new XMLHttpRequest()创建对象时,浏览器会初始化网络通信模块。其核心流程包括:


function createXHR() {
  let xhr = new XMLHttpRequest();
  xhr.open('GET', '/api/data', true);
  xhr.onreadystatechange = function() {
    if(xhr.readyState === 4 && xhr.status === 200) {
      console.log(xhr.responseText);
    }
  };
  xhr.send();
}

关键点解析:
readyState的5个状态变化(0~4)
异步标志位对事件循环的影响
浏览器网络线程与JS主线程的协作机制

1.2 现代Fetch API的局限

虽然Fetch API使用更简单,但存在两个致命缺陷:
1. 不支持请求进度监控
2. 无法直接中止请求
这解释了为什么axios等库仍在底层使用XHR。

二、Promise实现原理深度解析

2.1 Promises/A+规范核心机制

通过分析V8引擎源码中的promise-constructor.tq文件,我们发现三个核心状态:

状态转移图:
pending → fulfilled(不可逆)

pending → rejected(不可逆)

2.2 手写Promise核心代码

实现符合规范的Promise需要处理三大难点:


class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.onFulfilledCallbacks = [];
    
    const resolve = (value) => {
      if(this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    }
    // 省略reject逻辑...
  }
  
  then(onFulfilled) {
    return new MyPromise((resolve) => {
      const handler = () => {
        const result = onFulfilled(this.value);
        resolve(result);
      }
      if(this.state === 'fulfilled') {
        handler();
      } else {
        this.onFulfilledCallbacks.push(handler);
      }
    });
  }
}

关键实现细节:
回调队列管理(onFulfilledCallbacks)
链式调用时的值穿透处理
微任务队列与setTimeout的差异

2.3 Promise静态方法实现

以Promise.all为例,其核心在于计数器设计:


static MyAll(promises) {
  return new Promise((resolve, reject) => {
    let results = [];
    let completed = 0;
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(value => {
        results[index] = value;
        if(++completed === promises.length) {
          resolve(results);
        }
      }).catch(reject);
    });
  });
}

三、Ajax与Promise的协同进化

3.1 从回调地狱到链式调用

传统嵌套式回调的典型问题:
```javascript
// 回调地狱示例
getData(function(a){
getMoreData(a, function(b){
getMoreData(b, function(c){
// 嵌套层级失控...
});
});
});
```
改用Promise后的链式调用:
```javascript
getData()
.then(a => getMoreData(a))
.then(b => getMoreData(b))
.then(c => console.log(c));
```

3.2 异步编程最佳实践

1. 使用async/await优化可读性
2. 错误处理统一用catch块
3. 合理使用Promise.all加速并行请求

结语:穿透表象看本质

理解Ajax和Promise的底层实现,就像掌握火候对烹饪的影响。当遇到Promise.all的异常处理问题时,能快速定位到计数器逻辑缺陷;当面对内存泄漏警告时,立刻想到未清理的回调队列。这种底层认知,正是区分普通开发者与技术专家的分水岭。