Skip to content

异步函数

异步函数(Async Functions)是 ES2017 引入的一个重要特性,它提供了一种更简洁、更易读的方式来处理异步操作。异步函数基于 Promise,使异步代码看起来像同步代码。

什么是异步函数?

异步函数是使用 async 关键字声明的函数,它会隐式返回一个 Promise。在异步函数内部,可以使用 await 关键字来等待 Promise 的解决。

基本语法

javascript
// 异步函数声明
async function myFunction() {
    // 函数体
}

// 异步函数表达式
const myFunction = async function() {
    // 函数体
};

// 异步箭头函数
const myFunction = async () => {
    // 函数体
};

// 对象方法中的异步函数
const obj = {
    async myMethod() {
        // 方法体
    }
};

// 类方法中的异步函数
class MyClass {
    async myMethod() {
        // 方法体
    }
}

await 关键字

await 关键字只能在异步函数内部使用,它会暂停函数的执行,等待 Promise 解决,然后返回结果。

javascript
async function fetchData() {
    try {
        // 等待 Promise 解决
        let response = await fetch('https://api.example.com/data');
        let data = await response.json();
        return data;
    } catch (error) {
        // 处理错误
        console.error('获取数据失败:', error);
        throw error;
    }
}

// 调用异步函数
fetchData().then(data => {
    console.log('获取到的数据:', data);
}).catch(error => {
    console.error('处理错误:', error);
});

错误处理

异步函数中的错误处理可以使用 try/catch 语句,这比使用 Promise 的 .catch() 方法更直观。

javascript
async function example() {
    try {
        let result1 = await someAsyncOperation1();
        let result2 = await someAsyncOperation2(result1);
        let result3 = await someAsyncOperation3(result2);
        return result3;
    } catch (error) {
        console.error('操作失败:', error);
        // 可以选择重新抛出错误或返回默认值
        throw error; // 重新抛出错误
        // 或者 return defaultValue; // 返回默认值
    }
}

并行执行

在异步函数中,默认情况下 await 会顺序执行 Promise。如果希望并行执行多个不相关的异步操作,可以使用 Promise.all()。

javascript
// 顺序执行(较慢)
async function sequential() {
    let result1 = await fetch('/api/data1');
    let result2 = await fetch('/api/data2');
    let result3 = await fetch('/api/data3');
    return [result1, result2, result3];
}

// 并行执行(较快)
async function parallel() {
    let [result1, result2, result3] = await Promise.all([
        fetch('/api/data1'),
        fetch('/api/data2'),
        fetch('/api/data3')
    ]);
    return [result1, result2, result3];
}

实际应用示例

获取用户数据

javascript
async function fetchUserData(userId) {
    try {
        // 获取用户基本信息
        let userResponse = await fetch(`/api/users/${userId}`);
        if (!userResponse.ok) {
            throw new Error(`HTTP error! status: ${userResponse.status}`);
        }
        let user = await userResponse.json();
        
        // 获取用户的文章
        let postsResponse = await fetch(`/api/users/${userId}/posts`);
        if (!postsResponse.ok) {
            throw new Error(`HTTP error! status: ${postsResponse.status}`);
        }
        let posts = await postsResponse.json();
        
        // 合并数据
        return {
            ...user,
            posts: posts
        };
    } catch (error) {
        console.error('获取用户数据失败:', error);
        throw new Error('无法获取用户数据');
    }
}

// 使用示例
fetchUserData(123)
    .then(userData => {
        console.log('用户数据:', userData);
    })
    .catch(error => {
        console.error('错误:', error.message);
    });

文件上传

javascript
async function uploadFile(file) {
    try {
        // 创建 FormData 对象
        let formData = new FormData();
        formData.append('file', file);
        
        // 上传文件
        let response = await fetch('/api/upload', {
            method: 'POST',
            body: formData
        });
        
        if (!response.ok) {
            throw new Error(`上传失败: ${response.statusText}`);
        }
        
        let result = await response.json();
        console.log('上传成功:', result);
        return result;
    } catch (error) {
        console.error('上传过程中发生错误:', error);
        throw error;
    }
}

