在前端开发中,无论是 Vue.js 还是小程序,使用 Promise 和 async/await 来封装请求都可以提高代码的可读性和可维护性。下面我们将讨论它们的异同点,并提供示例代码。

一、Promise 与 async/await 的异同点

1.相同点
  1. 异步处理Promise 和 async/await 都用于处理异步操作,允许代码在等待某个操作结果的时候不会阻塞执行。
  2. 基于Promiseasync/await 语法实际上是基于 Promise 的,所有使用 await 的表达式会返回一个 Promise
  3. 错误处理:两者都可以使用 .catch() 或 try...catch 来处理可能出现的错误。
2.不同点
  1. 语法结构
    • Promise:使用 .then() 和 .catch() 方法链式处理异步结果。
      const myPromise = new Promise((resolve, reject) => {
          // 异步操作
      });
      
      myPromise
          .then(result => {
              // 处理成功
          })
          .catch(error => {
              // 处理失败
          });
      
    • async/await:使用 async 函数和 await 关键字,使代码看起来更像同步代码。
      async function myAsyncFunction() {
          try {
              const result = await myPromise;
              // 处理成功
          } catch (error) {
              // 处理失败
          }
      }
      
  2. 可读性
    • Promise:链式调用可能导致代码嵌套,阅读起来不如 async/await 直观。
    • async/await:使得异步代码看起来更像同步代码,更加易读和易于维护。
  3. 错误处理的方式
    • Promise:错误处理在链的末端,使用 .catch() 方法来捕获。
    • async/await:可以使用 try...catch 结构,这使得错误处理更加灵活和方便,特别是在多个异步操作中。
  4. 执行顺序
    • Promise:可以很容易处理并行的多个异步操作。
    • async/await:默认情况下是按顺序执行的,如果想要并行执行,仍然需要使用 Promise.all()
3.示例对比

以下是 Promise 和 async/await 的示例对比:

1.使用 Promise
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("数据加载成功!");
        }, 1000);
    });
}

fetchData()
    .then(data => {
        console.log(data);
        return fetchData();
    })
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.error(error);
    });
2.使用 async/await
async function fetchDataAsync() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve("数据加载成功!");
        }, 1000);
    });
}

async function main() {
    try {
        const data1 = await fetchDataAsync();
        console.log(data1);
        const data2 = await fetchDataAsync();
        console.log(data2);
    } catch (error) {
        console.error(error);
    }
}

main();
4.封装请求
1.使用 Promise 封装请求

下面的代码示例展示了如何使用 Promise 进行请求封装:

function fetchData(url) {
    return new Promise((resolve, reject) => {
        fetch(url)
            .then(response => {
                // 检查响应状态
                if (!response.ok) {
                    return reject(new Error('网络响应错误'));
                }
                return response.json(); // 解析为 JSON
            })
            .then(data => {
                resolve(data); // 成功返回数据
            })
            .catch(error => {
                reject(error); // 捕获并返回错误
            });
    });
}

// 使用示例
fetchData('https://api.example.com/data')
    .then(data => {
        console.log('获取的数据:', data);
    })
    .catch(error => {
        console.error('请求失败:', error);
    });
2.使用 async/await 封装请求

使用 async/await 可以使得代码看起来更简洁。下面是封装请求的示例:

async function fetchData(url) {
    try {
        const response = await fetch(url); // 等待 fetch 的结果
        if (!response.ok) throw new Error('网络响应错误'); // 检查响应状态
        const data = await response.json(); // 解析为 JSON
        return data; // 返回数据
    } catch (error) {
        throw error; // 捕获并抛出错误
    }
}

// 使用示例
(async () => {
    try {
        const data = await fetchData('https://api.example.com/data');
        console.log('获取的数据:', data);
    } catch (error) {
        console.error('请求失败:', error);
    }
})();

二、在实际开发中注意事项

在实际开发中,使用 Promise 和 async/await 时有一些注意事项,可以帮助开发者更有效地处理异步操作,并避免常见问题。以下是一些关键点:

1. 错误处理
  • Promise: 使用 .catch() 方法来捕获和处理错误。但要注意,如果有多层嵌套的 Promise,每层的.catch() 都需要处理错误。

    fetch('/api/data')
        .then(response => response.json())
        .catch(error => console.error('Error:', error));
    
  • async/await: 使用 try/catch 块可以捕获所有可能的错误,使得错误处理更加集中和清晰。

    async function fetchData() {
        try {
            const response = await fetch('/api/data');
            const data = await response.json();
        } catch (error) {
            console.error('Error:', error);
        }
    }
    
2. 不要在循环中直接使用 await

