手写异步神器:从Ajax到Promise,再到优雅的Sleep函数

31 阅读3分钟

手写异步神器:从Ajax到Promise,再到优雅的Sleep函数

在Web开发中,异步操作是我们每天都要面对的核心问题。从最初的XMLHttpRequest到现代的Fetch API,再到Promise的优雅解决方案,JavaScript的异步编程方式发生了翻天覆地的变化。本文将带你深入理解异步编程的演进,并手把手教你封装实用的异步工具函数。

异步编程的演进:从Ajax到Fetch

传统的Ajax实现

在早期,我们主要使用XMLHttpRequest对象进行异步数据请求:

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.github.com/users/shunwuyu', true);
xhr.send();
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        const data = JSON.parse(xhr.responseText);
        console.log(data);
    }
}

这种方式虽然功能强大,但存在明显的缺点:

  • 代码冗余,需要手动处理readyState和status
  • 回调函数嵌套导致代码难以维护
  • 错误处理机制不够完善

现代的Fetch API

ES6引入的Fetch API让异步请求变得更加简洁:

fetch('https://api.github.com/users/shunwuyu')
    .then(res => res.json())
    .then(data => {
        console.log(data);
    });

Fetch的优势显而易见:

  • 基于Promise实现,支持链式调用
  • 语法简洁,易于理解和使用
  • 内置了对JSON数据的处理支持

手写Promise化的Ajax封装

虽然Fetch已经很优秀,但理解其底层原理对我们掌握异步编程至关重要。下面我们来封装一个支持Promise的getJSON函数:

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) {
                    try {
                        const data = JSON.parse(xhr.responseText);
                        resolve(data);
                    } catch (error) {
                        reject(new Error('JSON解析失败'));
                    }
                } else {
                    reject(new Error(`请求失败: ${xhr.status}`));
                }
            }
        };
        
        xhr.onerror = function() {
            reject(new Error('网络请求失败'));
        };
    });
};

// 使用示例
getJSON('https://api.github.com/users/shunwuyu')
    .then(data => {
        console.log('获取数据成功:', data);
    })
    .catch(error => {
        console.error('获取数据失败:', error);
    });

这个封装解决了以下问题:

  • 将回调地狱转换为清晰的Promise链式调用
  • 统一了成功和失败的处理逻辑
  • 增加了JSON解析异常处理
  • 提供了更精确的错误信息

深入理解Promise机制

Promise的三种状态

Promise对象代表一个异步操作的最终完成(或失败)及其结果值。它有三种状态:

  1. pending(等待中):初始状态,既不是成功,也不是失败
  2. fulfilled(已成功):操作成功完成
  3. rejected(已失败):操作失败

状态一旦改变,就不会再变。Promise的状态改变只有两种可能:

  • 从pending变为fulfilled
  • 从pending变为rejected

Promise的生命周期

const promise = new Promise((resolve, reject) => {
    // 执行异步操作
    setTimeout(() => {
        if (Math.random() > 0.5) {
            resolve('操作成功');
        } else {
            reject('操作失败');
        }
    }, 1000);
});

promise
    .then(result => {
        console.log('成功:', result);
    })
    .catch(error => {
        console.error('失败:', error);
    })
    .finally(() => {
        console.log('操作完成');
    });

实用的Sleep函数实现

在实际开发中,我们经常需要控制异步操作的执行时序。下面实现一个优雅的sleep函数:

基础版本

function sleep(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

// 使用示例
sleep(3000)
    .then(() => {
        console.log('3秒后执行');
    });

箭头函数简化版

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

// 使用async/await进一步简化
async function demo() {
    console.log('开始');
    await sleep(2000);
    console.log('2秒后执行');
}

demo();

完整功能版

function sleep(ms, shouldReject = false) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (shouldReject) {
                reject(new Error('睡眠被中断'));
            } else {
                resolve(`睡眠${ms}毫秒完成`);
            }
        }, ms);
    });
}

// 使用示例
sleep(3000)
    .then(message => {
        console.log(message);
    })
    .catch(error => {
        console.error(error.message);
    })
    .finally(() => {
        console.log('sleep函数执行完毕');
    });

内存管理:值拷贝与引用拷贝

在JavaScript中,理解变量在内存中的存储方式对编写高效代码至关重要。

栈内存与堆内存

  • 栈内存:存储基本数据类型和对象引用地址,访问速度快
  • 堆内存:存储复杂对象,空间较大但访问相对较慢

引用拷贝的问题

const arr = [1, 2, 3, 4, 5];
const arr2 = arr; // 引用拷贝

arr2[0] = 0;
console.log(arr); // [0, 2, 3, 4, 5] - 原数组也被修改

实现真正的深拷贝

// 方法1:JSON序列化(有局限性)
const arr3 = JSON.parse(JSON.stringify(arr));

// 方法2:数组拼接
const arr4 = arr.concat([]);

// 方法3:扩展运算符
const arr5 = [...arr];

// 方法4:Array.from()
const arr6 = Array.from(arr);

// 方法5:手写深拷贝函数
function deepClone(obj) {
    if (obj === null || typeof obj !== 'object') return obj;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof Array) return obj.map(item => deepClone(item));
    
    const clonedObj = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            clonedObj[key] = deepClone(obj[key]);
        }
    }
    return clonedObj;
}

}

总结

通过本文的学习,我们深入理解了:

  1. 异步编程的演进:从回调地狱到Promise的优雅解决方案
  2. Promise核心机制:状态管理、链式调用和错误处理
  3. 实用工具封装:getJSON和sleep函数的多种实现方式
  4. 内存管理原理:值类型与引用类型的区别及深拷贝实现

掌握这些知识不仅能够帮助我们编写更优雅、更健壮的代码,还能深入理解JavaScript的异步编程本质。在实际开发中,我们可以根据具体需求选择合适的异步处理方案,组合使用这些工具函数,让我们的代码既简洁又强大。 异步编程是现代JavaScript开发的基石,深入理解其原理和最佳实践,将为我们构建复杂应用打下坚实基础。希望本文能为你的异步编程之旅提供有价值的指导!