
core-js 中的 Promise.prototype.finally提案语义、源码实现与 polyfill 实战【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-jsPromise.prototype.finally是 ECMAScript 提案 Promise.prototype.finally已进入 ES2018 正式规范提出的方法用于在 Promise 无论兑现还是拒绝时都执行一段收尾逻辑。本文以 core-js 仓库中的 Promise.prototype.finally 官方文档 为主体结合其 模块实现 与 单元测试完整讲解该方法的语义、core-js 的 polyfill 实现原理、各命名空间入口的引入方式以及浏览器兼容性细节帮助你在旧环境中安全、精准地启用这一能力。一、提案与内置签名Promise.prototype.finally最初来自 tc39 的proposal-promise-finally提案设计意图是给 Promise 提供与同步代码try { ... } finally { ... }相呼应的异步收尾能力无论前面的 Promise 是兑现fulfilled还是拒绝rejected注册的回调都会被调用用于清理资源、停止加载动画、关闭连接等场景。core-js 文档中给出的内置签名如下class Promise { finally(onFinally: Function): Promise; }要点解析入参onFinally一个函数在 Promise 敲定settled后被调用。它不接收任何参数也不关心 Promise 的最终状态返回值一个新的Promise其状态与值会沿用原 Promise 的最终结果而不是onFinally的返回值但onFinally抛错或返回 rejected Promise 时例外见下文语义说明该方法的规范性语义定义在 ECMAScript 规范的Promise.prototype.finally小节core-js 在源码注释中同样标注了这条规范链接。二、finally 的语义细节从行为上看finally(onFinally)等价于在.then()两侧都挂上回调的一种封装但语义上有三个关键点回调不接收参数onFinally被调用时不带任何参数这与.then(onFulfilled)会透传兑现值不同原结果被透传若原 Promise 兑现为值v则finally返回的 Promise 也兑现为v若原 Promise 以原因e拒绝则返回的 Promise 同样以e拒绝——onFinally的返回值非 Promise 值不会覆盖原结果回调返回 Promise 时会等待若onFinally返回一个 Promise则最终结果会等待该 Promise 敲定后再透传原结果若onFinally抛出异常或返回一个 rejected Promise则该异常/拒绝原因会覆盖原结果。上述第 3 点在 模块实现 中体现得非常直接兑现分支return promiseResolve(C, onFinally()).then(function () { return x; })会先构造onFinally()的 Promise 并等待其敲定成功后才把原值x透传拒绝分支return promiseResolve(C, onFinally()).then(function () { throw e; })则保证原拒绝原因e被重新抛出。这正是回调抛错会覆盖原结果这一语义的底层来源。三、core-js 的 polyfill 实现逐行拆解core-js 对Promise.prototype.finally的实现位于 packages/core-js/modules/es.promise.finally.js核心逻辑非常精炼主要分为两个部分。3.1 方法本体基于 then 的组合实现$({ target: Promise, proto: true, real: true, forced: NON_GENERIC }, { finally: function (onFinally) { var C speciesConstructor(this, getBuiltIn(Promise)); var isFunction isCallable(onFinally); return this.then( isFunction ? function (x) { return promiseResolve(C, onFinally()).then(function () { return x; }); } : onFinally, isFunction ? function (e) { return promiseResolve(C, onFinally()).then(function () { throw e; }); } : onFinally ); } });逐行解读speciesConstructor(this, getBuiltIn(Promise))遵循Symbol.species机制取得目标构造函数。若当前实例是Promise的子类或自定义 species最终返回的 Promise 将属于该构造函数保证子类化场景下.finally返回的仍是子类实例isCallable(onFinally)先判断回调是否可调用。若传入的不是函数例如null、undefined或普通对象两个分支都直接使用onFinally本身行为退化为与this.then(onFinally, onFinally)等价的透传——这是规范要求的对非函数参数的处理方式兑现分支promiseResolve(C, onFinally())将回调返回值包装成目标构造函数C的 Promise若返回值本身就是C的实例则直接复用见 internals/promise-resolve.js 中的anObject(C)与x.constructor C短路判断等待其敲定后透传原值x拒绝分支同样先等待onFinally()的结果成功后再throw e重新抛出原拒绝原因。也就是说finally并非调用底层原生实现而是完全基于then组合出来这保证了 polyfill 在不存在finally的旧引擎上依然能以统一语义工作。3.2 对 Safari bug 200829 的强制补丁var NON_GENERIC !!NativePromiseConstructor fails(function () { NativePromisePrototype[finally].call({ then: function () { /* empty */ } }, function () { /* empty */ }); });源码注释明确指出这是针对 Safari 的 bugWebKit 问题 200829Safari 的部分版本中原生Promise.prototype.finally不是泛型方法无法对只有then方法的 thenable 对象正确调用。core-js 通过fails检测在该环境下调用是否会抛错若检测失败则将forced置为真强制用 polyfill 版本覆盖原生实现。3.3 让原生 Promise 联动已打补丁的 thenif (!IS_PURE isCallable(NativePromiseConstructor)) { var method getBuiltIn(Promise).prototype[finally]; if (NativePromisePrototype[finally] ! method) { defineBuiltIn(NativePromisePrototype, finally, method, { unsafe: true }); } }在非 pure 版本中core-js 会把补丁后的finally同步写入原生Promise.prototype。其目的在于当原生finally内部依赖的then已被 core-js 补丁时保证基于原生 Promise 的 API如async function返回值也能走统一的补丁路径避免原生 finally 补丁 then组合出现不一致行为。IS_PURE开关见 internals/is-pure.js则确保core-js-pure版本不污染全局命名空间。四、入口点如何在项目中引入关联文档明确给出了该功能的入口点声明core-js/proposals/promise-finally这个入口文件 packages/core-js/proposals/promise-finally.js 内容极简本质上是模块的转发use strict; // https://github.com/tc39/proposal-promise-finally require(../modules/es.promise.finally);之所以放在proposals/目录下是因为该入口按提案维度聚合功能。实际上Promise.prototype.finally已进入 ES2018 正式标准它同时存在于多个命名空间。根据 docs/web/docs/usage.md 中Entry points一节对应关联文档中的{docs-version}/docs/usage#h-entry-points链接core-js 提供从宽到严的四种命名空间均可按需引入// polyfill 全部 core-js 特性含早期提案按提案维度引入的入口即属于此类 import core-js/full; // polyfill 所有实际特性——稳定 ES、web 标准与 stage 3 提案 import core-js/actual; // polyfill 仅稳定特性——ES 与 web 标准 import core-js/stable; // polyfill 仅稳定 ES 特性 import core-js/es;对finally而言最精准的按方法粒度引入方式为// 全局版本直接污染 Promise.prototype import core-js/es/promise/finally; // 或通过提案入口 import core-js/proposals/promise-finally;packages/core-js/es/promise/finally.js 展示了命名空间入口的标准结构先加载依赖模块es.object.to-string、es.promise、es.promise.finally再通过entryUnbind(Promise, finally)导出绑定后的方法。而stable、full、actual命名空间则逐级向上继承stable/promise/finally.js → full/promise/finally.js → actual/promise/finally.js形成es stable actual full的递进关系。4.1 不污染全局的 pure 版本如果使用core-js-pure由于不能污染原生构造函数的原型finally这类原型方法会被转换为静态方法使用import Promise from core-js-pure/es/promise; Promise.resolve(42).finally(() { console.log(cleanup); });从 tests/unit-pure/es.promise.finally.js 可以看到pure 版本的测试直接import Promise from core-js-pure/es/promise验证的语义与全局版本完全一致Promise.prototype.finally是函数、arity 为 1、不可枚举且Promise.resolve(42).finally(...)仍返回 Promise 实例。4.2 安装方式与注意事项按 usage.md 的说明安装命令如下以当前仓库的 3.50.0 版本为例npm install --save core-js3.50.0 // 全局版本 npm install --save core-js-pure3.50.0 // 不污染全局命名空间的版本 npm install --save core-js-bundle3.50.0 // 打包后的全局版本使用modules路径即core-js/modules/...时需注意它是内部 API不会自动注入所有依赖模块只应服务于自定义构建场景。若以扩展原生对象的方式使用 core-js建议在应用入口文件的最顶部加载所有 core-js 模块避免与第三方库的原生扩展产生冲突。五、测试验证语义如何被确认core-js 为Promise.prototype.finally配备了全局与 pure 两套 QUnit 测试tests/unit-global/es.promise.finally.js 中的断言覆盖了规范语义的核心点存在性与形态assert.isFunction(Promise.prototype.finally)确认方法存在assert.arity(Promise.prototype.finally, 1)确认形参个数为 1assert.nonEnumerable(Promise.prototype, finally)确认其不可枚举符合规范中finally不作为可枚举属性暴露的要求返回新 PromisePromise.resolve(42).finally(() {}) instanceof Promise确认返回值为 Promise 实例兑现透传Promise.resolve(42).finally(it { ... }).then(it assert.same(it, 42))验证原值42被完整透传且onFinally被调用恰好一次、调用参数为undefined拒绝透传Promise.reject(42).finally(...)后then(() assert.avoid(), () { ... })验证原拒绝原因42被保留且onFinally同样不接收参数原生 Promise 联动测试文件后半部分专门针对原生 async 函数返回值其constructor非Promise的场景验证补丁后的finally在这些对象上同样可用且返回 Promise 实例——这正是上一节让原生 Promise 联动已打补丁的 then所解决的问题。六、典型使用场景综合语义与实现Promise.prototype.finally最适合以下场景// 1. 无论成功失败都清理状态 showLoading(); fetchData() .then(render) .catch(handleError) .finally(() hideLoading()); // 2. 释放资源 let handle; openResource() .then(h { handle h; return h.read(); }) .finally(() handle handle.close()); // 3. 回调抛错时覆盖原结果利用规范语义 Promise.resolve(1).finally(() { throw new Error(override); }) .catch(e console.log(e.message)); // override需要强调的是第 3 个例子中回调抛错覆盖原结果以及回调返回 Promise 时等待其敲定这两个行为正是 core-js 实现源码 中promiseResolve(C, onFinally()).then(...)组合的直接体现也是finally与简单使用then(onF, onF)的差异所在。小结Promise.prototype.finally以最小的心智负担为 Promise 链提供了可靠的收尾机制。core-js 通过 es.promise.finally 模块 用then组合出完整规范语义并通过NON_GENERIC检测修复 Safari 的泛型方法 bug、通过defineBuiltIn让原生 Promise 联动补丁后的then。无论是按命名空间es/stable/actual/full整体引入还是按core-js/proposals/promise-finally精准引入你都可以参考本文给出的入口方式与 单元测试 中的断言来验证行为是否符合预期。【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考