在循环中直接使用 await 可能会导致性能低下,因为这会使每次迭代都等待前一次完成。

  • 错误的做法

    for (const item of items) {
        await processItem(item); // 每次都等待,逐个执行
    }
    
  • 正确的做法: 使用 Promise.all 来并行处理所有异步操作。

    const promises = items.map(item => processItem(item));
    const results = await Promise.all(promises);
    
3. 保持 async 和 Promise 一致性
  • 确保在使用 async/await 的函数中,没有遗漏的 Promise 处理。如果一个函数是 async 的,它应该返回 Promise。
4. 使用 .finally() 处理结束状态

对于 Promise,可以使用 .finally() 来进行清理工作,无论是成功还是失败。这在 async/await 中则需要在 try/catch 的最后进行相应的处理。

fetch('/api/data')
    .then(response => response.json())
    .catch(error => console.error(error))
    .finally(() => {
        // 清理工作
    });
5. 降低 Promise 链的深度

尽量避免过深的 Promise 链,这会让代码变得更加复杂和难以维护。使用 async/await 可以更好地管理异步操作的逻辑结构。

6. 适当使用 Promise.race

如果需要在多个 Promise 中获得第一个完成的结果,可以使用 Promise.race。在实际开发中,这对于处理超时、并发请求等场景非常有用。

const fetchWithTimeout = (url, timeout) => {
    return Promise.race([
        fetch(url),
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Timeout')), timeout)
        )
    ]);
};
7. 处理未处理的 Promise 拒绝

在 Node.js 环境中,未处理的 Promise 拒绝会导致程序崩溃,可以通过 process.on('unhandledRejection', handler) 捕获这些拒绝并进行处理。

三、封装 Vue 和小程序的 API 请求示例

以下是 Vue 和小程序的请求封装示例,包含 GET、POST、文件上传和下载。

1. Vue.js 示例
// apiService.js
export default class ApiService {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }

    async get(endpoint, params = {}) {
        const url = new URL(`${this.baseUrl}${endpoint}`);
        Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));

        try {
            const response = await fetch(url);
            return await this.handleResponse(response);
        } catch (error) {
            console.error(error);
        }
    }

    async post(endpoint, data) {
        try {
            const response = await fetch(`${this.baseUrl}${endpoint}`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(data),
            });
            return await this.handleResponse(response);
        } catch (error) {
            console.error(error);
        }
    }

    async uploadFile(endpoint, file) {
        const formData = new FormData();
        formData.append('file', file);

        try {
            const response = await fetch(`${this.baseUrl}${endpoint}`, {
                method: 'POST',
                body: formData,
            });
            return await this.handleResponse(response);
        } catch (error) {
            console.error(error);
        }
    }

    async downloadFile(endpoint) {
        try {
            const response = await fetch(`${this.baseUrl}${endpoint}`);
            const blob = await response.blob();
            const url = window.URL.createObjectURL(blob);

            const a = document.createElement('a');
            a.href = url;
            a.download = endpoint.split('/').pop();
            document.body.appendChild(a);
            a.click();
            a.remove();
        } catch (error) {
            console.error(error);
        }
    }

    async handleResponse(response) {
        if (!response.ok) {
            const error = await response.json();
            throw new Error(error.message || '请求失败');
        }
        return response.json();
    }
}
2. 小程序示例
// apiService.js
class ApiService {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }

    async get(endpoint, params = {}) {
        const url = this.buildUrl(endpoint, params);
        return new Promise((resolve, reject) => {
            wx.request({
                url,
                method: 'GET',
                success: res => resolve(res.data),
                fail: err => reject(err),
            });
        });
    }

    async post(endpoint, data) {
        return new Promise((resolve, reject) => {
            wx.request({
                url: `${this.baseUrl}${endpoint}`,
                method: 'POST',
                data,
                header: { 'Content-Type': 'application/json' },
                success: res => resolve(res.data),
                fail: err => reject(err),
            });
        });
    }

    async uploadFile(endpoint, filePath) {
        return new Promise((resolve, reject) => {
            wx.uploadFile({
                url: `${this.baseUrl}${endpoint}`,
                filePath,
                name: 'file',
                success: res => resolve(res.data),
                fail: err => reject(err),
            });
        });
    }

    async downloadFile(url) {
        return new Promise((resolve, reject) => {
            wx.downloadFile({
                url,
                success: res => {
                    // 处理文件下载逻辑
                    resolve(res);
                },
                fail: err => reject(err),
            });
        });
    }

    buildUrl(endpoint, params) {
        const url = new URL(`${this.baseUrl}${endpoint}`);
        Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
        return url.toString();
    }
}

总结

  • Promise:通过 .then() 和 .catch() 进行异步处理,适合简单任务,但在复杂情况下可读性下降。
  • async/await:使异步代码看起来像同步代码,更加易读,错误处理更加直观。

在封装 API 请求时,建议使用 async/await 以提高代码的可维护性,无论是在 Vue.js 还是小程序中。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