├── .eslintrc ├── .github └── workflows │ ├── node-aught.yml │ ├── node-native.yml │ ├── node-pretest.yml │ ├── node-promise-shimmed.yml │ ├── node-tens.yml │ ├── rebase.yml │ └── require-allow-edits.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .nycrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── auto.js ├── implementation.js ├── index.js ├── package.json ├── polyfill.js ├── requirePromise.js ├── shim.js └── test ├── builtin.js ├── implementation.js ├── index.js ├── native.js ├── promise-shimmed.js ├── shimmed.js └── tests.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "extends": "@ljharb", 5 | 6 | "rules": { 7 | "new-cap": [2, { 8 | "capIsNewExceptions": [ 9 | "IsCallable", 10 | "PromiseResolve", 11 | "RequireObjectCoercible", 12 | "SpeciesConstructor", 13 | "Type" 14 | ], 15 | }], 16 | "no-magic-numbers": 0, 17 | }, 18 | 19 | "overrides": [ 20 | { 21 | "files": "test/**/*", 22 | "rules": { 23 | "max-lines-per-function": 0, 24 | "max-params": 0, 25 | "no-invalid-this": 1, 26 | "prefer-promise-reject-errors": 0, 27 | }, 28 | }, 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/node-aught.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js < 10' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/node.yml@main 8 | with: 9 | range: '>= 0.11 < 10' 10 | type: minors 11 | command: npm run tests-only 12 | 13 | node: 14 | name: 'node < 10' 15 | needs: [tests] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - run: 'echo tests completed' 19 | -------------------------------------------------------------------------------- /.github/workflows/node-native.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js: native' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/node.yml@main 8 | with: 9 | range: '>= 10' 10 | type: majors 11 | continue-on-error: true 12 | command: npm run test:native 13 | 14 | node: 15 | name: 'node, native tests' 16 | needs: [tests] 17 | runs-on: ubuntu-latest 18 | steps: 19 | - run: 'echo tests completed' 20 | -------------------------------------------------------------------------------- /.github/workflows/node-pretest.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: pretest/posttest' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/pretest.yml@main 8 | -------------------------------------------------------------------------------- /.github/workflows/node-promise-shimmed.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js: shimmed Promise' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | majors: 7 | name: 'majors, promise-shimmed tests' 8 | continue-on-error: true 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | node-version: 15 | - '0.12' 16 | - '0.11' 17 | - '0.10' 18 | - '0.8' 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: ljharb/actions/node/install@main 23 | name: 'nvm install ${{ matrix.node-version }} && npm install' 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm run test:promise-shimmed 27 | 28 | node: 29 | name: 'node, promise-shimmed tests' 30 | needs: [majors] 31 | runs-on: ubuntu-latest 32 | steps: 33 | - run: 'echo tests completed' 34 | -------------------------------------------------------------------------------- /.github/workflows/node-tens.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js >= 10' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/node.yml@main 8 | with: 9 | range: '>= 10' 10 | type: minors 11 | command: npm run tests-only 12 | 13 | node: 14 | name: 'node >= 10' 15 | needs: [tests] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - run: 'echo tests completed' 19 | -------------------------------------------------------------------------------- /.github/workflows/rebase.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Rebase 2 | 3 | on: [pull_request_target] 4 | 5 | jobs: 6 | _: 7 | uses: ljharb/actions/.github/workflows/rebase.yml@main 8 | secrets: 9 | token: ${{ secrets.GITHUB_TOKEN }} 10 | -------------------------------------------------------------------------------- /.github/workflows/require-allow-edits.yml: -------------------------------------------------------------------------------- 1 | name: Require “Allow Edits” 2 | 3 | on: [pull_request_target] 4 | 5 | jobs: 6 | _: 7 | name: "Require “Allow Edits”" 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: ljharb/require-allow-edits@main 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Only apps should have lockfiles 40 | package-lock.json 41 | yarn.lock 42 | npm-shrinkwrap.json 43 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Only apps should have lockfiles 40 | package-lock.json 41 | yarn.lock 42 | npm-shrinkwrap.json 43 | 44 | .github/workflows 45 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | allow-same-version=true 3 | message=v%s 4 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "check-coverage": false, 4 | "reporter": ["text-summary", "text", "html", "json"], 5 | "lines": 86, 6 | "statements": 85.93, 7 | "functions": 82.43, 8 | "branches": 76.06, 9 | "exclude": [ 10 | "coverage", 11 | "test" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [v3.1.8](https://github.com/es-shims/Promise.prototype.finally/compare/v3.1.7...v3.1.8) - 2024-02-04 9 | 10 | ### Commits 11 | 12 | - [Refactor] use `es-errors` where possible, so things that only need those do not need `get-intrinsic` [`9f15e2a`](https://github.com/es-shims/Promise.prototype.finally/commit/9f15e2a679b1f3e16f5bf005bf62569896a68a65) 13 | - [Deps] update `call-bind`, `es-abstract`, `get-intrinsic` [`4dbd141`](https://github.com/es-shims/Promise.prototype.finally/commit/4dbd141a4eebbba1cc3c7b54a6059457bf9c06b6) 14 | - [Dev Deps] update `aud`, `tape` [`3661fdb`](https://github.com/es-shims/Promise.prototype.finally/commit/3661fdbd28000474d20a2135f8fcc7317b319c77) 15 | 16 | ## [v3.1.7](https://github.com/es-shims/Promise.prototype.finally/compare/v3.1.6...v3.1.7) - 2023-09-13 17 | 18 | ### Commits 19 | 20 | - [Deps] update `define-properties`, `set-function-name` [`01d3f17`](https://github.com/es-shims/Promise.prototype.finally/commit/01d3f17514abb9da154890c28e317bd3b3ccddfd) 21 | 22 | ## [v3.1.6](https://github.com/es-shims/Promise.prototype.finally/compare/v3.1.5...v3.1.6) - 2023-09-13 23 | 24 | ### Commits 25 | 26 | - [Refactor] use `set-function-name` [`903d207`](https://github.com/es-shims/Promise.prototype.finally/commit/903d2071f0fc8391ce69fa249915067d57a59332) 27 | - [actions] update checkout action [`594ef8e`](https://github.com/es-shims/Promise.prototype.finally/commit/594ef8ef4cb71189eb867cedeb6b201c7b2e27c2) 28 | 29 | ## [v3.1.5](https://github.com/es-shims/Promise.prototype.finally/compare/v3.1.4...v3.1.5) - 2023-08-30 30 | 31 | ### Commits 32 | 33 | - [Deps] update `define-properties`, `es-abstract` [`2ff6ac3`](https://github.com/es-shims/Promise.prototype.finally/commit/2ff6ac356367e89eb555c50e3522e815d9d1bbbf) 34 | - [Dev Deps] update `@es-shims/api`, `@ljharb/eslint-config`, `aud`, `es6-shim`, `tape` [`4cacca4`](https://github.com/es-shims/Promise.prototype.finally/commit/4cacca47298df952f4547c5d7cdad5226c6266b8) 35 | 36 | ## [v3.1.4](https://github.com/es-shims/Promise.prototype.finally/compare/v3.1.3...v3.1.4) - 2022-11-07 37 | 38 | ### Commits 39 | 40 | - [actions] reuse common workflows [`1f2f581`](https://github.com/es-shims/Promise.prototype.finally/commit/1f2f581ffc86fcc76c91ad9b4e36466c23e370a0) 41 | - [meta] add `auto-changelog` [`382073c`](https://github.com/es-shims/Promise.prototype.finally/commit/382073ccb71bc7f41977c112d316da1a33e1148d) 42 | - [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `safe-publish-latest`, `tape` [`82cee30`](https://github.com/es-shims/Promise.prototype.finally/commit/82cee3007dc2641d22542e3d105e5fb95caee61b) 43 | - [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `@es-shims/api`, `aud`, `tape` [`7a16cda`](https://github.com/es-shims/Promise.prototype.finally/commit/7a16cdadad7fc32548b9cd3aff3ba160968d85ed) 44 | - [actions] update rebase action to use reusable workflow [`a3cefcf`](https://github.com/es-shims/Promise.prototype.finally/commit/a3cefcf3d2774834477f4263eedcd5abb089b651) 45 | - [actions] update codecov uploader [`63f0668`](https://github.com/es-shims/Promise.prototype.finally/commit/63f06684ac969bc4a78afa8a96a61d1034055885) 46 | - [Deps] update `define-properties`, `es-abstract` [`efeba8d`](https://github.com/es-shims/Promise.prototype.finally/commit/efeba8d7ce3ec0a522b639c492d2c27e0f2991e0) 47 | 48 | 49 | 50 | 3.1.3 / 2021-10-04 51 | ================= 52 | * [Refactor] update `es-abstract`; use `call-bind` instead of `function-bind` 53 | * [Deps] update `es-abstract` 54 | * [readme] add github actions/codecov badges 55 | * [meta] remove unneeded token; update checkout action 56 | * [actions] use `node/install` instead of `node/run`; use `codecov` action 57 | * [actions] add Require Allow Edits workflow 58 | * [actions] switch Automatic Rebase workflow to `pull_request_target` event 59 | * [Tests] increase coverage 60 | * [Tests] migrate tests to Github Actions (#29) 61 | * [Tests] run `nyc` on all tests; use `tape` runner; add implementation tests; mark failing impl tests as TODO 62 | * [Tests] skip "observable calls" tests in node 6-9 63 | * [Tests] add passing tests from https://github.com/tc39/test262/pull/2752 64 | * [Tests] refactor Subclass tests to capture receiver 65 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `@es-shims/api`, `aud`, `es6-shim`, `tape` 66 | 67 | 3.1.2 / 2019-12-11 68 | ================= 69 | * [Refactor] use split-up `es-abstract` 70 | * [Deps] update `es-abstract` 71 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `safe-publish-latest` 72 | * [Tests] up to `node` `v12.12` 73 | * [Tests] use shared travis-ci configs 74 | * [meta] add `funding` field 75 | * [actions] add automatic rebasing / merge commit blocking 76 | 77 | 3.1.1 / 2019-08-25 78 | ================= 79 | * [Fix] `finally` receiver must only be an Object, not a Promise 80 | * [Deps] update `define-properties`, `es-abstract` 81 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `@es-shims/api`, `covert`, `es6-shim`, `safe-publish-latest`, `tape` 82 | * [Tests] up to `node` `v12.9`, `v11.15`, `v10.16`, `v9.11`, `v8.16`, `v6.17`, `v4.9` 83 | * [Tests] add test for non-Promise receiver 84 | * [Tests] use `npx aud` instead of `nsp` or `npm audit` with hoops 85 | 86 | 3.1.0 / 2017-10-26 87 | ================= 88 | * [New] Add auto shim file, allowing clean 'import' (#12) 89 | * [Refactor] only call `Promise#then` for a brand check once, instead of twice. 90 | * [Deps] update `es-abstract` 91 | * [Dev Deps] update `eslint`, `nsp` 92 | 93 | 3.0.1 / 2017-09-09 94 | ================= 95 | * [Fix] ensure that the “then” brand check doesn’t cause an unhandled rejection warning (#10) 96 | * [Deps] update `es-abstract`, `function-bind` 97 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `nsp`, `tape`, `@es-shims/api` 98 | * [Tests] up to `node` `v8.4`; use `nvm install-latest-npm` so new `npm` doesn’t break old `node`; add 0.8 99 | * [Tests] restore ES5 tests 100 | * [Tests] refactor to allow for unshimmed tests 101 | 102 | 3.0.0 / 2017-07-25 103 | ================= 104 | * [Breaking] update implementation to follow the new spec (#9) 105 | * [Deps] update `es-abstract` 106 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `es6-shim`, `nsp`, `safe-publish-latest`, `tape` 107 | * [Tests] up to `node` `v8.1`, `v7.10`, `v6.11`, `v4.8`; improve matrix 108 | * [Tests] fix 0.10; remove 0.8 109 | 110 | 2.0.1 / 2016-09-27 111 | ================= 112 | * [Fix] functions in IE 9-11 don’t have a `name` property (#3) 113 | 114 | 2.0.0 / 2016-08-21 115 | ================= 116 | * Re-release. 117 | 118 | [1.0.1](https://github.com/matthew-andrews/Promise.prototype.finally/releases/tag/v1.0.1) / 2015-02-09 119 | ================= 120 | * Always replace function for predictability (https://github.com/matthew-andrews/Promise.prototype.finally/issues/3) 121 | * Wrap polyfill so that if it's used direct it doesn't leak 122 | 123 | [1.0.0](https://github.com/matthew-andrews/Promise.prototype.finally/releases/tag/v1.0.0) / 2014-10-11 124 | ================= 125 | * Initial release. 126 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jordan Harband 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # promise.prototype.finally [![Version Badge][npm-version-svg]][package-url] 2 | 3 | [![github actions][actions-image]][actions-url] 4 | [![coverage][codecov-image]][codecov-url] 5 | [![dependency status][deps-svg]][deps-url] 6 | [![dev dependency status][dev-deps-svg]][dev-deps-url] 7 | [![License][license-image]][license-url] 8 | [![Downloads][downloads-image]][downloads-url] 9 | 10 | [![npm badge][npm-badge-png]][package-url] 11 | 12 | ES Proposal spec-compliant shim for Promise.prototype.finally. Invoke its "shim" method to shim `Promise.prototype.finally` if it is unavailable or noncompliant. **Note**: a global `Promise` must already exist: the [es6-shim](https://github.com/es-shims/es6-shim) is recommended. 13 | 14 | This package implements the [es-shim API](https://github.com/es-shims/api) interface. It works in an ES3-supported environment that has `Promise` available globally, and complies with the [proposed spec](https://github.com/tc39/proposal-promise-finally). 15 | 16 | Most common usage: 17 | ```js 18 | var assert = require('assert'); 19 | var promiseFinally = require('promise.prototype.finally'); 20 | 21 | var resolved = Promise.resolve(42); 22 | var rejected = Promise.reject(-1); 23 | 24 | promiseFinally(resolved, function () { 25 | assert.equal(arguments.length, 0); 26 | 27 | return Promise.resolve(true); 28 | }).then(function (x) { 29 | assert.equal(x, 42); 30 | }); 31 | 32 | promiseFinally(rejected, function () { 33 | assert.equal(arguments.length, 0); 34 | }).catch(function (e) { 35 | assert.equal(e, -1); 36 | }); 37 | 38 | promiseFinally(rejected, function () { 39 | assert.equal(arguments.length, 0); 40 | 41 | throw false; 42 | }).catch(function (e) { 43 | assert.equal(e, false); 44 | }); 45 | 46 | promiseFinally.shim(); // will be a no-op if not needed 47 | 48 | resolved.finally(function () { 49 | assert.equal(arguments.length, 0); 50 | 51 | return Promise.resolve(true); 52 | }).then(function (x) { 53 | assert.equal(x, 42); 54 | }); 55 | 56 | rejected.finally(function () { 57 | assert.equal(arguments.length, 0); 58 | }).catch(function (e) { 59 | assert.equal(e, -1); 60 | }); 61 | 62 | rejected.finally(function () { 63 | assert.equal(arguments.length, 0); 64 | 65 | throw false; 66 | }).catch(function (e) { 67 | assert.equal(e, false); 68 | }); 69 | ``` 70 | 71 | ## Tests 72 | Simply clone the repo, `npm install`, and run `npm test` 73 | 74 | ## Thanks 75 | Huge thanks go out to [@matthew-andrews](https://github.com/matthew-andrews), who provided the npm package name for v2 of this module. v1 is both in [the original repo][v1-repo-url] and preserved in [a branch][v1-branch-url] 76 | 77 | [package-url]: https://npmjs.com/package/promise.prototype.finally 78 | [npm-version-svg]: http://versionbadg.es/es-shims/Promise.prototype.finally.svg 79 | [deps-svg]: https://david-dm.org/es-shims/Promise.prototype.finally.svg 80 | [deps-url]: https://david-dm.org/es-shims/Promise.prototype.finally 81 | [dev-deps-svg]: https://david-dm.org/es-shims/Promise.prototype.finally/dev-status.svg 82 | [dev-deps-url]: https://david-dm.org/es-shims/Promise.prototype.finally#info=devDependencies 83 | [testling-svg]: https://ci.testling.com/es-shims/Promise.prototype.finally.png 84 | [testling-url]: https://ci.testling.com/es-shims/Promise.prototype.finally 85 | [npm-badge-png]: https://nodei.co/npm/promise.prototype.finally.png?downloads=true&stars=true 86 | [license-image]: http://img.shields.io/npm/l/promise.prototype.finally.svg 87 | [license-url]: LICENSE 88 | [downloads-image]: http://img.shields.io/npm/dm/promise.prototype.finally.svg 89 | [downloads-url]: http://npm-stat.com/charts.html?package=promise.prototype.finally 90 | [v1-repo-url]: https://github.com/matthew-andrews/Promise.prototype.finally 91 | [v1-branch-url]: https://github.com/es-shims/Promise.prototype.finally/tree/v1 92 | [codecov-image]: https://codecov.io/gh/es-shims/Promise.prototype.finally/branch/main/graphs/badge.svg 93 | [codecov-url]: https://app.codecov.io/gh/es-shims/Promise.prototype.finally/ 94 | [actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/es-shims/Promise.prototype.finally 95 | [actions-url]: https://github.com/es-shims/Promise.prototype.finally/actions 96 | -------------------------------------------------------------------------------- /auto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./shim')(); 4 | -------------------------------------------------------------------------------- /implementation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var requirePromise = require('./requirePromise'); 4 | 5 | requirePromise(); 6 | 7 | var $TypeError = require('es-errors/type'); 8 | 9 | var PromiseResolve = require('es-abstract/2023/PromiseResolve'); 10 | var IsCallable = require('es-abstract/2023/IsCallable'); 11 | var SpeciesConstructor = require('es-abstract/2023/SpeciesConstructor'); 12 | var Type = require('es-abstract/2023/Type'); 13 | 14 | var setFunctionName = require('set-function-name'); 15 | 16 | var OriginalPromise = Promise; 17 | 18 | var createThenFinally = function CreateThenFinally(C, onFinally) { 19 | return function (value) { 20 | var result = onFinally(); 21 | var promise = PromiseResolve(C, result); 22 | var valueThunk = function () { 23 | return value; 24 | }; 25 | return promise.then(valueThunk); 26 | }; 27 | }; 28 | 29 | var createCatchFinally = function CreateCatchFinally(C, onFinally) { 30 | return function (reason) { 31 | var result = onFinally(); 32 | var promise = PromiseResolve(C, result); 33 | var thrower = function () { 34 | throw reason; 35 | }; 36 | return promise.then(thrower); 37 | }; 38 | }; 39 | 40 | /* eslint no-invalid-this: 0 */ 41 | 42 | module.exports = setFunctionName(function finally_(onFinally) { 43 | var promise = this; 44 | 45 | if (Type(promise) !== 'Object') { 46 | throw new $TypeError('receiver is not an Object'); 47 | } 48 | 49 | var C = SpeciesConstructor(promise, OriginalPromise); // may throw 50 | 51 | var thenFinally = onFinally; 52 | var catchFinally = onFinally; 53 | if (IsCallable(onFinally)) { 54 | thenFinally = createThenFinally(C, onFinally); 55 | catchFinally = createCatchFinally(C, onFinally); 56 | } 57 | 58 | return promise.then(thenFinally, catchFinally); 59 | }, 'finally', true); 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var callBind = require('call-bind'); 4 | var define = require('define-properties'); 5 | 6 | var implementation = require('./implementation'); 7 | var getPolyfill = require('./polyfill'); 8 | var shim = require('./shim'); 9 | 10 | var bound = callBind(getPolyfill()); 11 | 12 | define(bound, { 13 | getPolyfill: getPolyfill, 14 | implementation: implementation, 15 | shim: shim 16 | }); 17 | 18 | module.exports = bound; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promise.prototype.finally", 3 | "version": "3.1.8", 4 | "author": "Jordan Harband ", 5 | "funding": { 6 | "url": "https://github.com/sponsors/ljharb" 7 | }, 8 | "contributors": [ 9 | { 10 | "name": "Matt Andrews", 11 | "email": "matt@mattandre.ws" 12 | }, 13 | { 14 | "name": "Jordan Harband", 15 | "email": "ljharb@gmail.com", 16 | "url": "http://ljharb.codes" 17 | } 18 | ], 19 | "description": "ES Proposal spec-compliant shim for Promise.prototype.finally", 20 | "license": "MIT", 21 | "main": "index.js", 22 | "scripts": { 23 | "prepublishOnly": "safe-publish-latest", 24 | "prepublish": "not-in-publish || npm run prepublishOnly", 25 | "pretest": "npm run --silent lint", 26 | "test": "npm run --silent tests-only", 27 | "posttest": "npx aud --production", 28 | "tests-only": "nyc tape test/{implementation,index,shimmed}.js", 29 | "test:promise-shimmed": "nyc node test/promise-shimmed", 30 | "test:native": "nyc node test/native", 31 | "lint": "eslint --ext=.js,.mjs .", 32 | "postlint": "es-shim-api --bound", 33 | "version": "auto-changelog && git add CHANGELOG.md", 34 | "postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\"" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/es-shims/Promise.prototype.finally.git" 39 | }, 40 | "keywords": [ 41 | "Promise", 42 | "promises", 43 | "finally", 44 | "promise.prototype.finally", 45 | "ES7", 46 | "ES8", 47 | "ES2017", 48 | "shim", 49 | "polyfill", 50 | "es-shim API" 51 | ], 52 | "dependencies": { 53 | "call-bind": "^1.0.5", 54 | "define-properties": "^1.2.1", 55 | "es-abstract": "^1.22.3", 56 | "es-errors": "^1.0.0", 57 | "set-function-name": "^2.0.1" 58 | }, 59 | "devDependencies": { 60 | "@es-shims/api": "^2.4.2", 61 | "@ljharb/eslint-config": "^21.1.0", 62 | "aud": "^2.0.4", 63 | "auto-changelog": "^2.4.0", 64 | "es6-shim": "^0.35.8", 65 | "eslint": "=8.8.0", 66 | "in-publish": "^2.0.1", 67 | "nyc": "^10.3.2", 68 | "safe-publish-latest": "^2.0.0", 69 | "tape": "^5.7.4" 70 | }, 71 | "testling": { 72 | "files": "test/index.js", 73 | "browsers": [ 74 | "iexplore/9.0..latest", 75 | "firefox/4.0..6.0", 76 | "firefox/15.0..latest", 77 | "firefox/nightly", 78 | "chrome/4.0..10.0", 79 | "chrome/20.0..latest", 80 | "chrome/canary", 81 | "opera/11.6..latest", 82 | "opera/next", 83 | "safari/5.0..latest", 84 | "ipad/6.0..latest", 85 | "iphone/6.0..latest", 86 | "android-browser/4.2" 87 | ] 88 | }, 89 | "auto-changelog": { 90 | "output": "CHANGELOG.md", 91 | "template": "keepachangelog", 92 | "unreleased": false, 93 | "commitLimit": false, 94 | "backfillLimit": false, 95 | "hideCredit": true, 96 | "startingVersion": "3.1.4" 97 | }, 98 | "engines": { 99 | "node": ">= 0.4" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /polyfill.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var requirePromise = require('./requirePromise'); 4 | 5 | var implementation = require('./implementation'); 6 | 7 | module.exports = function getPolyfill() { 8 | requirePromise(); 9 | return typeof Promise.prototype['finally'] === 'function' ? Promise.prototype['finally'] : implementation; 10 | }; 11 | -------------------------------------------------------------------------------- /requirePromise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function requirePromise() { 4 | if (typeof Promise !== 'function') { 5 | throw new TypeError('`Promise.prototype.finally` requires a global `Promise` be available.'); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /shim.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var requirePromise = require('./requirePromise'); 4 | 5 | var getPolyfill = require('./polyfill'); 6 | var define = require('define-properties'); 7 | 8 | module.exports = function shimPromiseFinally() { 9 | requirePromise(); 10 | 11 | var polyfill = getPolyfill(); 12 | define(Promise.prototype, { 'finally': polyfill }, { 13 | 'finally': function testFinally() { 14 | return Promise.prototype['finally'] !== polyfill; 15 | } 16 | }); 17 | return polyfill; 18 | }; 19 | -------------------------------------------------------------------------------- /test/builtin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var defineProperties = require('define-properties'); 4 | var callBind = require('call-bind'); 5 | var isEnumerable = Object.prototype.propertyIsEnumerable; 6 | var functionsHaveNames = function f() {}.name === 'f'; 7 | var fnNamesConfigurable = functionsHaveNames && Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(function f() {}, 'name').configurable; 8 | 9 | var runTests = require('./tests'); 10 | 11 | module.exports = function (t) { 12 | t.equal(Promise.prototype['finally'].length, 1, 'Promise.prototype.finally has a length of 1'); 13 | t.test('Function name', { skip: !fnNamesConfigurable }, function (st) { 14 | st.equal(Promise.prototype['finally'].name, 'finally', 'Promise.prototype.finally has name "finally"'); 15 | st.end(); 16 | }); 17 | 18 | t.test('enumerability', { skip: !defineProperties.supportsDescriptors }, function (et) { 19 | et.equal(false, isEnumerable.call(Promise.prototype, 'finally'), 'Promise.prototype.finally is not enumerable'); 20 | et.end(); 21 | }); 22 | 23 | var supportsStrictMode = (function () { return typeof this === 'undefined'; }()); 24 | 25 | t.test('bad object value', { skip: !supportsStrictMode }, function (st) { 26 | st['throws'](function () { return Promise.prototype['finally'].call(undefined); }, TypeError, 'undefined is not an object'); 27 | st['throws'](function () { return Promise.prototype['finally'].call(null); }, TypeError, 'null is not an object'); 28 | st.end(); 29 | }); 30 | 31 | runTests(callBind(Promise.prototype['finally']), t); 32 | }; 33 | -------------------------------------------------------------------------------- /test/implementation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape'); 4 | var callBind = require('call-bind'); 5 | 6 | var promiseFinally = require('../implementation'); 7 | var runTests = require('./tests'); 8 | 9 | var bound = callBind(promiseFinally); 10 | 11 | test('as a function', function (t) { 12 | t.test('bad Promise/this value', function (st) { 13 | // eslint-disable-next-line no-useless-call 14 | st['throws'](function () { promiseFinally.call(undefined); }, TypeError, 'undefined is not an object'); 15 | 16 | // eslint-disable-next-line no-useless-call 17 | st['throws'](function () { promiseFinally.call(null); }, TypeError, 'null is not an object'); 18 | st.end(); 19 | }); 20 | 21 | runTests(bound, t); 22 | 23 | t.end(); 24 | }); 25 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var promiseFinally = require('../'); 4 | var test = require('tape'); 5 | var runTests = require('./tests'); 6 | 7 | test('as a function', function (t) { 8 | t.test('bad Promise/this value', function (st) { 9 | st['throws'](function () { promiseFinally(undefined); }, TypeError, 'undefined is not an object'); 10 | st['throws'](function () { promiseFinally(null); }, TypeError, 'null is not an object'); 11 | st.end(); 12 | }); 13 | 14 | runTests(promiseFinally, t); 15 | 16 | t.end(); 17 | }); 18 | -------------------------------------------------------------------------------- /test/native.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape'); 4 | 5 | var runTests = require('./builtin'); 6 | 7 | test('shimmed', function (t) { 8 | runTests(t); 9 | 10 | t.end(); 11 | }); 12 | -------------------------------------------------------------------------------- /test/promise-shimmed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('es6-shim'); 4 | 5 | require('./'); 6 | 7 | require('./shimmed'); 8 | -------------------------------------------------------------------------------- /test/shimmed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('../auto'); 4 | 5 | var test = require('tape'); 6 | 7 | var runTests = require('./builtin'); 8 | 9 | test('shimmed', function (t) { 10 | runTests(t); 11 | 12 | t.end(); 13 | }); 14 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assertArray = function (t, value, length, assertType) { 4 | t.ok(Array.isArray(value), 'value is an array'); 5 | t.equal(value.length, length, 'length is ' + length); 6 | if (typeof assertType === 'function') { 7 | for (var i = 0; i < value.length; i += 1) { 8 | assertType(value[i]); 9 | } 10 | } 11 | }; 12 | 13 | module.exports = function (promiseFinally, t) { 14 | if (typeof Promise !== 'function') { 15 | return t.skip('No global Promise detected'); 16 | } 17 | 18 | t.test('non-Promises', function (st) { 19 | var sentinel = {}; 20 | var onFulfill = function () {}; 21 | var onReject = function () {}; 22 | var results = promiseFinally( 23 | { then: function () { return [sentinel].concat(Array.prototype.slice.call(arguments)); } }, 24 | onFulfill, 25 | onReject 26 | ); 27 | st.equal(results[0], sentinel, 'a receiver with a custom `then` has its return value returned immediately'); 28 | st.equal(typeof results[1], 'function', 'a receiver with a custom `then` gets the right arguments'); 29 | st.equal(typeof results[2], 'function', 'a receiver with a custom `then` gets the right arguments'); 30 | st.end(); 31 | }); 32 | 33 | t.test('onFinally arguments', function (st) { 34 | st.plan(4); 35 | 36 | promiseFinally(Promise.resolve(42), function () { 37 | st.equal(arguments.length, 0, 'resolved promise passes no arguments to onFinally'); 38 | }).then(st.pass, st.fail); 39 | 40 | promiseFinally(Promise.reject(NaN), function () { 41 | st.equal(arguments.length, 0, 'rejected promise passes no arguments to onFinally'); 42 | }).then(st.fail, st.pass); 43 | }); 44 | 45 | t.test('onFinally fulfillment', function (st) { 46 | st.plan(6); 47 | 48 | promiseFinally(Promise.resolve(42), function () { return Promise.resolve(Infinity); }).then(function (x) { 49 | st.equal(x, 42, 'resolved promise onFinally resolution does not affect promise resolution value'); 50 | })['catch'](st.fail); 51 | 52 | promiseFinally(Promise.resolve(42), function () { return Promise.reject(-Infinity); })['catch'](function (x) { 53 | st.equal(x, -Infinity, 'resolved promise onFinally returning a rejected Promise rejects with the new rejection value'); 54 | })['catch'](st.fail); 55 | 56 | promiseFinally(Promise.resolve(42), function () { throw Function; })['catch'](function (e) { 57 | st.equal(e, Function, 'resolved promise onFinally throwing rejects with the thrown rejection value'); 58 | })['catch'](st.fail); 59 | 60 | promiseFinally(Promise.reject(42), function () { return Promise.resolve(Infinity); })['catch'](function (e) { 61 | st.equal(e, 42, 'rejected promise onFinally resolution does not affect promise rejection value'); 62 | })['catch'](st.fail); 63 | 64 | promiseFinally(Promise.reject(42), function () { return Promise.reject(-Infinity); })['catch'](function (x) { 65 | st.equal(x, -Infinity, 'rejected promise onFinally returning a rejected Promise rejects with the new rejection value'); 66 | })['catch'](st.fail); 67 | 68 | promiseFinally(Promise.reject(42), function () { throw Function; })['catch'](function (e) { 69 | st.equal(e, Function, 'rejected promise onFinally throwing rejects with the thrown rejection value'); 70 | })['catch'](st.fail); 71 | }); 72 | 73 | var Subclass = (function () { 74 | try { 75 | // eslint-disable-next-line no-new-func 76 | return Function('class Subclass extends Promise { constructor(...args) { super(...args); this.thenArgs = []; } then(...args) { Subclass.thenArgs.push({ promise: this, args: args }); this.thenArgs.push({ promise: this, args: args }); return super.then(...args); } } Subclass.thenArgs = []; return Subclass;')(); 77 | } catch (e) { /**/ } 78 | 79 | return false; 80 | }()); 81 | t.test('inheritance', { skip: !Subclass }, function (st) { 82 | st.test('preserves correct subclass when chained', function (s2t) { 83 | var promise = promiseFinally(Subclass.resolve()); 84 | s2t.ok(promise instanceof Subclass, 'promise is instanceof Subclass'); 85 | s2t.equal(promise.constructor, Subclass, 'promise.constructor is Subclass'); 86 | 87 | s2t.end(); 88 | }); 89 | 90 | st.test('preserves correct subclass when rejected', function (s2t) { 91 | var promise = promiseFinally(Subclass.resolve(), function () { 92 | throw new Error('OMG'); 93 | }); 94 | s2t.ok(promise instanceof Subclass, 'promise is instanceof Subclass'); 95 | s2t.equal(promise.constructor, Subclass, 'promise.constructor is Subclass'); 96 | 97 | promise['catch'](function () {}); // avoid unhandled rejection warning 98 | 99 | s2t.end(); 100 | }); 101 | 102 | st.test('preserves correct subclass when someone returns a thenable', function (s2t) { 103 | var promise = promiseFinally(Subclass.resolve(), function () { 104 | return Promise.resolve(1); 105 | }); 106 | s2t.ok(promise instanceof Subclass, 'promise is instanceof Subclass'); 107 | s2t.equal(promise.constructor, Subclass, 'promise.constructor is Subclass'); 108 | 109 | s2t.end(); 110 | }); 111 | 112 | st.test('invokes the subclass’ then', function (s2t) { 113 | Subclass.thenArgs.length = 0; 114 | 115 | var original = Subclass.resolve(); 116 | promiseFinally(original, function () {}); 117 | 118 | assertArray(s2t, original.thenArgs, 1); 119 | assertArray(s2t, Subclass.thenArgs, 1); 120 | 121 | s2t.end(); 122 | }); 123 | 124 | st.test('passes the original onFinally when not a function', function (s2t) { 125 | Subclass.thenArgs.length = 0; 126 | 127 | var original = Subclass.resolve(); 128 | var sentinel = {}; 129 | promiseFinally(original, sentinel); 130 | 131 | assertArray(s2t, original.thenArgs, 1, function (x) { s2t.ok(Array.isArray(x.args)); }); 132 | assertArray(s2t, Subclass.thenArgs, 1, function (x) { s2t.ok(Array.isArray(x.args)); }); 133 | 134 | assertArray(s2t, original.thenArgs[0].args, 2, function (x) { s2t.equal(x, sentinel); }); 135 | 136 | s2t.end(); 137 | }); 138 | 139 | st.test('when onFinally is a function, passes thenFinally/catchFinally', function (s2t) { 140 | Subclass.thenArgs.length = 0; 141 | 142 | var sentinel = {}; 143 | var original = Subclass.resolve(sentinel); 144 | var onFinallyArgs = []; 145 | var onFinally = function onFinallyHandler() { 146 | onFinallyArgs.push(Array.prototype.slice.call(arguments)); 147 | return 42; 148 | }; 149 | var promise = promiseFinally(original, onFinally); 150 | 151 | assertArray(s2t, original.thenArgs, 1, function (x) { s2t.ok(Array.isArray(x.args)); }); 152 | assertArray(s2t, Subclass.thenArgs, 1, function (x) { s2t.ok(Array.isArray(x.args)); }); 153 | 154 | var thenArgs = original.thenArgs[0]; 155 | assertArray(s2t, thenArgs.args, 2, function (x) { s2t.equal(typeof x, 'function'); }); 156 | 157 | s2t.deepEqual(onFinallyArgs, [], 'onFinally not yet called'); 158 | 159 | s2t.test('thenFinally works as expected', function (s3t) { 160 | onFinallyArgs.length = 0; 161 | 162 | s3t.plan(6); 163 | 164 | assertArray(s3t, Subclass.thenArgs, 1); 165 | 166 | promise.then(function (x) { 167 | s3t.equal(x, sentinel, 'original resolution value provided'); 168 | s3t.deepEqual(onFinallyArgs, [[]], 'onFinally called once with no args'); 169 | assertArray(s3t, Subclass.thenArgs, 9); 170 | s3t.end(); 171 | })['catch'](s3t.fail); 172 | }); 173 | 174 | s2t.test('catchFinally works as expected', function (s3t) { 175 | onFinallyArgs.length = 0; 176 | 177 | s3t.plan(17); 178 | var thrown = { toString: function () { return 'thrown object'; } }; 179 | var onFinallyRejects = function onFinallyThrower() { 180 | onFinally.apply(undefined, arguments); 181 | throw thrown; 182 | }; 183 | Subclass.thenArgs.length = 0; 184 | 185 | var rejectedPromise = promiseFinally(original, onFinallyRejects); 186 | 187 | assertArray(s3t, Subclass.thenArgs, 1); 188 | 189 | var rejectedPromiseCatch = function (e) { 190 | s3t.equal(e, thrown, 'original rejection value provided'); 191 | s3t.deepEqual(onFinallyArgs, [[]], 'onFinally called once with no args'); 192 | 193 | assertArray(s3t, Subclass.thenArgs, 3); 194 | /* 195 | * 1) initial call with thenFinally/catchFinally 196 | * 2) rejectedPromise.then call 197 | * 3) rejectedPromise.then -> onFinally call 198 | */ 199 | assertArray(s3t, Subclass.thenArgs[0].args, 2, function (x) { s3t.equal(typeof x, 'function'); }); 200 | 201 | assertArray(s3t, Subclass.thenArgs[1].args, 2); 202 | s3t.deepEqual(Subclass.thenArgs[1].args, [s3t.fail, rejectedPromiseCatch], 'rejectedPromise.then call args'); 203 | 204 | assertArray(s3t, Subclass.thenArgs[2].args, 2); 205 | s3t.equal(Subclass.thenArgs[2].args[0], undefined, 'final .then call gets no onFulfill'); 206 | s3t.equal(typeof Subclass.thenArgs[2].args[1], 'function', 'final .then call gets an onReject'); 207 | 208 | s3t.end(); 209 | }; 210 | 211 | rejectedPromise.then(s3t.fail, rejectedPromiseCatch)['catch'](s3t.fail); 212 | }); 213 | 214 | s2t.end(); 215 | }); 216 | 217 | st.test('observable then calls', { todo: true }, function (s2t) { 218 | var mp1Value = {}; 219 | var mp1 = Subclass.resolve(mp1Value); 220 | var mp2 = Subclass.resolve(42); 221 | var mp3 = Subclass.reject(mp1Value); 222 | var mp4 = Subclass.reject(42); 223 | mp3['catch'](function () {}); // avoid unhandled rejection warning 224 | mp4['catch'](function () {}); // avoid unhandled rejection warning 225 | 226 | s2t.test('resolved observable then calls', { todo: true }, function (s3t) { 227 | var orig = Subclass.thenArgs.length; 228 | s3t.plan(6); 229 | return promiseFinally(mp1, function () { return mp2; }).then(function () { 230 | assertArray(s3t, Subclass.thenArgs, orig + 5); 231 | 232 | var mp2Calls = Subclass.thenArgs.filter(function (c) { return c.promise === mp2; }); 233 | assertArray(s3t, mp2Calls, 1); 234 | s3t.equal(mp2Calls[0].args[1], undefined, '`reject` is undefined'); 235 | s3t.equal(mp2Calls[0].args[0](), mp1Value, '`resolve` produces `mp1Value`'); 236 | }); 237 | }); 238 | 239 | s2t.test('rejected observable then calls', { todo: true }, function (s3t) { 240 | var orig = Subclass.thenArgs.length; 241 | s3t.plan(7); 242 | return promiseFinally(mp3, function () { return mp4; }).then(s3t.fail, function () { 243 | assertArray(s3t, Subclass.thenArgs, orig + 5); 244 | 245 | var mp4Calls = Subclass.thenArgs.filter(function (c) { return c.promise === mp4; }); 246 | assertArray(s3t, mp4Calls, 1); 247 | s3t.equal(mp4Calls[0].args[1], undefined, '`reject` is undefined'); 248 | var thrown = false; 249 | try { 250 | mp4Calls[0].args[0](); 251 | } catch (error) { 252 | thrown = true; 253 | s3t.equal(error, mp1Value, 'rejects to `mp1Value`'); 254 | } 255 | s3t.ok(thrown, 'threw an error'); 256 | }); 257 | }); 258 | }); 259 | }); 260 | 261 | return t.comment('tests completed'); 262 | }; 263 | --------------------------------------------------------------------------------