深入理解 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 语法打下坚实基础。

相关推荐
Heo3 小时前
简单聊聊webpack摇树的原理
前端·javascript·面试
Watermelo6173 小时前
href 和 src 有什么区别,它们对性能有什么影响?
前端·javascript·vue.js·性能优化·html·html5·用户体验
AAA阿giao3 小时前
大厂面试之反转字符串:深入解析与实战演练
前端·javascript·数据结构·面试·职场和发展·编程技巧
Lsx_4 小时前
🔍 React 有 useAntdTable,Vue3 怎么办?自封一个 useTable!
前端·javascript·vue.js
O***p6045 小时前
TypeScript类型守卫
前端·javascript·typescript
凡人程序员5 小时前
微前端qiankun接入的问题
前端·javascript
新晨4375 小时前
JavaScript map() 方法:从工具到编程哲学的升华
前端·javascript
醒了接着睡5 小时前
JS 对象深拷贝
javascript
码途进化论5 小时前
Vue3 + Vite 系统中 SVG 图标和 Element Plus 图标的整合实战
前端·javascript·vue.js