← JavaScript Promise JavaScript Promise.any() →

JavaScript Promise链式调用

原创 2026-03-14 JavaScript 已有人查阅

JavaScript Promise链式调用:优雅处理连续异步任务

项目开发中经常遇到这种情况:先获取用户信息,再用用户ID查订单,之后用订单ID查详情。这种连续异步操作如果用回调函数写,很快就会陷入回调地狱。而Promise链式调用(Promise Chaining)就是解决这个问题的利器。

什么是Promise链式调用?

简单说,就是在then()方法里返回一个新的Promise,让下一个then()等待这个新Promise完成后再执行。这样就能把多个异步操作像链条一样串起来。

// 基本结构
doFirstThing()
    .then(result => doSecondThing(result))
    .then(result => doThirdThing(result))
    .then(finalResult => console.log('最终结果:', finalResult))
    .catch(error => console.error('出错了:', error));

链式调用的两种写法

写法一:显式返回新Promise

代码号学习: 用户登录流程
function login(username, password) {
    return new Promise((resolve, reject) => {
        // 模拟登录验证
        setTimeout(() => {
            if (username === 'admin' && password === '123') {
                resolve({ userId: 1, token: 'abc123' });
            } else {
                reject('用户名或密码错误');
            }
        }, 1000);
    });
}

function getUserInfo(userId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ 
                userId: userId, 
                name: '张三', 
                email: 'zhangsan@example.com' 
            });
        }, 500);
    });
}

function getUserOrders(userId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve([
                { id: 1001, amount: 299 },
                { id: 1002, amount: 599 }
            ]);
        }, 800);
    });
}

// 链式调用
login('admin', '123')
    .then(user => {
        console.log('登录成功:', user);
        return getUserInfo(user.userId);
    })
    .then(userInfo => {
        console.log('用户信息:', userInfo);
        return getUserOrders(userInfo.userId);
    })
    .then(orders => {
        console.log('用户订单:', orders);
        console.log('订单总数:', orders.length);
    })
    .catch(error => {
        console.error('处理失败:', error);
    });

写法二:直接传递函数引用

如果下一个函数直接接受上一个结果作为参数,可以简化写法:

// 前提:getUserInfo 接受 userId 参数
login('admin', '123')
    .then(user => getUserInfo(user.userId))
    .then(userInfo => getUserOrders(userInfo.userId))
    .then(orders => {
        console.log('最终拿到的订单:', orders);
    });

// 更简洁的写法(参数自动传递)
login('admin', '123')
    .then(getUserInfo)  // 自动传入 user 对象
    .then(getUserOrders) // 自动传入 userInfo
    .then(orders => {
        console.log('最终拿到的订单:', orders);
    });

个人经验:第二种写法代码更简洁,但前提是函数设计要合理——刚好接收上一个Promise返回的值作为参数。我一般会在项目里统一这种风格,让代码更易读。

链式调用的实际应用场景

场景1:数据处理流水线

// 读取文件 -> 处理数据 -> 保存结果
function readFile(filePath) {
    return new Promise((resolve) => {
        console.log(`读取文件: ${filePath}`);
        setTimeout(() => {
            resolve('原始数据: 1,2,3,4,5');
        }, 1000);
    });
}

function processData(data) {
    return new Promise((resolve) => {
        console.log('处理数据中...');
        setTimeout(() => {
            const numbers = data.split(':')[1].trim().split(',').map(Number);
            const processed = numbers.map(n => n * 2);
            resolve(`处理结果: ${processed.join(',')}`);
        }, 800);
    });
}

function saveResult(result) {
    return new Promise((resolve) => {
        console.log('保存结果...');
        setTimeout(() => {
            console.log('保存成功:', result);
            resolve('操作完成');
        }, 600);
    });
}

// 链式执行
readFile('/data/info.txt')
    .then(processData)
    .then(saveResult)
    .then(message => {
        console.log('最终状态:', message);
    });

场景2:分页数据获取

function fetchPage(pageNum) {
    return new Promise((resolve) => {
        console.log(`获取第${pageNum}页数据`);
        setTimeout(() => {
            // 模拟分页数据
            const data = Array.from({ length: 10 }, (_, i) => ({
                id: (pageNum - 1) * 10 + i + 1,
                name: `商品${(pageNum - 1) * 10 + i + 1}`
            }));
            resolve({
                page: pageNum,
                data: data,
                hasMore: pageNum < 5
            });
        }, 600);
    });
}

