ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

core-js 中的 AsyncIterator helpers 提案:异步迭代器工具方法全解析

core-js 中的 AsyncIterator helpers 提案:异步迭代器工具方法全解析 core-js 中的 AsyncIterator helpers 提案异步迭代器工具方法全解析【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js导读本文聚焦 core-js 对 TC39 AsyncIterator helpers 提案异步迭代器工具方法的完整实现涵盖其提供的AsyncIterator全局构造器、from/drop/map/filter/reduce/toArray等 13 个工具模块、全部入口点的引入方式以及core-js/configurator中USE_FUNCTION_CONSTRUCTOR与AsyncIteratorPrototype两个关键配置的深层原理。阅读完本文你将掌握在任意支持 ES2018 的环境中以标准方式组合异步迭代器变换管道chain并能在core-js-pure与 CSP 受限环境等边界条件下正确选用配置。该特性当前属于早期阶段提案early-stage proposalcore-js 将其置于esnext.*命名空间下仅通过proposals/与full/等入口暴露不属于stable与actual的稳定面。提案背景为什么需要 AsyncIterator helpers同步侧已经通过 Iterator helpers 提案 为普通迭代器提供了map、filter、take等懒求值工具方法异步侧的 AsyncIterator helpers 是它的自然延伸目标是让AsyncIterator异步迭代器也能享受同样的组合式编程体验不提前物化数据、按需拉取pull-based、可提前终止early exit。core-js 将整个提案拆成了 14 个独立模块1 个构造器 13 个工具方法分布在 packages/core-js/modules 下模块文件作用esnext.async-iterator.constructor.js定义全局AsyncIterator抽象构造器esnext.async-iterator.from.js静态方法AsyncIterator.from(iterable)esnext.async-iterator.drop.jsdrop(limit)跳过前 N 个元素esnext.async-iterator.every.jsevery(callbackfn)全部满足esnext.async-iterator.filter.jsfilter(callbackfn)过滤esnext.async-iterator.find.jsfind(callbackfn)查找首个匹配esnext.async-iterator.flat-map.jsflatMap(callbackfn)扁平化映射esnext.async-iterator.for-each.jsforEach(callbackfn)遍历消费esnext.async-iterator.map.jsmap(callbackfn)映射esnext.async-iterator.reduce.jsreduce(callbackfn, initialValue)归约esnext.async-iterator.some.jssome(callbackfn)任一满足esnext.async-iterator.take.jstake(limit)取前 N 个esnext.async-iterator.to-array.jstoArray()物化为数组esnext.iterator.to-async.js同步迭代器的toAsync()适配入口聚合文件 packages/core-js/proposals/async-iterator-helpers.js 依次require上述全部模块一次性注册整个提案。内置 API 签名一份完整的 TypeScript 视图关联文档给出了提案的完整 TS 签名这是理解整个特性面的最佳起点class Iterator { toAsync(): AsyncIteratorany; } class AsyncIterator { static from(iterable: AsyncIterableany | Iterableany | AsyncIteratorany): AsyncIteratorany; drop(limit: uint): AsyncIteratorany; every(async callbackfn: (value: any, counter: uint) boolean): Promiseboolean; filter(async callbackfn: (value: any, counter: uint) boolean): AsyncIteratorany; find(async callbackfn: (value: any, counter: uint) boolean)): Promiseany; flatMap(async callbackfn: (value: any, counter: uint) AsyncIterableany | Iterableany | AsyncIteratorany): AsyncIteratorany; forEach(async callbackfn: (value: any, counter: uint) void): Promisevoid; map(async callbackfn: (value: any, counter: uint) any): AsyncIteratorany; reduce(async callbackfn: (memo: any, value: any, counter: uint) any, initialValue: any): Promiseany; some(async callbackfn: (value: any, counter: uint) boolean): Promiseboolean; take(limit: uint): AsyncIteratorany; toArray(): PromiseArray; toStringTag: AsyncIterator }解读其中的关键设计回调都是异步的callbackfn可以是返回 Promise 的异步函数也可以返回普通值core-js 内部统一通过Promise.resolve收编见下文实现剖析所有终止类方法返回Promiseevery/find/forEach/reduce/some/toArray返回PromiseT只有流式变换方法drop/filter/flatMap/map/take返回新的AsyncIterator可以继续链式调用toStringTag为AsyncIterator由 esnext.async-iterator.constructor.js 中的createNonEnumerableProperty在%AsyncIteratorPrototype%上以不可枚举方式定义保证Object.prototype.toString行为符合规范且不污染枚举。AsyncIterator构造器本身是抽象类从 esnext.async-iterator.constructor.js 可以看到直接调用new AsyncIterator()会抛出TypeError: Abstract class AsyncIterator not directly constructablevar AsyncIteratorConstructor function AsyncIterator() { anInstance(this, AsyncIteratorPrototype); if (getPrototypeOf(this) AsyncIteratorPrototype) throw new $TypeError(Abstract class AsyncIterator not directly constructable); };也就是说AsyncIterator只作为所有异步迭代器的公共原型基座实例必须来自异步生成器、AsyncIterator.from、toAsync()或原生异步可迭代对象。入口点按需引入的四种粒度关联文档列出的入口点覆盖了proposals、actual、full三个命名空间stable与es不含早期提案对应 docs/web/docs/usage.md 中描述的按需 polyfill约定core-js/proposals/async-iterator-helpers core-js(-pure)/actual|full/async-iterator core-js(-pure)/actual|full/async-iterator/drop core-js(-pure)/actual|full/async-iterator/every core-js(-pure)/actual|full/async-iterator/filter core-js(-pure)/actual|full/async-iterator/find core-js(-pure)/actual|full/async-iterator/flat-map core-js(-pure)/actual|full/async-iterator/for-each core-js(-pure)/actual|full/async-iterator/from core-js(-pure)/actual|full/async-iterator/map core-js(-pure)/actual|full/async-iterator/reduce core-js(-pure)/actual|full/async-iterator/some core-js(-pure)/actual|full/async-iterator/take core-js(-pure)/actual|full/async-iterator/to-array core-js(-pure)/actual|full/iterator/to-async使用建议一次性引入整套提案import core-js/proposals/async-iterator-helpers或在core-js-builder自定义构建中选择该 feature按方法精确引入如import core-js/actual/async-iterator/map只注入用到的模块适合对包体积敏感的项目actual与full的取舍actual包含 stage 3 及已落地的提案特性full则额外包含 stage 2 等更早期提案。按 docs/web/docs/usage.md 的官方建议生产环境优先使用/actual/由于 AsyncIterator helpers 长期处于 stage 2.7实践中通常仍通过/full/或proposals/引入core-js-pure变体将core-js替换为core-js-pure即可在不污染全局的前提下使用适合库作者。实操示例异步变换管道关联文档给出了两个经典示例完整还原如下注意toArray()的awaitawait AsyncIterator.from([1, 2, 3, 4, 5, 6, 7]) .drop(1) .take(5) .filter(it it % 2) .map(it it ** 2) .toArray(); // [9, 25]执行过程推演from包装数组迭代器 →drop(1)跳过1→take(5)限流为[2,3,4,5,6]→filter保留奇数[3,5]→map平方得[9,25]→toArray物化。整个链是惰性的只有调用toArray()才开始真正从上游拉取数据。await [1, 2, 3].values().toAsync().map(async it it ** 2).toArray(); // [1, 4, 9]第二个示例展示了Iterator.prototype.toAsync()将同步迭代器[].values()转换为异步迭代器再map一个返回 Promise 的异步回调。注意这里也可以直接写await it ** 2因为map的回调返回值无论是否为 Promise 都会被正确 await。异步回调的真实行为下面验证回调可以返回 Promise这一核心设计。先看 packages/core-js/internals/async-iterator-map.js 的实现思路它通过Promise.resolve(call(callback, state.iterator, value, counter))把回调结果统一收编为 Promise再传入next生成器的yield表达式——异步函数yield await promise的行为恰好实现了等待回调完成后再取下一个值。这正是天然支持异步回调的原因const result await AsyncIterator .from([1, 2, 3]) .map(async value { await sleep(10); // 模拟异步耗时操作 return value * 10; }) .toArray(); console.log(result); // [10, 20, 30]从源码看实现原理代理与内部状态core-js 的这一整套实现并非简单地在%AsyncIteratorPrototype%上挂方法而是构建了两层精巧的基础设施集中在 packages/core-js/internals/async-iterator-create-proxy.js内部状态Internal State每个工具方法产生的包装异步迭代器都携带state包含iterator上游迭代器、next上游 next 方法、nextHandler本方法特有逻辑、counter元素下标从 0 计数以及done标志。InternalStateModule以非枚举方式把状态挂在实例上避免属性名冲突。两类原型WrapForValidAsyncIteratorPrototype由AsyncIterator.from/toAsync()产生的纯包装器如 packages/core-js/internals/async-iterator-wrap.js 所示next直接转发this.nextAsyncIteratorHelperPrototype由drop/map等方法产生的助手迭代器其toStringTag被定义为Async Iterator Helper。提前终止early exitreturn()方法实现了完整的资源回收——先尝试关闭内部迭代器flatMap场景下的inner再关闭外部迭代器并正确处理上游return方法缺失、Promise 拒绝等情况见 async-iterator-create-proxy.js。这保证了for await...of中途break或reduce提前完成时不会泄漏迭代器资源。drop的惰性跳过实现esnext.async-iterator.drop.js 展示了一个典型实现drop并不立刻消费上游而是在nextHandler中维护state.remaining计数器每次被拉取时才跳过直到跳满limit才返回第一个真实值if (anObject(step).done) { state.done true; resolve(createIterResultObject(undefined, true)); } else if (state.remaining) { state.remaining--; loop(); } else resolve(createIterResultObject(step.value, false));limit参数经过toPositiveInteger(notANaN(limit))规范化NaN抛错、负值按 0 处理、小数向下取整来自to-positive-integer内部模块。from的多态适配esnext.async-iterator.from.js 接受AsyncIterable | Iterable | AsyncIterator | string四种输入其底层是 packages/core-js/internals/get-async-iterator-flattenable.js优先读取obj[Symbol.asyncIterator]若不存在回退到同步的Symbol.iterator并通过 packages/core-js/internals/async-from-sync-iterator.js 将同步迭代器适配为异步迭代器next()返回 Promise且 Promise 被拒绝时主动对同步迭代器调用throw关闭字符串会被toObject包装后走同样的流程因此AsyncIterator.from(abc)也是合法的若输入本身已经继承%AsyncIteratorPrototype%from直接原样返回不做多余包装。reduce的健壮性设计esnext.async-iterator.reduce.js 是最复杂的一个终止类方法值得关注的点缺省initialValue时以首个元素作为累加器对空迭代器且无初始值的情况明确拒绝TypeError: Reduce of empty iterator with no initial value与数组reduce语义一致每一步的reducer结果如果是对象可能为 thenable会Promise.resolve(result).then(handler, ...)异步接续否则同步进入下一轮全程通过closeAsyncIteration在出错时关闭上游迭代器避免悬挂。原型获取的兼容性方案Caveats 深挖关联文档的 Caveats 部分是全文最有价值的技术细节它揭示了 core-js 在老浏览器上获取真实%AsyncIteratorPrototype%的艰难取舍。问题根源在core-js-pureIS_PURE下为避免原型污染新方法不会添加到真实%AsyncIteratorPrototype%上而只存在于包装器。因此不要写[].values().toAsync().map(fn)而应使用AsyncIterator.from([]).map(fn)——后者返回的就是 core-js 自己的包装器方法都在上面。在老浏览器中core-js 只有在能访问异步生成器语法时才拿得到真实原型。源码 packages/core-js/internals/async-iterator-prototype.js 给出了完整的分支逻辑if (PassedAsyncIteratorPrototype) { // 1. configurator 传入的原型 AsyncIteratorPrototype PassedAsyncIteratorPrototype; } else if (isCallable(AsyncIterator)) { // 2. 环境原生存在 AsyncIterator AsyncIteratorPrototype AsyncIterator.prototype; } else if (shared[USE_FUNCTION_CONSTRUCTOR] || globalThis[USE_FUNCTION_CONSTRUCTOR]) { try { // 3. Function 构造器动态生成 async generator prototype getPrototypeOf(getPrototypeOf(getPrototypeOf(Function(return async function*(){}())()))); if (getPrototypeOf(prototype) Object.prototype) AsyncIteratorPrototype prototype; } catch (error) { /* empty */ } } if (!AsyncIteratorPrototype) AsyncIteratorPrototype {}; else if (IS_PURE) AsyncIteratorPrototype create(AsyncIteratorPrototype);分支 3 是兼容老浏览器的关键用Function构造器在运行时动态编译async function*(){}()通过三次getPrototypeOf拿到%AsyncIteratorPrototype%async generator 实例 → generator 原型 → 再上一层才是 AsyncIterator 原型。代价是Function构造器在 CSP内容安全策略环境下会被拦截导致整个降级路径失效最终退回{}功能退化为仅存在于包装器上。方案一开启USE_FUNCTION_CONSTRUCTOR关联文档给出的第一种解法是显式开启开关注意文档原文将选项写在configurator调用中且USE_FUNCTION_CONSTRUCTOR的取值会被configurator按布尔语义处理const configurator require(core-js/configurator); configurator({ USE_FUNCTION_CONSTRUCTOR: true }); require(core-js/actual/async-iterator); (async function * () { /* empty */ })() instanceof AsyncIterator; // true此时async-iterator-prototype.js会走分支 3尝试用Function构造器抓取真实原型instanceof AsyncIterator成立说明原生 async generator 实例成功接入了 core-js 扩展的%AsyncIteratorPrototype%。适用场景不启用 CSP 或 CSP 允许unsafe-eval的环境。方案二直接注入原型对象作为替代关联文档演示了绕过Function的纯运行时方案——把自己从 async generator 上现取的原型交给configuratorconst configurator require(core-js/configurator); const { getPrototypeOf } Object; configurator({ AsyncIteratorPrototype: getPrototypeOf(getPrototypeOf(getPrototypeOf(async function * () { /* empty */ }()))) }); require(core-js/actual/async-iterator); (async function * () { /* empty */ }()) instanceof AsyncIterator; // true这一方案在async-iterator-prototype.js中命中分支 1PassedAsyncIteratorPrototype完全避免Function构造器因此不受 CSP 限制但要求运行环境本身已经支持 async generator 语法。两种方案的效果等价让真实的%AsyncIteratorPrototype%获得 core-js 注入的 helpers使任何原生 async generator 产物都能直接使用map/filter等方法。configurator内部如何接收这些选项从 packages/core-js/configurator.js 可以看到configurator除了处理useNative/usePolyfill/useFeatureDetection三个激进等级选项外专门为这两个键做了透传if (hasOwn(options, USE_FUNCTION_CONSTRUCTOR)) { shared[USE_FUNCTION_CONSTRUCTOR] !!options[USE_FUNCTION_CONSTRUCTOR]; } if (hasOwn(options, ASYNC_ITERATOR_PROTOTYPE)) { shared[ASYNC_ITERATOR_PROTOTYPE] options[ASYNC_ITERATOR_PROTOTYPE]; }两者都写入shared-storecore-js 内部共享的单例存储随后被async-iterator-prototype.js读取。需要注意configurator必须在任何 core-js 模块加载之前调用源码注释与 docs/web/docs/usage.md 均强调加载顺序USE_FUNCTION_CONSTRUCTOR也可通过全局globalThis.USE_FUNCTION_CONSTRUCTOR直接设置见 async-iterator-prototype.js传入的AsyncIteratorPrototype对象必须形如真实的%AsyncIteratorPrototype%否则instanceof判断可能不成立。测试覆盖如何在仓库中验证tests/unit-global目录下提供了与提案一一对应的单元测试文件同样适用于tests/unit-pure的 pure 版本可作为行为规范的活文档tests/unit-global/esnext.async-iterator.constructor.jstests/unit-global/esnext.async-iterator.drop.jstests/unit-global/esnext.async-iterator.every.jstests/unit-global/esnext.async-iterator.filter.jstests/unit-global/esnext.async-iterator.find.jstests/unit-global/esnext.async-iterator.flat-map.jstests/unit-global/esnext.async-iterator.for-each.jstests/unit-global/esnext.async-iterator.from.jstests/unit-global/esnext.async-iterator.map.jstests/unit-global/esnext.async-iterator.reduce.jstests/unit-global/esnext.async-iterator.some.jstests/unit-global/esnext.async-iterator.take.jstests/unit-global/esnext.async-iterator.to-array.jstests/unit-global/esnext.iterator.to-async.js在仓库根目录执行测试如npm run test-unit-global或按项目 CONTRIBUTING.md 的指引即可验证这些行为尤其是回调返回 Promise、提前终止、toStringTag与原型接入等边界情况。实战选型建议综合以上分析在真实项目中引入 AsyncIterator helpers 时可以参考以下决策场景推荐入口 / 配置现代浏览器 Node.js希望全局可用import core-js/actual/async-iterator或full/以覆盖更早期提案按需精确引入单个方法import core-js/actual/async-iterator/map库作者避免全局污染core-js-pure变体并用AsyncIterator.from(...)链式调用老浏览器 允许Function构造器先configurator({ USE_FUNCTION_CONSTRUCTOR: true })再加载模块CSP 严格限制、但支持 async generator先configurator({ AsyncIteratorPrototype: 真实原型 })再加载模块环境既不支持 async generator 也没有AsyncIterator保持默认退回包装器实现放弃真实原型接入需要再次强调的是该提案目前仍是早期阶段提案API 细节可能随规范演进而变化core-js 通过esnext.*命名空间与proposals/入口将其与稳定特性隔离升级 core-js 小版本时应关注 CHANGELOG.md 中关于该提案的改动记录。【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表