Promise最佳实践

本章将介绍Promise使用的最佳实践,帮助您写出更健壮、更易维护的异步代码,避免常见的陷阱和错误。

7.1 命名规范和代码组织

良好的命名和代码组织是编写高质量Promise代码的基础。

清晰的函数命名

示例:清晰的Promise函数命名

// 好的命名
function fetchUserData(userId) {
    return new Promise((resolve, reject) => {
        // 实现
    });
}

function validateUserCredentials(username, password) {
    return new Promise((resolve, reject) => {
        // 实现
    });
}

function saveUserPreferences(userId, preferences) {
    return new Promise((resolve, reject) => {
        // 实现
    });
}

// 使用
fetchUserData(123)
    .then(user => validateUserCredentials(user.username, user.password))
    .then(isValid => {
        if (isValid) {
            return saveUserPreferences(123, { theme: 'dark' });
        }
        throw new Error('用户验证失败');
    })
    .then(() => {
        console.log('用户偏好设置保存成功');
    })
    .catch(error => {
        console.error('操作失败:', error.message);
    });

避免嵌套Promise

示例:避免嵌套Promise

// 不好的做法:嵌套Promise
fetchUserData(userId)
    .then(user => {
        return fetchUserPosts(user.id)
            .then(posts => {
                return fetchPostComments(posts[0].id)
                    .then(comments => {
                        return { user, posts, comments };
                    });
            });
    })
    .then(data => {
        console.log(data);
    });

// 好的做法:链式调用
fetchUserData(userId)
    .then(user => {
        return fetchUserPosts(user.id).then(posts => ({ user, posts }));
    })
    .then(({ user, posts }) => {
        return fetchPostComments(posts[0].id).then(comments => ({ user, posts, comments }));
    })
    .then(data => {
        console.log(data);
    });

// 更好的做法:返回Promise进行链式调用
fetchUserData(userId)
    .then(user => {
        return fetchUserPosts(user.id)
            .then(posts => ({ user, posts }));
    })
    .then(({ user, posts }) => {
        return fetchPostComments(posts[0].id)
            .then(comments => ({ user, posts, comments }));
    })
    .then(({ user, posts, comments }) => {
        console.log('用户:', user);
        console.log('帖子:', posts);
        console.log('评论:', comments);
    });

7.2 错误处理最佳实践

正确的错误处理是构建健壮应用的关键。

始终处理Promise拒绝

示例:完整的错误处理

// 好的做法:始终处理错误
function performUserOperation(userId) {
    return fetchUserData(userId)
        .then(user => {
            if (!user.isActive) {
                throw new Error('用户账户已被禁用');
            }
            return processUserData(user);
        })
        .then(processedData => {
            return saveProcessedData(processedData);
        })
        .then(saveResult => {
            console.log('操作成功完成');
            return saveResult;
        })
        .catch(error => {
            // 统一错误处理
            console.error('用户操作失败:', error.message);
            
            // 根据错误类型进行不同处理
            if (error.message.includes('网络')) {
                showNetworkErrorMessage();
            } else if (error.message.includes('权限')) {
                showPermissionErrorMessage();
            } else {
                showGenericErrorMessage();
            }
            
            // 重新抛出错误供上层处理
            throw error;
        });
}

// 使用
performUserOperation(123)
    .then(result => {
        // 操作成功
        updateUI(result);
    })
    .catch(error => {
        // 错误已在performUserOperation中处理,这里可以进行额外处理
        logErrorToServer(error);
    });

区分不同类型的错误

示例:自定义错误类型

// 自定义错误类
class NetworkError extends Error {
    constructor(message) {
        super(message);
        this.name = 'NetworkError';
    }
}

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = 'ValidationError';
    }
}

class PermissionError extends Error {
    constructor(message) {
        super(message);
        this.name = 'PermissionError';
    }
}

// 使用自定义错误
function fetchUserData(userId) {
    return new Promise((resolve, reject) => {
        if (!userId) {
            reject(new ValidationError('用户ID不能为空'));
        }
        
        // 模拟网络请求
        setTimeout(() => {
            if (Math.random() > 0.8) {
                reject(new NetworkError('网络连接失败'));
            } else if (userId < 0) {
                reject(new PermissionError('权限不足'));
            } else {
                resolve({ id: userId, name: `用户${userId}` });
            }
        }, 1000);
    });
}