function loadAllPages() {
    let allData = [];
    
    function loadNext(pageNum) {
        return fetchPage(pageNum)
            .then(result => {
                allData = allData.concat(result.data);
                console.log(`第${pageNum}页加载完成,累计${allData.length}条`);
                
                if (result.hasMore) {
                    return loadNext(pageNum + 1);
                } else {
                    return allData;
                }
            });
    }
    
    return loadNext(1);
}

// 使用
loadAllPages()
    .then(allData => {
        console.log('所有数据加载完成,总数:', allData.length);
    });

需要注意的坑:多个then不是链式调用

新手常犯的错误:在一个Promise上多次调用then,这不是链式调用,而是添加多个独立的处理器:

let promise = new Promise((resolve) => {
    resolve(5);
});

// 错误写法:这不是链式调用!
promise.then(result => {
    console.log('处理器1:', result);
    return result * 2;
});

promise.then(result => {
    console.log('处理器2:', result); // 还是5,不是10
    return result * 3;
});

promise.then(result => {
    console.log('处理器3:', result); // 还是5,不是15
    return result * 4;
});

// 输出:
// 处理器1: 5
// 处理器2: 5  
// 处理器3: 5

正确写法:要链式调用,必须连续使用then:

promise
    .then(result => {
        console.log('处理器1:', result);
        return result * 2;
    })
    .then(result => {
        console.log('处理器2:', result); // 现在是10
        return result * 3;
    })
    .then(result => {
        console.log('处理器3:', result); // 现在是30
        return result * 4;
    });
// 输出:
// 处理器1: 5
// 处理器2: 10
// 处理器3: 30

错误处理的实践

链式调用中,错误会沿着链条往下传递,直到被catch捕获:

function step1() {
    return new Promise((resolve) => {
        console.log('步骤1执行');
        resolve(10);
    });
}

function step2(num) {
    return new Promise((resolve, reject) => {
        console.log('步骤2执行');
        if (num > 5) {
            reject('步骤2出错:数字太大');
        } else {
            resolve(num * 2);
        }
    });
}

function step3(num) {
    return new Promise((resolve) => {
        console.log('步骤3执行');
        resolve(num + 10);
    });
}

// 错误会被后面的catch捕获
step1()
    .then(step2)
    .then(step3)
    .then(final => {
        console.log('最终结果:', final);
    })
    .catch(error => {
        console.log('捕获到错误:', error); // 步骤2出错会直接跳到这里
    });

// 也可以在不同位置捕获错误
step1()
    .then(step2)
    .catch(error => {
        console.log('步骤2出错,但可以恢复');
        return 20; // 返回一个默认值继续执行
    })
    .then(step3)
    .then(final => {
        console.log('最终结果:', final);
    })
    .catch(error => {
        console.log('之后的错误处理');
    });

个人建议:在较长的Promise链中,可以考虑在中间加catch来处理可恢复的错误,给用户更好的体验。比如某个步骤出错时,用默认值继续,而不是整个流程都中断。

链式调用与async/await的对比

Promise链式调用是async/await的基础,两者可以混用:

// Promise链式调用
function loadUserData(userId) {
    return getUser(userId)
        .then(user => getPosts(user.id))
        .then(posts => {
            // 在链中混合async/await
            const comments = await getComments(posts[0].id);
            return { user, posts, comments };
        })
        .catch(error => handleError(error));
}

// 纯async/await写法更直观
async function loadUserData(userId) {
    try {
        const user = await getUser(userId);
        const posts = await getPosts(user.id);
        const comments = await getComments(posts[0].id);
        return { user, posts, comments };
    } catch (error) {
        handleError(error);
    }
}

个人观点:新代码我更倾向用async/await,但理解Promise链式调用很重要,因为:

  1. 很多老项目还在用Promise链

  2. Promise.all、Promise.race等方法返回的就是Promise

  3. async/await本质上还是Promise,只是语法糖

课程知识要点

  • Promise链式调用:在then()中返回新Promise,实现异步操作顺序执行

  • 两种写法:显式返回Promise vs 直接传递函数引用

  • 错误传递:链中任何环节出错,都会跳到最近的catch

  • 与多个then的区别:链式调用是连续执行,多个then是并行添加处理器

  • 实际应用:数据流水线、分页加载、依赖请求等场景

  • 与async/await关系:链式调用是基础,async/await是更现在的写法

← JavaScript Promise JavaScript Promise.any() →
分享笔记 (共有 篇笔记)
验证码:
微信公众号