` 与 `Promise.some()` 的“部分完成”语义)
Bluebird 并发原语解析深入掌握.some()与Promise.some()的“部分完成”语义【免费下载链接】bluebird:bird: :zap: Bluebird is a full featured promise library with unmatched performance.项目地址: https://gitcode.com/gh_mirrors/bl/bluebird.some()是 Bluebird 提供的集合类 Promise API 之一用于在多个并发任务中“只要满足指定数量就立即收尾”的场景。本文以 some.md 为骨架结合 src/some.js、src/promise_array.js 与 test/mocha/some.js 的实现细节系统讲解其签名、行为语义、错误类型与源码原理帮助你用它写出更精准的并发控制逻辑如抢占式超时、多副本竞速等。一、API 概览实例方法.somesome.md文档给出的核心签名如下.some(int count) - Promise它是静态方法 Promise.some 的实例形态等价于Promise.some(this, count)即对调用方 Promise 自身所代表的输入集合执行“部分完成”逻辑。例如var ping require(ping); var nameserverPromises [ ping(ns1.example.com), ping(ns2.example.com), ping(ns3.example.com), ping(ns4.example.com) ]; // 实例方法形态promise.some(count) Promise.all(nameserverPromises).some(2);从 src/some.js 可以看到实例方法只是简单的转发Promise.prototype.some function (howMany) { return some(this, howMany); };二、核心语义何时完成、何时拒绝Promise.some的完整签名见 promise.some.md为Promise.some( Iterableany|PromiseIterableany input, int count ) - Promise其行为可归纳为三条规则接受输入input可以是一个可迭代对象数组天然是Iterable也可以是“解析后得到可迭代对象”的 Promise即 Promise of Iterable。它会遍历该集合中的每个元素——元素既可以是 Promise也可以是普通值——逐个纳入统计。成功条件当集合中有count个 Promise 完成fulfilled时返回的 Promise 立即完成。完成值是一个数组包含这count个值按它们完成的先后顺序排列而非输入顺序。失败条件如果被拒绝rejected的 Promise 数量多到“无论如何都不可能再凑齐count个完成”返回的 Promise 会立即以 AggregateError 拒绝该错误聚合了所有已发生的拒绝原因按它们被抛出的顺序排列。文档中的经典示例——并发 ping 4 个域名服务器并取最快返回的 2 个Promise.some([ ping(ns1.example.com), ping(ns2.example.com), ping(ns3.example.com), ping(ns4.example.com) ], 2).spread(function(first, second) { console.log(first, second); });这里.spread详见 spread.md把完成数组解包为first、second两个参数在更现代的写法中也可以直接用.then接收数组。与any/race的区别.some(input, 1)等价于.any()——只要有一个完成即收尾.some(input, N)等价于“N 个.any()的叠加”或“带数量的 race”.race()只关心第一个落定settled的结果无论成功失败而.some(count)只统计完成数量拒绝只用于判断“是否还有可能凑够”。三、错误处理AggregateError 的正确捕获姿势当“无法凑齐count个完成”时错误以AggregateError形式抛出。可以通过Promise.AggregateError拿到该类型的引用并使用错误过滤器进行精确捕获Promise.some([...]) .then(...) .then(...) .catch(Promise.AggregateError, function(err) { err.forEach(function(e) { console.error(e.stack); }); });AggregateError之所以能用.forEach遍历是因为 src/errors.js 在构造时将 Array 原型上的 21 个方法join pop push shift unshift slice filter forEach some every map indexOf lastIndexOf reduce reduceRight sort reverse等逐一挂载到了AggregateError.prototype上因此它可以像数组一样被索引err[0]和遍历。注意在测试 test/mocha/some.js 中错误对象内部存储的拒绝原因是非枚举属性notEnumerableProp写入见 src/errors.js因此不宜依赖对象键枚举来取值应使用数组式索引访问。四、源码级原理SomePromiseArray的运作机制some的实现位于 src/some.js核心思路是构造一个专用的SomePromiseArray子类挂到PromiseArray基础框架上function some(promises, howMany) { if ((howMany | 0) ! howMany || howMany 0) { return apiRejection(POSITIVE_INTEGER_ERROR); } var ret new SomePromiseArray(promises); var promise ret.promise(); ret.setHowMany(howMany); ret.init(); return promise; }整个流程分四步参数校验(howMany | 0) ! howMany || howMany 0这一条件同时排除了负数、NaN 以及非整数如小数不合法时直接以 constants.js 中定义的expecting a positive integer错误拒绝。计数初始化setHowMany(count)写入目标数量init()将_initialized置为 true 后触发_init()src/some.js。当count 0时直接以空数组完成——这是测试should fulfill with empty array with 0覆盖的行为。输入归一化SomePromiseArray继承自PromiseArraysrc/promise_array.js。_init中先用tryConvertToPromise处理“Promise of Iterable”的情形——若输入本身是 Promise则等待其解析否则通过util.asArray把可迭代对象转为数组解析结果不是数组/可迭代对象时以expecting an array or an iterable object见 constants.js拒绝。逐项驱动与终局判定_promiseFulfilled(value)src/some.js每次完成就把值写入数组_addFulfilled用_totalResolved自增下标天然按完成顺序存放当_fulfilled() howMany()时截断数组并以这count个值 resolve_promiseRejected(reason)src/some.js把拒绝原因追加进同一数组_addRejected直接push到.length之后见 src/some.js然后调用_checkOutcome()_checkOutcome()src/some.js一旦howMany _canPossiblyFulfill()即“剩余可能完成数已不足”就收集所有非取消原因的拒绝值构造AggregateError并 reject。这种“完成值从头部写入、拒绝原因从尾部追加”的复用单数组设计是 src/some.js 注释中明确说明的优化点避免为收集拒绝原因额外分配对象。五、边界条件与参数约束结合实现与测试test/mocha/some.jscount与输入的合法/非法形态可总结如下场景行为依据count为负数以TypeError拒绝test/mocha/some.jscount为NaN以TypeError拒绝test/mocha/some.js输入既非数组也非可迭代对象以TypeError拒绝test/mocha/some.jscount大于输入总长度永远无法凑齐以RangeError拒绝消息为Input array must contain at least N items but contains only M itemssrc/some.js、test/mocha/some.jscount 0立即以[]完成test/mocha/some.js输入为空数组且count 0以RangeError拒绝test/mocha/some.js输入为“Promise of 数组”等待输入 Promise 解析后正常处理test/mocha/some.js输入 Promise 直接拒绝原样转发该拒绝原因test/mocha/some.js稀疏数组稀疏位按undefined计入完成值test/mocha/some.js两点额外说明RangeError/TypeError 与原生类型的混用Bluebird 优先复用原生TypeError/RangeError仅在原生类型不可用时才回退到自建子类src/errors.js。测试中捕获Promise.RangeError与原生捕获行为一致。拒绝原因的数组式访问当AggregateError抛出后可以通过err[0]、err[1]按“抛出顺序”取各拒绝原因test/mocha/some.js 验证了这一点。六、测试验证一览test/mocha/some.js 是some行为最权威的“可执行规格”除上文边界表外还覆盖了普通值数组、Promise 数组均可作为输入should resolve values array/should resolve promises array结果必须是输入值的子集isSubset断言且长度严格等于count聚合错误可通过.error()回调捕获aggregate error should be caught in .error见 test/mocha/some.js输入 Promise 解析为非数组时以TypeError拒绝。这些测试源自 When.js 的移植文件头部注释已说明也印证了some语义在主流 Promise 库中的一致性。七、与其他集合 API 的协同使用.some/Promise.some属于 Bluebird 集合类 API 家族相关能力可对照阅读Promise.some静态方法完整参考Promise.anycount 1的特例Promise.race第一个落定即返回不区分成败Promise.all全部完成才返回collections.md集合类 API 总览。典型落地场景多副本请求竞速如向多个 CDN/存储节点发起相同读取取最先成功的 2 份做一致性校验、传感器/探针冗余采集N 路采样取前 k 路成功、以及“至少 k 个节点确认”的分布式仲裁逻辑。理解_checkOutcome的“尽早失败”判定src/some.js后可以确信 Bluebird 会在“注定失败”的第一时间拒绝而非傻等全部完成这正是它在部分完成场景下兼具正确性与响应速度的根基。【免费下载链接】bluebird:bird: :zap: Bluebird is a full featured promise library with unmatched performance.项目地址: https://gitcode.com/gh_mirrors/bl/bluebird创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考