promise-logic -- 声明式 Promise 逻辑组合

91 阅读7分钟

开发者应专注于业务逻辑,而非 Promise API 的细节

传统 Promise 组合(如 Promise.allPromise.race)的命名与语义不够直观,尤其在复杂异步场景下,代码可读性迅速下降。

promise-logic 通过逻辑门(Logic Gate) 的方式,将异步组合抽象为 andorxor 等逻辑操作,使代码语义清晰、逻辑自解释。

示例场景:电商订单处理

import { PromiseLogic } from 'promise-logic';

// 订单处理流程
async function createOrder() {
  // 统一错误类型
  const PAYMENT_OR_INVENTORY_ERROR = 'PAYMENT_OR_INVENTORY_ERROR';
  const ORDER_ERROR = 'ORDER_ERROR';
  const LOGISTICS_ERROR = 'LOGISTICS_ERROR';
  const COUPON_ERROR = 'COUPON_ERROR';

  // 从 PromiseLogic 解构 and, or 方法,以获得更简洁的语法并理解当前场景的逻辑关系
  const { and, or } = PromiseLogic;

  try {
    // 执行支付和库存操作(两者必须都成功)
    const [
      [paymentResult, inventoryResult], // 支付和库存操作结果
      logistics,                        // 物流操作结果
      coupon                            // 优惠券操作结果
    ] = await and([
      and([paymentAPI(), inventoryAPI()], {
        errorType: PAYMENT_OR_INVENTORY_ERROR, // 自定义支付或库存错误类型
      }),
      or([oneLogisticsAPI(), twoLogisticsAPI()], {
        errorType: LOGISTICS_ERROR, // 自定义物流错误类型
      }),
      or([couponAPI1(), couponAPI2()], {
        errorType: COUPON_ERROR, // 自定义优惠券错误类型
      })
    ], {
      errorType: ORDER_ERROR,          // 自定义订单错误类型
      errorMessage: '订单创建失败'       // 自定义订单错误信息
    });

    // 订单创建成功,返回支付结果、库存结果、物流结果和优惠券结果
    return {
      payment: paymentResult,
      inventory: inventoryResult,
      logistics: logistics,
      coupon: coupon,
      status: 'success'
    };
  } catch (error) {
    // 分析错误类型
    switch (error.type) {
      case ORDER_ERROR:
        return {
          status: 'error',
          errorType: 'order_error',
          message: '订单创建失败',
          details: error
        };
      case PAYMENT_OR_INVENTORY_ERROR:
        return {
          status: 'error',
          errorType: 'payment_or_inventory_failed',
          message: '支付或库存操作失败',
          details: error
        };
      case LOGISTICS_ERROR:
        return {
          status: 'error',
          errorType: 'logistics_unavailable',
          message: '所有物流服务均不可用',
          details: error
        };
      case COUPON_ERROR:
        return {
          status: 'error',
          errorType: 'coupon_error',
          message: '所有优惠券均不可用',
          details: error
        };
      default:
        return {
          status: 'error',
          errorType: 'default_error',
          message: '订单创建过程中发生未知错误',
          details: error
        };
    }
  }
}

可以看到,代码结构与业务规则完全一致,代码即是文档,逻辑自解释。


功能特性

