想象一下:你同时在餐厅点餐、给朋友发信息、听音乐。如果只能做完一件再做下一件,你会疯掉吗?这就是为什么我们需要异步编程!

什么是异步编程?

同步编程(传统方式):

console.log("开始做早餐");
// 等待10秒做煎蛋
console.log("煎蛋做好了");
console.log("开始煮咖啡");
// 等待5秒煮咖啡
console.log("咖啡煮好了");
// 总共需要15秒

异步编程(现代方式):

console.log("开始做早餐");
开始煎蛋(10秒后完成);  // 不等待,继续下一步
开始煮咖啡(5秒后完成); // 立即开始煮咖啡
// 总共只需要10秒(两者同时进行)

简单来说:异步编程让你的程序可以同时处理多个任务,而不是傻傻等待!

为什么需要异步编程?

  1. 避免阻塞:网络请求可能需要几秒钟,不能让用户界面卡住
  2. 提高效率:同时处理多个操作
  3. 更好的用户体验:应用保持响应

异步编程的进化史

阶段1:回调函数(Callback Hell)

最早的异步处理方式:

// 获取用户数据后,再获取其订单,再获取订单详情...
getUserData(function(user) {
    getUserOrders(user.id, function(orders) {
        getOrderDetails(orders[0].id, function(details) {
            getProductInfo(details.productId, function(product) {
                console.log("最终产品信息:", product);
                // 更多的嵌套...
            });
        });
    });
});

问题:回调地狱!代码像金字塔一样,难以阅读和维护。

阶段2:Promise - 救世主登场

Promise:承诺更好的未来

Promise 是一个对象,表示一个异步操作的**最终完成(或失败)**及其结果值。

// Promise 的三种状态:
// 1. Pending(进行中) - 初始状态
// 2. Fulfilled(已成功) - 操作成功完成
// 3. Rejected(已失败) - 操作失败

创建 Promise

// 创建一个简单的 Promise
const myPromise = new Promise((resolve, reject) => {
    // 异步操作,比如网络请求、读取文件等
    
    setTimeout(() => {
        const success = Math.random() > 0.3; // 70% 成功概率
        
        if (success) {
            resolve("操作成功!数据是: {name: '小明', age: 20}");
        } else {
            reject(new Error("操作失败:网络连接问题"));
        }
    }, 2000);
});

使用 Promise

// 使用 .then() 处理成功,.catch() 处理失败
myPromise
    .then(result => {
        console.log("成功:", result);
    })
    .catch(error => {
        console.error("失败:", error.message);
    })
    .finally(() => {
        console.log("无论成功失败,都会执行这里");
    });

Promise 链式调用 - 解决回调地狱

// 模拟三个依次依赖的异步操作
function step1() {
    return new Promise(resolve => {
        setTimeout(() => resolve("第一步完成"), 1000);
    });
}

function step2(data) {
    return new Promise(resolve => {
        setTimeout(() => resolve(`${data} → 第二步完成`), 1000);
    });
}

function step3(data) {
    return new Promise(resolve => {
        setTimeout(() => resolve(`${data} → 第三步完成`), 1000);
    });
}

// 链式调用,不再是嵌套!
step1()
    .then(result => {
        console.log(result);
        return step2(result);
    })
    .then(result => {
        console.log(result);
        return step3(result);
    })
    .then(finalResult => {
        console.log("最终结果:", finalResult);
    })
    .catch(error => {
        console.error("出错:", error);
    });

Promise 的实用方法

// 1. Promise.all - 等待所有Promise完成
const promise1 = fetchUser();
const promise2 = fetchProducts();
const promise3 = fetchSettings();

Promise.all([promise1, promise2, promise3])
    .then(([user, products, settings]) => {
        console.log("所有数据加载完成");
        renderPage(user, products, settings);
    })
    .catch(error => {
        console.error("至少一个请求失败:", error);
    });

// 2. Promise.race - 竞速,第一个完成的获胜
const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error("请求超时")), 5000);
});

Promise.race([fetchUserData(), timeoutPromise])
    .then(data => {
        console.log("在5秒内获取到数据");
    })
    .catch(error => {
        console.error(error.message);
    });

