深入理解 JavaScript 异步编程:从 Ajax 到 Promise

在现代 Web 开发中,异步编程是不可或缺的核心概念。本文将通过几个实际的代码示例,带你深入理解 JavaScript 中的异步操作,从传统的 Ajax 到现代的 Promise 和 Fetch API。

1. 传统 Ajax 与现代 Fetch API

XMLHttpRequest:经典的异步请求方式

html 复制代码
<script>
    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://api.github.com/orgs/lemoncode/members', true);
    xhr.send();
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4 && xhr.status === 200) {
            const data = JSON.parse(xhr.responseText);
            console.log(data);
        }
    }
</script>

XMLHttpRequest 是传统的异步请求方式,基于回调函数实现。需要手动处理 readyState 和 status 状态码,代码相对复杂。

Fetch API:现代化的替代方案

html 复制代码
<script>
    fetch('https://api.github.com/orgs/lemoncode/members')
        .then(res => res.json())
        .then(data => {
            console.log(data);
        })
</script>

Fetch API 的优势:

  • 基于 Promise 实现,支持链式调用
  • 语法简洁,无需手动处理状态码
  • 更符合现代 JavaScript 编程风格

2. 封装基于 Promise 的 getJSON 函数

为了将传统的 Ajax 改造成 Promise 风格,我们可以封装一个 getJSON 函数:

javascript 复制代码
const getJSON = (url) => {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.send();
        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4) {
                if(xhr.status === 200) {
                    const data = JSON.parse(xhr.responseText);
                    resolve(data);
                } else {
                    reject(`HTTP错误: ${xhr.status}`);
                }
            }
        }
        xhr.onerror = function() {
            reject('网络错误');
        }
    });
}

// 使用示例
getJSON('https://api.github.com/orgs/lemoncode/members')
    .then(data => {
        console.log(data);
    })
    .catch(err => {
        console.log(err);
    })

关键点说明:

  • Promise 构造函数接收一个执行器函数,同步执行
  • resolve() 将 Promise 状态改为 fulfilled,触发 then 回调
  • reject() 将 Promise 状态改为 rejected,触发 catch 回调
  • onerror 只能捕获网络错误,不能捕获 HTTP 错误状态码

3. Promise 状态管理

状态 (State) 含义 说明
pending (等待中) 初始状态 Promise 被创建后,尚未被兑现或拒绝时的状态
fulfilled (已成功) 操作成功完成 异步任务成功结束,调用了 resolve(value)
rejected (已失败) 操作失败 异步任务出错,调用了 reject(reason)

4. 实现 Sleep 函数:控制异步流程

在同步语言中,我们可以使用 sleep 函数暂停程序执行,但在 JavaScript 中需要借助 Promise 模拟这一行为:

javascript 复制代码
// 基础版本
function sleep(n) {
    let p;
    p = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(p); // pending 状态
            resolve(); // 或 reject()
            console.log(p); // fulfilled/rejected 状态
        }, n);
    });
    return p;
}

// 简化版本
const sleep = n => new Promise(resolve => setTimeout(resolve, n));

// 使用示例
sleep(3000)
    .then(() => {
        console.log('3秒后执行');
    })
    .catch(() => {
        console.log('error');
    })
    .finally(() => {
        console.log('finally'); // 无论成功失败都会执行
    });

setTimeout vs Sleep:本质区别

setTimeout - 事件调度

js 复制代码
console.log("第1步");
setTimeout(() => {
    console.log("第3步 - 在2秒后执行");
}, 2000);
console.log("第2步 - 不会等待setTimeout");

// 输出顺序:
// 第1步
// 第2步 - 不会等待setTimeout
// 第3步 - 在2秒后执行

Sleep - 流程控制

js 复制代码
async function demo() {
    console.log("第1步");
    await sleep(2000);  // 真正暂停函数的执行
    console.log("第2步 - 在2秒后执行");
}

demo();

// 输出顺序:
// 第1步
// (等待2秒)
// 第2步 - 在2秒后执行

这么来说吧:setTimeout是告诉 JavaScript 引擎:"等 X 毫秒后,帮我执行这个函数。" 它会去做别的同步操作,不会阻塞其他代码的执行。sleep则是在一个连续的异步操作流中,插入一段等待时间。sleep 让你写出"顺序等待"的逻辑,而 setTimeout 只是"未来某个时间点做某事"。

5. JavaScript 内存管理与数据拷贝

理解 JavaScript 的内存管理对于编写高效代码至关重要:

javascript 复制代码
const arr = [1, 2, 3, 4, 5, 6];

const arr2 = [].concat(arr); 
arr2[0] = 0;
console.log(arr, arr2); // arr 不变,arr2 改变

// 深拷贝 - 开销大
const arr3 = JSON.parse(JSON.stringify(arr));
arr3[0] = 100;
console.log(arr, arr3); // arr 不变,arr3 改变

在数组上进行存储的时候我们应该尽量规避JSON序列化的深拷贝,开销太大,我们可以选择用[].cancat(arr)这种巧妙的方法实现拷贝,虽然创建了一个新数组,但开销仍然小于序列化。

内存管理要点

  1. JS 变量在编译阶段分配内存空间
  2. 简单数据类型存储在栈内存中
  3. 复杂数据类型存储在堆内存中,栈内存存储引用地址
  4. 浅拷贝只复制引用,深拷贝创建完全独立的新对象

总结

通过本文的示例和解析,我们深入探讨了:

  1. Ajax 与 Fetch 的对比:Fetch API 提供了更简洁的 Promise-based 接口
  2. 手写getJson函数:将传统的 Ajax 改造成 现代的 Promise 风格
  3. 手写sleep函数:使用 Promise 实现类似同步的编程体验
  4. 内存管理:理解变量存储方式对编写高效代码的重要性

掌握这些概念将帮助你编写更清晰、更易维护的异步 JavaScript 代码,为学习更高级的 async/await 语法打下坚实基础。

相关推荐
kyriewen5 小时前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
山河木马6 小时前
矩阵专题2-怎么创建视图矩阵(uViewMatrix)
javascript·webgl·计算机图形学
tangdou3690986558 小时前
AI真好玩系列-2分钟快速了解DeepAgents | Quick Guide to DeepAgents in 2 Minutes
前端·javascript·后端
张元清8 小时前
React useIntersectionObserver Hook:懒加载与可见性检测(2026)
javascript·react.js
彭于晏爱编程8 小时前
纯 JS + Node,一个下午手搓了能读懂公司代码的 AI 助手,老板以为我转行了
前端·javascript
妙码生花9 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十四):眨眼小人登录页制作
前端·javascript·ai编程
妙码生花9 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十三):前端路由初始化
前端·javascript·ai编程
PBitW9 小时前
GPT训练我的第四天,被打惨了!!!😭😭😭
前端·javascript·面试
DarkLONGLOVE9 小时前
快速上手 Pinia!Vue3 极简状态管理使用教程
javascript·vue.js
mackbob9 小时前
.eslintrc.js详细配置说明
javascript