【c++】手撸C++ Promise:从零实现通用异步回调组件,支持链式调用+异常安全

文章目录

在C++开发中,异步编程(如模拟网络请求、文件读写)常面临"回调地狱""状态管理混乱""异常难以捕获"等问题。前端Promise的出现完美解决了这些痛点,但C++缺乏原生支持。本文将带大家从零实现一个 通用C++ Promise组件 ------纯标准库依赖,支持强类型结果/错误传递、链式调用、异常安全,可直接集成到任何C++项目中。

一、Promise核心设计目标

一个实用的Promise组件需满足以下核心需求:

  1. 状态管理:支持3种互斥状态(Pending/已完成Fulfilled/已拒绝Rejected),状态一旦转换不可逆;
  2. 回调机制:支持注册成功(onFulfilled)/失败(onRejected)回调,状态变更时自动触发;
  3. 链式调用:支持多步异步操作串联,结果可无缝转换,错误自动透传;
  4. 强类型支持 :适配任意结果类型(如std::vector<int>、自定义结构体)和错误类型(如错误码、空类型);
  5. 异常安全:捕获异步操作中的异常,自动转为失败状态,避免程序崩溃;
  6. 无依赖:仅使用C++标准库,不引入第三方框架。

二、核心实现原理

Promise的本质是"状态机+回调链":

  • 状态机:通过枚举管控3种状态,确保状态转换的唯一性和不可逆性;
  • 回调链:通过then方法注册回调,链式调用时生成新Promise,实现结果透传与转换;
  • 类型适配:通过模板参数ResultT(成功结果类型)和ErrorT(失败错误类型)支持任意类型。

三、完整源代码实现

cpp 复制代码
#ifndef PROMISE_H
#define PROMISE_H

#include <functional>
#include <utility>
#include <stdexcept>

// 空类型标识:用于"失败时无需传递额外错误信息"的场景
struct undefined {};

template <typename ResultT, typename ErrorT = undefined>
class Promise {
public:
    // 回调函数类型定义(强类型约束)
    using ResolveCallback = std::function<void(ResultT)>;  // 成功回调:接收ResultT类型结果
    using RejectCallback = std::function<void(ErrorT)>;    // 失败回调:接收ErrorT类型错误
    using Executor = std::function<void(ResolveCallback, RejectCallback)>;  // 执行器:封装异步逻辑

    // 构造函数:接收执行器(异步任务),初始化状态为Pending
    explicit Promise(Executor executor) : m_state(State::Pending) {
        // 封装框架内部的resolve逻辑:状态转换+结果存储+触发回调
        ResolveCallback resolve = [this](ResultT result) {
            if (m_state != State::Pending) return;  // 状态已变更,忽略重复调用
            m_result = std::move(result);           // 移动语义:避免拷贝,提升性能
            m_state = State::Fulfilled;             // 状态转为"已完成"
            triggerCallbacks();                     // 触发成功回调
        };

        // 封装框架内部的reject逻辑:状态转换+错误存储+触发回调
        RejectCallback reject = [this](ErrorT error) {
            if (m_state != State::Pending) return;  // 状态已变更,忽略重复调用
            m_error = std::move(error);             // 移动语义存储错误信息
            m_state = State::Rejected;              // 状态转为"已拒绝"
            triggerCallbacks();                     // 触发失败回调
        };

        // 执行用户传入的异步逻辑,捕获所有异常并转为reject
        try {
            executor(std::move(resolve), std::move(reject));
        } catch (...) {
            reject(ErrorT());  // 异常时自动触发失败回调
        }
    }