数据库操作

javascript
// 模拟数据库操作
async function getUserById(id) {
    // 模拟异步数据库查询
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ id, name: `用户${id}`, email: `user${id}@example.com` });
        }, 100);
    });
}

async function updateUser(id, updates) {
    try {
        // 获取现有用户数据
        let user = await getUserById(id);
        
        // 应用更新
        let updatedUser = { ...user, ...updates };
        
        // 模拟保存到数据库
        await new Promise(resolve => setTimeout(resolve, 200));
        
        console.log('用户更新成功:', updatedUser);
        return updatedUser;
    } catch (error) {
        console.error('更新用户失败:', error);
        throw error;
    }
}

// 使用示例
async function main() {
    try {
        let updatedUser = await updateUser(1, { name: '新名字' });
        console.log('更新后的用户:', updatedUser);
    } catch (error) {
        console.error('主函数中捕获的错误:', error);
    }
}

与 Promise 的比较

使用 Promise

javascript
function fetchUserData(userId) {
    return fetch(`/api/users/${userId}`)
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
        })
        .then(user => {
            return fetch(`/api/users/${userId}/posts`);
        })
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
        })
        .then(posts => {
            return { user, posts };
        })
        .catch(error => {
            console.error('获取用户数据失败:', error);
            throw new Error('无法获取用户数据');
        });
}

使用异步函数

javascript
async function fetchUserData(userId) {
    try {
        let userResponse = await fetch(`/api/users/${userId}`);
        if (!userResponse.ok) {
            throw new Error(`HTTP error! status: ${userResponse.status}`);
        }
        let user = await userResponse.json();
        
        let postsResponse = await fetch(`/api/users/${userId}/posts`);
        if (!postsResponse.ok) {
            throw new Error(`HTTP error! status: ${postsResponse.status}`);
        }
        let posts = await postsResponse.json();
        
        return { user, posts };
    } catch (error) {
        console.error('获取用户数据失败:', error);
        throw new Error('无法获取用户数据');
    }
}

注意事项和最佳实践

  1. 总是使用 try/catch:在异步函数中使用 try/catch 来处理错误。

  2. 避免在循环中顺序使用 await:如果需要处理多个不相关的异步操作,考虑并行执行。

javascript
// 不好的做法 - 顺序执行
async function badExample() {
    let results = [];
    for (let i = 0; i < urls.length; i++) {
        let response = await fetch(urls[i]);
        let data = await response.json();
        results.push(data);
    }
    return results;
}

// 好的做法 - 并行执行
async function goodExample() {
    let promises = urls.map(url => fetch(url).then(r => r.json()));
    let results = await Promise.all(promises);
    return results;
}
  1. 正确处理 Promise 返回值:记住异步函数总是返回 Promise。
javascript
async function getData() {
    return "数据";
}

// 正确用法
getData().then(data => console.log(data));

// 错误用法
// let data = getData(); // data 是一个 Promise,不是实际数据
  1. 避免忘记 await:忘记使用 await 会导致获取到 Promise 而不是实际值。
javascript
async function example() {
    // 错误 - 忘记 await
    let data = fetch('/api/data');
    console.log(data); // 输出: Promise {...}
    
    // 正确
    let data = await fetch('/api/data');
    console.log(data); // 输出: Response 对象
}
  1. 在顶层使用异步函数:在现代浏览器和 Node.js 中,可以在模块顶层使用 await。
javascript
// 在模块顶层使用 await (现代环境支持)
let data = await fetch('/api/data');
let jsonData = await data.json();
console.log(jsonData);

异步函数极大地简化了异步编程,使代码更易读、更易维护。它是现代 JavaScript 开发中处理异步操作的首选方式。