// 3. Promise.allSettled - 等待所有Promise完成(无论成功失败)
Promise.allSettled([promise1, promise2, promise3])
    .then(results => {
        results.forEach((result, index) => {
            if (result.status === "fulfilled") {
                console.log(`Promise ${index} 成功:`, result.value);
            } else {
                console.log(`Promise ${index} 失败:`, result.reason);
            }
        });
    });

阶段3:async/await - 异步编程的终极形态

async/await 是 Promise 的语法糖,让你可以用同步的方式写异步代码

基本用法

// async 函数总是返回一个 Promise
async function getUserData() {
    // 假装这是从服务器获取数据
    return { name: "小明", age: 20 };
}

// await 只能在 async 函数内部使用
async function main() {
    console.log("开始获取用户数据...");
    
    try {
        const user = await getUserData(); // 等待Promise完成
        console.log("获取到的用户:", user);
        
        const orders = await getUserOrders(user.id); // 等待下一个Promise
        console.log("用户的订单:", orders);
        
        return { user, orders }; // 自动包装为Promise
    } catch (error) {
        console.error("获取数据失败:", error);
        throw error; // 重新抛出错误
    }
}

// 使用 async 函数
main().then(result => {
    console.log("所有操作完成:", result);
});

对比:Promise vs async/await

Promise 写法

function fetchData() {
    return fetchUser()
        .then(user => {
            return fetchUserPosts(user.id)
                .then(posts => {
                    return fetchPostComments(posts[0].id)
                        .then(comments => {
                            return { user, posts, comments };
                        });
                });
        })
        .catch(error => {
            console.error("出错:", error);
        });
}

async/await 写法

async function fetchData() {
    try {
        const user = await fetchUser();
        const posts = await fetchUserPosts(user.id);
        const comments = await fetchPostComments(posts[0].id);
        return { user, posts, comments };
    } catch (error) {
        console.error("出错:", error);
        throw error;
    }
}

是不是清晰多了?就像写同步代码一样!

实际应用示例

示例1:用户登录流程
async function loginUser(email, password) {
    // 显示加载中
    showLoadingSpinner();
    
    try {
        // 1. 验证输入
        if (!email || !password) {
            throw new Error("邮箱和密码不能为空");
        }
        
        // 2. 发送登录请求(异步)
        const user = await api.login(email, password);
        
        // 3. 获取用户详情(依赖登录结果)
        const userDetails = await api.getUserDetails(user.id);
        
        // 4. 获取用户偏好设置(与上一步并行)
        const [notifications, settings] = await Promise.all([
            api.getNotifications(user.id),
            api.getUserSettings(user.id)
        ]);
        
        // 5. 更新UI
        updateUserInterface(userDetails, notifications, settings);
        
        console.log("登录成功!");
        return userDetails;
        
    } catch (error) {
        // 统一错误处理
        if (error.response?.status === 401) {
            showErrorMessage("邮箱或密码错误");
        } else if (error.response?.status === 429) {
            showErrorMessage("尝试次数过多,请稍后再试");
        } else {
            showErrorMessage("登录失败,请检查网络连接");
        }
        throw error;
    } finally {
        // 无论成功失败,都隐藏加载动画
        hideLoadingSpinner();
    }
}

// 使用
loginButton.addEventListener("click", async () => {
    try {
        await loginUser(emailInput.value, passwordInput.value);
        navigateToDashboard();
    } catch (error) {
        // 已经在上面的函数中处理了
    }
});
示例2:图片批量下载器
async function downloadImages(imageUrls, maxConcurrent = 3) {
    const results = [];
    const errors = [];
    
    // 分组下载,避免同时发起太多请求
    for (let i = 0; i < imageUrls.length; i += maxConcurrent) {
        const batch = imageUrls.slice(i, i + maxConcurrent);
        
        const batchPromises = batch.map(async (url, index) => {
            try {
                console.log(`开始下载: ${url}`);
                const imageData = await downloadImage(url);
                console.log(`下载完成: ${url}`);
                return { url, data: imageData, success: true };
            } catch (error) {
                console.error(`下载失败: ${url}`, error);
                return { url, error: error.message, success: false };
            }
        });
        
        const batchResults = await Promise.allSettled(batchPromises);
        
        batchResults.forEach(result => {
            if (result.status === "fulfilled") {
                if (result.value.success) {
                    results.push(result.value);
                } else {
                    errors.push(result.value);
                }
            }
        });
        
        // 批次之间稍作等待
        await delay(500);
    }
    
    return { results, errors };
}