1. 逻辑语义化

  • and:所有任务必须成功(等价于 Promise.all
  • or:至少一个任务成功(等价于 Promise.any
  • xor有且仅有一个任务成功
  • nand:不是所有任务都成功(至少一个失败)
  • nor:所有任务都失败(没有任务成功)
  • xnor:所有任务都成功或都失败(状态相同)
  • not:反转单个 Promise 的结果
  • majority:多数任务成功

2. 零依赖

仅依赖原生 Promise,无额外运行时依赖。

3. 全测试覆盖

所有逻辑门均经过严格单元测试,确保行为符合预期。

4. 错误分类明确

  • PromiseLogicError 统一错误类型
  • error.type 区分具体逻辑错误(如 'XOR_ERROR'
  • 支持自定义错误类型和消息

5. 超时控制

  • maxTimer:为任何 Promise 操作添加超时功能(单位:毫秒)
  • 支持自定义超时错误信息

说明:超时后会立即中断当前 Promise 链的执行,跳转到错误处理,但不会取消已经开始的底层异步操作(如网络请求、文件读写等)。

6. 扩展操作

  • allFulfilled:按顺序返回所有成功结果,当存在成功结果时会立即尝试返回
  • allRejected:按顺序返回所有失败结果,当存在失败结果时会立即尝试返回
  • allSettled:返回所有结果(包括成功和失败)

安装

npm install promise-logic

快速开始

基础使用示例

安全审计(XOR 场景)

import { PromiseLogic } from 'promise-logic';

// 执行 XOR 逻辑:有且仅有一个成功
PromiseLogic.xor([
  biometricAuth(), // 生物识别
  hardwareKeyAuth() // 硬件密钥
])
  .then((result) => {
    console.log('成功获取数据:', result);
  })
  .catch((error) => {
    if (error.type === 'XOR_ERROR') {
      console.error('生物识别和硬件密钥发生冲突');
    } else {
      console.error('网络错误:', error);
    }
  });

多数决决策(Majority 场景)

import { PromiseLogic } from 'promise-logic';

const services = [
  fetch('https://api.node1.com/vote'),
  fetch('https://api.node2.com/vote'),
  fetch('https://api.node3.com/vote')
];

// 自定义阈值 0.6 (60%)
PromiseLogic.majority(services, { max: 0.6 })
  .then((results) => {
    console.log('达到自定义阈值,成功结果:', results);
  })
  .catch((error) => {
    console.error('未达到自定义阈值:', error);
  });

超时控制

import { PromiseLogic } from 'promise-logic';

// 执行带自定义超时错误信息的操作
PromiseLogic.and([
  Promise.resolve(1),
  new Promise((resolve) => setTimeout(resolve, 3000)), // 3秒操作
  Promise.resolve(3)
])
  .maxTimer(2000, '自定义超时错误:操作在 2000ms 内未完成') // 2秒超时
  .then((result) => {
    console.log('操作在超时时间内完成:', result);
  })
  .catch((error) => {
    console.error('操作超时:', error.message);
  });

扩展操作

import { PromiseLogic } from 'promise-logic';

const operations = [
  Promise.resolve('success1'),
  Promise.reject('error1'),
  Promise.resolve('success2'),
  Promise.reject('error2')
];

// 获取所有成功结果(一有成功就立即返回)
PromiseLogic.allFulfilled(operations).then((results) => {
  console.log('成功结果:', results); // ['success1', 'success2']
});

// 获取所有失败结果(一有失败就立即返回)
PromiseLogic.allRejected(operations).then((errors) => {
  console.log('失败结果:', errors); // ['error1', 'error2']
});

// 获取所有结果(包括成功和失败)
PromiseLogic.allSettled(operations).then((results) => {
  console.log('所有结果:', results);
});

自定义错误类型和消息

import { PromiseLogic } from 'promise-logic';

// 自定义错误类型
const CUSTOM_ERROR_TYPE = 'CUSTOM_ERROR';

// 自定义错误消息
const CUSTOM_ERROR_MESSAGE = 'Custom error message';

// 使用自定义错误类型和消息
PromiseLogic.and([Promise.resolve('success1'), Promise.reject('error1')], {
  errorType: CUSTOM_ERROR_TYPE,
  errorMessage: CUSTOM_ERROR_MESSAGE
})
  .then((results) => {
    console.log(results);
  })
  .catch((error) => {
    if (error.type === CUSTOM_ERROR_TYPE) {
      console.error(error); // 输出:Custom error message
    } else {
      console.error(error);
    }
  });

使用工厂函数

工厂函数允许你创建自定义命名的 PromiseLogic 方法:

import { createPromiseLogic } from 'promise-logic';

// 创建自定义命名的实例
const logic = createPromiseLogic({
  prefix: 'api_',
  suffix: '_call',
  rename: {
    and: 'all',
    or: 'any',
    xor: 'exclusive'
  }
});

// 使用自定义命名的方法
logic.api_all_call([fetch('/api/users'), fetch('/api/posts')]);
logic.api_any_call([fetch('/api/cache'), fetch('/api/database')]);

TypeScript 支持

import { PromiseLogic } from 'promise-logic/typescript';

// 类型推断
PromiseLogic.and([Promise.resolve(1), Promise.resolve(2)]).then(
  (results: number[]) => {
    console.log(results);
  }
);

// 类型断言
PromiseLogic.and<number>([Promise.resolve(1), Promise.resolve(2)]);

动态逻辑嵌套

import { PromiseLogic } from 'promise-logic';


// 动态嵌套示例:根据用户等级调整物流策略
const orderFlow = async (userLevel) => {
  // VIP用户:双高可用物流保障(至少一个成功)
  // 普通用户:优先标准物流,失败则降级为经济物流(至少一个成功)
  const logisticsStrategy = userLevel === 'VIP'
    ? PromiseLogic.or([premiumLogistics(), backupLogistics()])
    : PromiseLogic.or([standardLogistics(), economyLogistics()]);

  return await PromiseLogic.and([
    PromiseLogic.and([paymentAPI(), inventoryAPI()]), // 支付+库存原子操作
    logisticsStrategy,                                // 物流策略
    couponValidation()                                // 优惠券验证
  ]);
};

API 参考

API说明
and所有 Promise 成功,返回结果数组;任一失败则整体失败,等价原生 Promise.all
or至少一个 Promise 成功,返回首个成功结果;全部失败则整体失败,等价原生 Promise.any
xor有且仅有一个 Promise 成功,返回该结果;否则抛出 XOR_ERROR
nand不是所有 Promise 都成功(至少一个失败),返回成功结果数组;全部成功则整体失败。
nor所有 Promise 都失败(没有任务成功),返回空数组;任一成功则整体失败。
xnor所有 Promise 都成功或都失败(状态相同),返回成功结果数组;否则抛出 XNOR_ERROR
not反转单个 Promise 的结果:成功变失败,失败变成功。
majority超过指定阈值的 Promise 成功,返回成功结果数组;否则整体失败。接受 options 参数,其中 max 属性可自定义阈值(默认:0.5),范围:[0,1]。
allFulfilled返回所有成功结果作为数组,忽略失败结果。存在成功结果立即返回,同时保持输入输出顺序一致。
allRejected返回所有失败结果作为数组,忽略成功结果。存在失败结果立即返回,同时保持输入输出顺序一致。
allSettled返回所有结果(包括成功和失败)作为数组,等价原生 Promise.allSettled
race返回第一个完成的 Promise 结果(无论成功或失败),等价原生 Promise.race
maxTimer为任何 Promise 操作添加超时功能(单位:毫秒)。支持自定义超时错误信息。

更新日志

v2.9.0

错误机制完善

  • 添加逻辑门的 errorTypeerrorMessage 参数,支持自定义错误类型和消息,进一步贴合业务场景
  • 优化现有的错误类型体系,确保 TypeScript 类型定义正确
  • 优化 TypeScript 类型兼容问题
  • 优化 allSettled 门的返回类型错误信息
  • 完善测试脚本,覆盖大部分了 promise 组合逻辑边界和刁钻的场景问题,确保逻辑门的运行更加稳定
  • 优化了逻辑门的执行效率,减少了不必要的 Promise 包装

v2.8.0

  • 从底层优化 allFulfilledallRejected 实现逻辑,存在结果便立即返回,同时保持输入和输出顺序一致
  • 新增链式超时控制自定义错误信息:可以在 maxTimer 方法中自定义超时错误信息
  • 类型修复:修复 TypeScript 版本的类型声明问题
  • 测试完善:添加 allFulfilledallRejectedmaxTimer 完整测试用例
  • 代码重构:改进代码结构,提高可维护性

贡献指南

1. 开发环境

git clone https://github.com/xier123456/promise-logic.git
cd promise-logic
npm install

2. 测试

npm test

3. 提交规范

  • 提交信息需包含 feat:(新功能)、fix:(修复)、docs:(文档)前缀。
  • Pull Request 需附带测试用例。

资源链接