// 根据错误类型处理
fetchUserData(-1)
    .then(user => {
        console.log('用户数据:', user);
    })
    .catch(error => {
        if (error instanceof ValidationError) {
            console.error('验证错误:', error.message);
            showValidationMessage(error.message);
        } else if (error instanceof NetworkError) {
            console.error('网络错误:', error.message);
            showNetworkErrorMessage(error.message);
        } else if (error instanceof PermissionError) {
            console.error('权限错误:', error.message);
            showPermissionErrorMessage(error.message);
        } else {
            console.error('未知错误:', error.message);
            showGenericErrorMessage();
        }
    });

7.3 Promise链的最佳实践

编写清晰、可维护的Promise链需要遵循一些最佳实践。

保持链的简洁性

示例:简洁的Promise链

// 好的做法:每个then只做一件事
fetchUserData(userId)
    .then(validateUser)
    .then(transformUserData)
    .then(saveUserData)
    .then(notifyUser)
    .then(result => {
        console.log('所有操作完成:', result);
        return result;
    })
    .catch(handleError);

// 辅助函数
function validateUser(user) {
    if (!user || !user.id) {
        throw new Error('无效的用户数据');
    }
    return user;
}

function transformUserData(user) {
    return {
        ...user,
        fullName: `${user.firstName} ${user.lastName}`,
        lastModified: new Date().toISOString()
    };
}

function saveUserData(user) {
    // 模拟保存操作
    return new Promise(resolve => {
        setTimeout(() => resolve(user), 500);
    });
}

function notifyUser(user) {
    console.log(`用户 ${user.fullName} 的数据已更新`);
    return user;
}

function handleError(error) {
    console.error('操作失败:', error.message);
    showErrorMessage('操作失败,请稍后重试');
}

合理使用async/await

示例:async/await与Promise的结合

// 在适当的地方使用async/await
async function processUserBatch(userIds) {
    try {
        // 并行处理多个用户
        const userPromises = userIds.map(id => fetchUserData(id));
        const users = await Promise.all(userPromises);
        
        // 串行处理用户数据
        const processedUsers = [];
        for (const user of users) {
            const processedUser = await processSingleUser(user);
            processedUsers.push(processedUser);
        }
        
        // 保存所有处理后的用户数据
        const savePromises = processedUsers.map(user => saveUserData(user));
        const saveResults = await Promise.all(savePromises);
        
        return saveResults;
    } catch (error) {
        console.error('批量处理用户失败:', error);
        throw error;
    }
}

// 辅助函数
async function processSingleUser(user) {
    // 模拟复杂的用户处理逻辑
    await new Promise(resolve => setTimeout(resolve, 100));
    return { ...user, processed: true };
}

async function saveUserData(user) {
    // 模拟保存操作
    await new Promise(resolve => setTimeout(resolve, 50));
    return { ...user, saved: true };
}

// 使用
processUserBatch([1, 2, 3, 4, 5])
    .then(results => {
        console.log('批量处理完成:', results);
    })
    .catch(error => {
        console.error('批量处理失败:', error);
    });

7.4 性能优化最佳实践

在处理大量Promise时,需要注意性能优化。

控制并发数量

示例:控制Promise并发数量

// 控制并发数量的工具函数
function limitConcurrency(promises, limit) {
    return new Promise((resolve, reject) => {
        const results = [];
        let index = 0;
        let pending = 0;
        let resolved = 0;
        
        function executeNext() {
            if (index >= promises.length && pending === 0) {
                resolve(results);
                return;
            }
            
            while (pending < limit && index < promises.length) {
                const currentIndex = index++;
                const promise = Promise.resolve(promises[currentIndex]);
                
                pending++;
                
                promise
                    .then(result => {
                        results[currentIndex] = result;
                        resolved++;
                        pending--;
                        executeNext();
                    })
                    .catch(error => {
                        reject(error);
                    });
            }
        }
        
        executeNext();
    });
}

// 使用示例
const tasks = Array.from({ length: 100 }, (_, i) => {
    return () => new Promise(resolve => {
        setTimeout(() => {
            console.log(`任务 ${i} 完成`);
            resolve(`结果 ${i}`);
        }, Math.random() * 1000);
    });
});

