Promise链式调用

本章将详细介绍Promise的链式调用机制,帮助您掌握如何将多个异步操作串联起来,避免回调地狱,使代码更加清晰易读。

3.1 Promise链式调用的基本原理

Promise链式调用的核心在于then方法返回一个新的Promise对象,这使得我们可以将多个then方法串联起来。每个then方法处理前一个Promise的结果,并返回一个新的Promise供下一个then方法处理。

示例:Promise链式调用的基本结构

// Promise链式调用的基本结构
promise
    .then(result1 => {
        // 处理第一个Promise的结果
        return result1;
    })
    .then(result2 => {
        // 处理第二个Promise的结果
        return result2;
    })
    .then(result3 => {
        // 处理第三个Promise的结果
        console.log(result3);
    })
    .catch(error => {
        // 统一处理错误
        console.error(error);
    });

3.2 then方法的返回值

then方法的返回值决定了链中下一个Promise的状态:

返回普通值

如果then方法返回一个普通值(非Promise对象),下一个Promise会以这个值为结果进入fulfilled状态。

示例:返回普通值

Promise.resolve('初始值')
    .then(result => {
        console.log(result); // 输出: 初始值
        return '第二个值';
    })
    .then(result => {
        console.log(result); // 输出: 第二个值
        return 123;
    })
    .then(result => {
        console.log(result); // 输出: 123
    });

返回Promise对象

如果then方法返回一个Promise对象,下一个then方法会等待这个Promise对象的状态改变后再执行。

示例:返回Promise对象

Promise.resolve('初始值')
    .then(result => {
        console.log(result); // 输出: 初始值
        // 返回一个新的Promise对象
        return new Promise(resolve => {
            setTimeout(() => {
                resolve('异步结果');
            }, 1000);
        });
    })
    .then(result => {
        console.log(result); // 1秒后输出: 异步结果
    });

抛出异常

如果then方法中抛出异常,下一个Promise会进入rejected状态。

示例:抛出异常

Promise.resolve('初始值')
    .then(result => {
        console.log(result); // 输出: 初始值
        throw new Error('发生错误');
    })
    .then(result => {
        // 这个then不会执行
        console.log('这行不会执行');
    })
    .catch(error => {
        console.error(error.message); // 输出: 发生错误
    });

3.3 链式调用的实际应用

链式调用在实际开发中非常有用,特别是在需要按顺序执行多个异步操作时。

示例:用户登录流程

// 模拟用户登录流程
function login(username, password) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (username === 'admin' && password === '123456') {
                resolve({ userId: 1, username: 'admin' });
            } else {
                reject(new Error('用户名或密码错误'));
            }
        }, 1000);
    });
}

function getUserInfo(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (userId === 1) {
                resolve({ name: '管理员', email: 'admin@example.com' });
            } else {
                reject(new Error('用户不存在'));
            }
        }, 1000);
    });
}

function getUserPermissions(userInfo) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(['read', 'write', 'delete']);
        }, 1000);
    });
}

// 链式调用执行登录流程
login('admin', '123456')
    .then(user => {
        console.log('登录成功:', user);
        return getUserInfo(user.userId);
    })
    .then(userInfo => {
        console.log('获取用户信息:', userInfo);
        return getUserPermissions(userInfo);
    })
    .then(permissions => {
        console.log('用户权限:', permissions);
        // 登录流程完成
    })
    .catch(error => {
        console.error('登录失败:', error.message);
    });

3.4 链式调用中的值传递

在Promise链中,每个then方法都可以接收前一个Promise的结果,并可以将处理后的结果传递给下一个then方法。

示例:数据处理链

// 模拟数据处理链
function fetchData() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve([1, 2, 3, 4, 5]);
        }, 1000);
    });
}

function processData(data) {
    return new Promise(resolve => {
        setTimeout(() => {
            const processed = data.map(item => item * 2);
            resolve(processed);
        }, 1000);
    });
}

function filterData(data) {
    return new Promise(resolve => {
        setTimeout(() => {
            const filtered = data.filter(item => item > 5);
            resolve(filtered);
        }, 1000);
    });
}