    // 链式调用接口:接收成功回调,返回新Promise(支持结果类型转换)
    template <typename NewResultT>
    Promise<NewResultT, ErrorT> then(std::function<NewResultT(const ResultT&)> onFulfilled) {
        // 返回新Promise,将当前Promise的结果传递给新Promise
        return Promise<NewResultT, ErrorT>([this, onFulfilled](
            ResolveCallback newResolve, RejectCallback newReject
        ) {
            // 注册当前Promise的成功回调:转换结果并触发新Promise的resolve
            m_onFulfilled = [onFulfilled, newResolve, newReject](const ResultT& result) {
                try {
                    NewResultT newResult = onFulfilled(result);  // 结果类型转换(如T→U)
                    newResolve(std::move(newResult));            // 触发新Promise的成功状态
                } catch (...) {
                    newReject(ErrorT());                          // 转换失败时触发新Promise的失败状态
                }
            };

            // 注册当前Promise的失败回调:错误透传(无需额外处理,直接传递给新Promise)
            m_onRejected = [newReject](const ErrorT& error) {
                newReject(error);
            };

            triggerCallbacks();  // 若当前Promise已完成,立即触发回调
        });
    }

    // 终结回调接口:接收成功+失败回调,无返回值(用于链式调用的最后一步)
    void then(
        std::function<void(const ResultT&)> onFulfilled,
        RejectCallback onRejected = {}  // 失败回调可选,默认空
    ) {
        m_onFulfilled = std::move(onFulfilled);
        m_onRejected = std::move(onRejected);
        triggerCallbacks();  // 若状态已完成,立即触发回调
    }

private:
    // Promise状态枚举(严格互斥)
    enum class State {
        Pending,    // 初始状态:异步操作未完成
        Fulfilled,  // 成功状态:异步操作完成,有结果
        Rejected    // 失败状态:异步操作出错,有错误信息
    } m_state;

    ResultT m_result;          // 成功时存储的结果(仅Fulfilled状态有效)
    ErrorT m_error;            // 失败时存储的错误(仅Rejected状态有效)
    std::function<void(const ResultT&)> m_onFulfilled;  // 外部注册的成功回调
    std::function<void(const ErrorT&)> m_onRejected;    // 外部注册的失败回调

    // 触发回调:根据当前状态调用对应的回调函数
    void triggerCallbacks() {
        if (m_state == State::Fulfilled && m_onFulfilled) {
            m_onFulfilled(m_result);  // 成功状态:调用成功回调
        } else if (m_state == State::Rejected && m_onRejected) {
            m_onRejected(m_error);    // 失败状态:调用失败回调
        }
    }
};

#endif // PROMISE_H

四、核心模块逐行解析

1. 空类型undefined:解决"无错误信息"场景

cpp 复制代码
struct undefined {};
  • 作用:当异步操作失败时无需传递额外错误信息(如"URL不存在""文件未找到"),用undefined作为ErrorT模板参数,替代C++中void作为模板参数的尴尬;
  • 优势:保持模板类型统一性,避免单独处理void类型,简化代码逻辑。

2. 模板参数设计:强类型适配任意场景

cpp 复制代码
template <typename ResultT, typename ErrorT = undefined>
class Promise { ... };
  • ResultT:成功时的结果类型(如intstd::stringstd::vector<Product>);
  • ErrorT:失败时的错误类型(默认undefined,可自定义为int错误码、std::string错误信息);
  • 强类型约束:回调函数参数类型与模板参数强制匹配,编译器自动校验,避免类型错误。

3. 状态机实现:确保状态转换安全

cpp 复制代码
enum class State { Pending, Fulfilled, Rejected } m_state;
  • 状态特性:初始为Pending,仅能转换为FulfilledRejected,一旦转换不可逆;
  • 安全防护:resolvereject回调中先判断m_state == Pending,避免重复触发(如多次调用resolve无效)。

4. 构造函数:注入异步逻辑

cpp 复制代码
explicit Promise(Executor executor) : m_state(State::Pending) { ... }
  • 核心逻辑:接收用户传入的异步任务(Executor),封装框架内部的resolvereject,并传递给用户;
  • 异常捕获:用try-catch捕获异步任务中的所有异常,自动转为reject状态,避免程序崩溃;
  • 移动语义:std::move(resolve)/std::move(reject)减少拷贝成本,尤其适合大数据场景(如std::vector)。