// 限制并发数量为5
limitConcurrency(tasks.map(task => task()), 5)
    .then(results => {
        console.log('所有任务完成,结果数量:', results.length);
    })
    .catch(error => {
        console.error('任务执行失败:', error);
    });

避免创建不必要的Promise

示例:避免不必要的Promise创建

// 工具函数:确保返回Promise
function ensurePromise(value) {
    return value instanceof Promise ? value : Promise.resolve(value);
}

// 好的做法:统一处理同步和异步返回值
function getData(source) {
    if (source === 'cache') {
        // 同步返回缓存数据
        return ensurePromise(getCachedData());
    } else {
        // 异步获取新数据
        return fetchNewData();
    }
}

function getCachedData() {
    // 模拟缓存数据
    const cached = localStorage.getItem('userData');
    return cached ? JSON.parse(cached) : null;
}

function fetchNewData() {
    // 模拟网络请求
    return new Promise(resolve => {
        setTimeout(() => {
            const data = { id: 1, name: '用户1' };
            resolve(data);
        }, 1000);
    });
}

// 使用
getData('cache')
    .then(data => {
        if (data) {
            console.log('使用缓存数据:', data);
        } else {
            return getData('network');
        }
    })
    .then(data => {
        if (data) {
            console.log('获取到数据:', data);
        }
    })
    .catch(error => {
        console.error('获取数据失败:', error);
    });

7.5 测试Promise代码

正确测试Promise代码对于确保应用质量非常重要。

单元测试Promise

示例:Promise单元测试

// 被测试的函数
function divide(a, b) {
    return new Promise((resolve, reject) => {
        if (b === 0) {
            reject(new Error('除数不能为零'));
        } else {
            resolve(a / b);
        }
    });
}

// 测试代码(使用Jest或其他测试框架)
describe('divide函数测试', () => {
    test('正常除法运算', () => {
        return expect(divide(10, 2)).resolves.toBe(5);
    });
    
    test('除零错误', () => {
        return expect(divide(10, 0)).rejects.toThrow('除数不能为零');
    });
    
    test('使用async/await测试', async () => {
        const result = await divide(15, 3);
        expect(result).toBe(5);
    });
    
    test('使用async/await测试错误情况', async () => {
        await expect(divide(10, 0)).rejects.toThrow('除数不能为零');
    });
});

// 更复杂的测试示例
function fetchUserDataWithRetry(userId, maxRetries = 3) {
    let attempts = 0;
    
    function attempt() {
        attempts++;
        return fetchUserData(userId)
            .catch(error => {
                if (attempts < maxRetries) {
                    return attempt();
                }
                throw error;
            });
    }
    
    return attempt();
}

// 测试重试机制
describe('fetchUserDataWithRetry测试', () => {
    beforeEach(() => {
        // 重置模拟
        attempts = 0;
    });
    
    test('成功获取用户数据', async () => {
        // 模拟第一次就成功
        jest.spyOn(global, 'fetchUserData').mockResolvedValue({ id: 1, name: '用户1' });
        
        const user = await fetchUserDataWithRetry(1);
        expect(user).toEqual({ id: 1, name: '用户1' });
        expect(fetchUserData).toHaveBeenCalledTimes(1);
    });
    
    test('重试后成功获取用户数据', async () => {
        // 模拟前两次失败,第三次成功
        jest.spyOn(global, 'fetchUserData')
            .mockRejectedValueOnce(new Error('网络错误'))
            .mockRejectedValueOnce(new Error('网络错误'))
            .mockResolvedValueOnce({ id: 1, name: '用户1' });
        
        const user = await fetchUserDataWithRetry(1, 3);
        expect(user).toEqual({ id: 1, name: '用户1' });
        expect(fetchUserData).toHaveBeenCalledTimes(3);
    });
    
    test('重试次数用尽后失败', async () => {
        // 模拟每次都失败
        jest.spyOn(global, 'fetchUserData').mockRejectedValue(new Error('网络错误'));
        
        await expect(fetchUserDataWithRetry(1, 2)).rejects.toThrow('网络错误');
        expect(fetchUserData).toHaveBeenCalledTimes(2);
    });
});