// 链式调用处理数据
fetchData()
    .then(data => {
        console.log('原始数据:', data);
        return processData(data);
    })
    .then(processedData => {
        console.log('处理后数据:', processedData);
        return filterData(processedData);
    })
    .then(filteredData => {
        console.log('过滤后数据:', filteredData);
    })
    .catch(error => {
        console.error('处理过程中出错:', error);
    });

3.5 链式调用中的错误处理

在Promise链中,错误会沿着链向下传播,直到遇到catch方法进行处理。

示例:链式调用中的错误处理

Promise.resolve('初始值')
    .then(result => {
        console.log('第一步:', result);
        return '第二步结果';
    })
    .then(result => {
        console.log('第二步:', result);
        throw new Error('第三步出错');
    })
    .then(result => {
        // 这个then不会执行,因为前面抛出了错误
        console.log('第三步:', result);
    })
    .then(result => {
        // 这个then也不会执行
        console.log('第四步:', result);
    })
    .catch(error => {
        console.error('捕获到错误:', error.message);
        // 返回一个值继续链式调用
        return '错误处理后的值';
    })
    .then(result => {
        console.log('错误处理后:', result);
    });

3.6 链式调用的扁平化处理

为了避免回调地狱,Promise链提供了一种扁平化的方式来处理嵌套的异步操作。

示例:避免回调地狱

// 回调地狱的写法(不推荐)
getData(function(a) {
    getMoreData(a, function(b) {
        getEvenMoreData(b, function(c) {
            getEvenEvenMoreData(c, function(d) {
                console.log(d);
            });
        });
    });
});

// Promise链式调用的写法(推荐)
getData()
    .then(a => getMoreData(a))
    .then(b => getEvenMoreData(b))
    .then(c => getEvenEvenMoreData(c))
    .then(d => {
        console.log(d);
    })
    .catch(error => {
        console.error('处理过程中出错:', error);
    });

3.7 链式调用的最佳实践

在使用Promise链式调用时,有一些最佳实践可以帮助我们写出更好的代码:

保持链的清晰性

  • 每个then方法只做一件事
  • 合理使用箭头函数简化代码
  • 避免在then方法中嵌套Promise

正确处理错误

  • 在链的末尾添加catch方法
  • 在必要时使用catch方法处理局部错误
  • 避免吞掉错误(即不处理也不传递)

示例:链式调用最佳实践

// 好的实践
fetchUser(userId)
    .then(user => validateUser(user))
    .then(validUser => fetchUserPermissions(validUser))
    .then(permissions => checkPermission(permissions, 'admin'))
    .then(hasPermission => {
        if (hasPermission) {
            return performAdminAction();
        } else {
            throw new Error('权限不足');
        }
    })
    .then(result => {
        console.log('操作成功:', result);
    })
    .catch(error => {
        console.error('操作失败:', error.message);
    });

// 避免的做法
fetchUser(userId)
    .then(user => {
        return validateUser(user)
            .then(validUser => {
                return fetchUserPermissions(validUser)
                    .then(permissions => {
                        // 过度嵌套,难以维护
                        return checkPermission(permissions, 'admin');
                    });
            });
    });

3.8 链式调用与async/await

虽然Promise链解决了回调地狱问题,但ES2017引入的async/await语法提供了更加简洁的异步代码编写方式。

示例:Promise链与async/await对比

// Promise链式调用
function fetchUserData(userId) {
    return fetchUser(userId)
        .then(user => fetchUserPosts(user.id))
        .then(posts => {
            return { user, posts };
        })
        .catch(error => {
            console.error('获取用户数据失败:', error);
            return null;
        });
}

// async/await写法
async function fetchUserData(userId) {
    try {
        const user = await fetchUser(userId);
        const posts = await fetchUserPosts(user.id);
        return { user, posts };
    } catch (error) {
        console.error('获取用户数据失败:', error);
        return null;
    }
}

提示:虽然async/await语法更加简洁,但理解Promise链式调用的原理仍然非常重要,因为async/await本质上还是基于Promise的。