// 使用
const imageUrls = [
    "https://example.com/image1.jpg",
    "https://example.com/image2.jpg",
    // ... 更多图片
];

downloadImages(imageUrls, 5).then(({ results, errors }) => {
    console.log(`成功下载 ${results.length} 张图片`);
    console.log(`${errors.length} 张图片下载失败`);
    
    // 显示下载结果
    renderImageGallery(results);
});

高级技巧与最佳实践

1. 不要滥用 async/await

不好的写法

async function processItems(items) {
    // 顺序执行,很慢!
    for (const item of items) {
        await processItem(item); // 等待每个完成
    }
}

好的写法

async function processItems(items) {
    // 并行执行,很快!
    const promises = items.map(item => processItem(item));
    const results = await Promise.all(promises);
    return results;
}

2. 错误处理要到位

async function robustFetch() {
    try {
        const response = await fetch(url);
        
        if (!response.ok) {
            throw new Error(`HTTP错误: ${response.status}`);
        }
        
        const data = await response.json();
        return data;
        
    } catch (error) {
        // 细分错误类型
        if (error.name === 'TypeError') {
            console.error("网络错误或CORS问题");
            // 尝试备用方案
            return await fetchFromBackup(url);
        } else if (error.name === 'SyntaxError') {
            console.error("JSON解析错误");
            throw new Error("服务器返回了无效的数据格式");
        } else {
            // 其他错误
            console.error("未知错误:", error);
            throw error;
        }
    }
}

3. 超时控制

async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
        const response = await fetch(url, {
            signal: controller.signal
        });
        clearTimeout(timeoutId);
        return response;
    } catch (error) {
        clearTimeout(timeoutId);
        if (error.name === 'AbortError') {
            throw new Error(`请求超时 (${timeout}ms)`);
        }
        throw error;
    }
}

实战:创建一个简单的异步任务队列

class AsyncQueue {
    constructor() {
        this.queue = [];
        this.processing = false;
    }
    
    // 添加任务到队列
    enqueue(task) {
        return new Promise((resolve, reject) => {
            this.queue.push({ task, resolve, reject });
            if (!this.processing) {
                this.processQueue();
            }
        });
    }
    
    // 处理队列中的任务
    async processQueue() {
        this.processing = true;
        
        while (this.queue.length > 0) {
            const { task, resolve, reject } = this.queue.shift();
            
            try {
                const result = await task();
                resolve(result);
            } catch (error) {
                reject(error);
            }
            
            // 可选:在任务之间添加延迟
            await this.delay(100);
        }
        
        this.processing = false;
    }
    
    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// 使用示例
const taskQueue = new AsyncQueue();

// 添加多个任务(它们会按顺序执行)
taskQueue.enqueue(() => downloadFile("file1.pdf"));
taskQueue.enqueue(() => processImage("photo.jpg"));
taskQueue.enqueue(() => sendAnalyticsData());

console.log("所有任务已添加到队列,将按顺序执行");

学习路径建议

  1. 先理解 Promise 基础:then/catch/finally
  2. 掌握 async/await 语法:像写同步代码一样写异步
  3. 学习错误处理:try/catch 和 Promise.catch
  4. 掌握并行执行:Promise.all/race/allSettled
  5. 了解高级模式:队列、重试、超时等

总结

异步编程是现代 JavaScript 的核心技能:

  • Promise:提供了标准的异步处理方式
  • async/await:让异步代码清晰易读
  • 错误处理:确保应用的健壮性
  • 并行执行:充分利用计算资源

记住:异步不是关于让代码跑得更快,而是关于不让代码在等待时浪费时间

开始在你的项目中使用 async/await 吧,你会发现异步编程也可以如此优雅! 🚀

提示:浏览器开发者工具的 Sources 面板中,可以逐步调试 async/await 代码,就像调试同步代码一样简单!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