7.6 调试和监控

有效的调试和监控可以帮助快速定位和解决问题。

添加调试信息

示例:Promise调试工具

// 调试工具函数
function debugPromise(promise, label, options = {}) {
    const { logSuccess = true, logError = true, logStart = true } = options;
    
    if (logStart) {
        console.log(`[开始] ${label}`);
    }
    
    return promise
        .then(result => {
            if (logSuccess) {
                console.log(`[成功] ${label}:`, result);
            }
            return result;
        })
        .catch(error => {
            if (logError) {
                console.error(`[失败] ${label}:`, error);
            }
            throw error;
        });
}

// 使用调试工具
const fetchUserPromise = debugPromise(
    fetchUserData(123),
    '获取用户数据',
    { logStart: true, logSuccess: true, logError: true }
);

const fetchPostsPromise = debugPromise(
    fetchUserPosts(123),
    '获取用户帖子',
    { logStart: false } // 不记录开始
);

Promise.all([fetchUserPromise, fetchPostsPromise])
    .then(([user, posts]) => {
        console.log('所有数据获取完成');
    })
    .catch(error => {
        console.error('数据获取失败:', error);
    });

性能监控

示例:Promise性能监控

// 性能监控装饰器
function monitorPromise(promiseFn, name) {
    return function(...args) {
        const startTime = performance.now();
        
        console.log(`[监控] 开始执行 ${name}`);
        
        return promiseFn.apply(this, args)
            .then(result => {
                const endTime = performance.now();
                const duration = endTime - startTime;
                console.log(`[监控] ${name} 执行成功,耗时: ${duration.toFixed(2)}ms`);
                return result;
            })
            .catch(error => {
                const endTime = performance.now();
                const duration = endTime - startTime;
                console.error(`[监控] ${name} 执行失败,耗时: ${duration.toFixed(2)}ms`, error);
                throw error;
            });
    };
}

// 使用性能监控
const monitoredFetchUserData = monitorPromise(fetchUserData, '获取用户数据');
const monitoredProcessUserData = monitorPromise(processUserData, '处理用户数据');

monitoredFetchUserData(123)
    .then(user => monitoredProcessUserData(user))
    .then(processedUser => {
        console.log('用户处理完成:', processedUser);
    })
    .catch(error => {
        console.error('用户处理失败:', error);
    });

7.7 常见陷阱和避免方法

了解常见的Promise陷阱并知道如何避免它们。

忘记处理Promise拒绝

示例:避免忘记处理Promise拒绝

// 不好的做法:忘记处理拒绝
const promise = fetchUserData(123);
// 如果fetchUserData失败,这里没有处理,可能导致未捕获的Promise拒绝

// 好的做法:始终处理拒绝
fetchUserData(123)
    .then(user => {
        console.log(user);
    })
    .catch(error => {
        console.error('获取用户数据失败:', error);
    });

// 或者使用async/await
async function handleUser() {
    try {
        const user = await fetchUserData(123);
        console.log(user);
    } catch (error) {
        console.error('获取用户数据失败:', error);
    }
}

在Promise链中吞掉错误

示例:避免吞掉错误

// 不好的做法:吞掉错误
fetchUserData(123)
    .then(user => {
        console.log(user);
    })
    .catch(error => {
        // 只记录错误,不重新抛出或处理
        console.log('记录错误到日志');
        // 错误被吞掉,上层调用者不知道发生了错误
    });

// 好的做法:正确处理错误
fetchUserData(123)
    .then(user => {
        console.log(user);
    })
    .catch(error => {
        console.log('记录错误到日志');
        // 显示用户友好的错误信息
        showErrorMessage('获取用户数据失败');
        // 如果需要,重新抛出错误
        throw error;
    });

// 或者在catch中返回默认值
fetchUserData(123)
    .then(user => {
        return user;
    })
    .catch(error => {
        console.error('获取用户数据失败:', error);
        // 返回默认用户数据
        return { id: 0, name: '默认用户' };
    })
    .then(user => {
        // 无论前面是否出错,都会执行到这里
        displayUser(user);
    });

提示:使用ESLint等工具可以帮助发现未处理的Promise错误。在开发过程中,始终要注意处理Promise的拒绝状态。