5. 链式调用then:支持结果转换与错误透传

cpp 复制代码
template <typename NewResultT>
Promise<NewResultT, ErrorT> then(...) { ... }
  • 结果转换:上一个Promise的ResultT可通过回调函数转换为新的NewResultT(如std::vector<Product>int(商品总数));
  • 错误透传:上一个Promise的错误自动传递到下一个Promise,无需重复编写错误处理逻辑;
  • 即时触发:若上一个Promise已完成(如同步返回结果),新then注册后立即触发回调,兼容同步/异步场景。

6. 终结then:处理最终结果

cpp 复制代码
void then(std::function<void(const ResultT&)> onFulfilled, RejectCallback onRejected = {}) { ... }
  • 用途:链式调用的最后一步,无需返回新Promise(如打印结果、提示错误);
  • 灵活配置:失败回调可选,若仅需处理成功场景,可只传递成功回调。

五、使用示例:覆盖常见异步场景

示例1:基础异步操作(无错误信息)

模拟网络请求,成功返回字符串,失败无错误信息:

cpp 复制代码
#include "Promise.h"
#include <iostream>
#include <chrono>
#include <thread>

// 模拟网络请求:2秒后返回成功结果
Promise<std::string, undefined> mockNetworkRequest(const std::string& url) {
    return Promise<std::string, undefined>([url](auto resolve, auto reject) {
        std::thread([url, resolve, reject]() {
            // 模拟网络延迟2秒
            std::this_thread::sleep_for(std::chrono::seconds(2));
            if (url == "https://api.success.com") {
                resolve("请求成功!返回数据:{name: 'MiniDataX'}");
            } else {
                reject(undefined());  // 失败无错误信息
            }
        }).detach();
    });
}

int main() {
    std::cout << "发起请求..." << std::endl;
    mockNetworkRequest("https://api.success.com")
    .then(
        // 成功回调
        [](const std::string& data) {
            std::cout << "成功:" << data << std::endl;
        },
        // 失败回调
        [](undefined) {
            std::cout << "失败:请求地址不存在" << std::endl;
        }
    );

    // 等待异步操作完成(避免主线程退出)
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 0;
}

示例2:链式调用(结果类型转换)

模拟"请求数据→解析数据→计算结果"的多步异步流程:

cpp 复制代码
#include "Promise.h"
#include <iostream>
#include <vector>

// 步骤1:模拟请求商品列表(ResultT=std::vector<float>,商品价格列表)
Promise<std::vector<float>, int> fetchProductPrices() {
    return Promise<std::vector<float>, int>([](auto resolve, auto reject) {
        // 模拟异步请求
        std::vector<float> prices = {99.9f, 199.9f, 299.9f};
        if (!prices.empty()) {
            resolve(prices);  // 成功返回价格列表
        } else {
            reject(404);      // 失败返回错误码404
        }
    });
}

int main() {
    fetchProductPrices()
    // 步骤2:转换结果:计算总价(std::vector<float> → float)
    .then([](const std::vector<float>& prices) {
        float total = 0.0f;
        for (float p : prices) total += p;
        return total;
    })
    // 步骤3:最终处理:打印总价(float → 无返回值)
    .then([](float total) {
        std::cout << "商品总价:" << total << "元" << std::endl;
    }, [](int errCode) {
        std::cout << "请求失败,错误码:" << errCode << std::endl;
    });

    return 0;
}

示例3:自定义错误类型(字符串错误信息)

失败时返回字符串错误信息,更直观:

cpp 复制代码
// 模拟文件读取:成功返回文件内容,失败返回错误信息(ErrorT=std::string)
Promise<std::string, std::string> readFile(const std::string& filename) {
    return Promise<std::string, std::string>([filename](auto resolve, auto reject) {
        if (filename == "config.txt") {
            resolve("数据库地址:localhost:3306");  // 成功返回文件内容
        } else {
            reject("文件不存在:" + filename);       // 失败返回字符串错误
        }
    });
}

int main() {
    readFile("config.txt")
    .then(
        [](const std::string& content) {
            std::cout << "文件内容:" << content << std::endl;
        },
        [](const std::string& errMsg) {
            std::cout << "读取失败:" << errMsg << std::endl;
        }
    );
    return 0;
}

六、编译与运行

编译命令(GCC/Clang)

bash 复制代码
g++ main.cpp -o promise-demo -std=c++11 -pthread
  • 依赖:C++11及以上标准(支持std::function、lambda、移动语义);
  • -pthread:若使用std::thread模拟异步,需链接线程库。

运行效果

示例1运行输出:

复制代码
发起请求...
成功:请求成功!返回数据:{name: 'MiniDataX'}

示例2运行输出:

复制代码
商品总价:599.7元

示例3运行输出:

复制代码
文件内容:数据库地址:localhost:3306

七、核心优势与扩展方向

核心优势

  1. 通用灵活:支持任意结果/错误类型,适配网络请求、文件读写、数据库操作等所有异步场景;
  2. 易用性强:链式调用语法简洁,类似前端Promise,降低异步编程门槛;
  3. 异常安全:自动捕获异步操作中的异常,避免程序崩溃;
  4. 无依赖:仅使用C++标准库,可直接集成到嵌入式、桌面、服务器等任何C++项目;
  5. 性能优秀:移动语义减少拷贝,无额外性能开销。

扩展方向

  1. 支持catch方法 :单独注册失败回调(类似前端promise.catch),简化错误处理;
  2. 支持finally方法:无论成功/失败,都执行收尾操作(如释放资源);
  3. 线程安全 :当前实现为单线程安全,若需多线程并发调用,可添加std::mutex保护状态和回调;
  4. 超时机制 :添加超时检测,超时后自动触发reject
  5. Promise组合 :实现Promise::all(所有Promise成功才返回)、Promise::race(第一个完成的Promise返回)。

八、总结

本文实现的C++ Promise组件,以"状态机+回调链"为核心,用极简代码实现了前端Promise的核心特性,同时兼顾C++的强类型和性能优势。该组件无需第三方依赖,用法简洁,可直接解决C++异步编程中的"回调地狱"和"状态管理混乱"问题。

无论是初学者学习异步编程思想,还是开发者在项目中处理异步场景,这个Promise组件都是一个实用的工具。后续可根据实际需求扩展线程安全、超时机制等功能,进一步提升其通用性。

如果你在使用中遇到问题或有优化建议,欢迎在评论区交流~

相关推荐
无心水1 小时前
【Python实战进阶】1、Python高手养成指南:四阶段突破法从入门到架构师
开发语言·python·django·matplotlib·gil·python实战进阶·python工程化实战进阶
学困昇2 小时前
C++中的异常
android·java·c++
抱琴_2 小时前
【Vue3】大屏性能优化黑科技:Vue 3 中实现请求合并,让你的大屏飞起来!
前端·vue.js
不会玩电脑的Xin.2 小时前
HTML + CSS
前端·css·html
q***31832 小时前
Windows安装Rust环境(详细教程)
开发语言·windows·rust
hadage2332 小时前
--- JavaScript 的一些常用语法总结 ---
java·前端·javascript
彭于晏爱编程2 小时前
🍭🍭🍭升级 AntD 6:做第一个吃螃蟹的人
前端
合作小小程序员小小店2 小时前
桌面安全开发,桌面二进制%恶意行为拦截查杀%系统安全开发3.0,基于c/c++语言,mfc,win32,ring3,dll,hook,inject,无数据库
c语言·开发语言·c++·安全·系统安全