如何强制消费方使用完整的迭代器?
如何强制消费方使用完整的迭代器?
这个需求挺有意思的——想确保使用者必须解构完元组的所有元素,不能只取部分。先说说你之前的思路为什么行不通:你在生成器的return里直接执行了抛出错误的函数,这会导致不管有没有解构完所有元素,只要生成器走到return步骤就会报错。比如解构两个元素时,生成器在yield第一个值后,第二次调用next()会走到return,照样触发错误,这显然不是你想要的效果。
那正确的思路应该是:追踪迭代器是否被完全遍历,只有当迭代提前终止(也就是没取完所有元素)时才抛出错误。利用生成器的try/finally特性就能实现这一点——当迭代器被提前终止时,finally块会自动执行,我们可以在里面检查是否已经遍历完所有元素:
class FullConsumeTuple { constructor(...values) { this.values = values; } *[Symbol.iterator]() { let consumedCount = 0; try { for (const val of this.values) { yield val; consumedCount++; } } finally { // 如果遍历的数量少于总元素数,说明提前终止了 if (consumedCount < this.values.length) { throw new Error("Must destructure all values from tuple"); } } } } // 测试一下 const tuple = new FullConsumeTuple('first', 'second'); // 只取第一个元素,会抛出错误 try { const [first] = tuple; } catch (e) { console.error(e.message); // 输出:Must destructure all values from tuple } // 取完所有元素,正常运行 const [first, second] = tuple; console.log(first, second); // 输出:first second
这个方法的核心是:解构操作如果只取部分元素,会自动终止迭代器,触发生成器的finally块。我们在finally里对比已消费的元素数量和总数量,就能判断是否提前终止,进而抛出错误。
不过有个小局限:如果使用者手动调用迭代器的next()但不终止它(比如只调用一次就不管了),finally块不会立即执行,也就不会触发错误。但这种手动操作迭代器的场景比较少见,在解构的核心需求下,这个方案已经能很好地解决问题。
另外,你也可以考虑用FinalizationRegistry来追踪迭代器的回收,在迭代器被垃圾回收时检查是否遍历完成,但这种方法是异步的,而且无法保证一定会触发,可靠性不如try/finally方案。
备注:内容来源于stack exchange,提问作者Justin Wallace
相关产品推荐
相关产品推荐

