├── .editorconfig ├── .gitignore ├── .jshintrc ├── README.md ├── finally.js ├── package.json └── test └── es6-promise.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.js] 8 | indent_style = tab 9 | 10 | [*.json] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": ["describe", "it"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Promise.prototype.finally [ ![Codeship Status for matthew-andrews/Promise.prototype.finally](https://codeship.io/projects/d2ed45d0-3364-0132-3c6b-26237c03a86a/status)](https://codeship.io/projects/40589) 2 | 3 | ## **Note** 4 | This package has been superseded by https://github.com/es-shims/Promise.prototype.finally as of v2.0.0. Please upgrade! 5 | 6 | ---- 7 | 8 | A polyfill for `Promise.prototype.finally`. See docs on what finally is on [your favourite pre-ES6 promise library](https://github.com/tildeio/rsvp.js/blob/master/README.md#finally). 9 | 10 | Warning: This micro-library doesn't force you to use any particular Promise implementation by using whatever `Promise` has been defined as globally. This is so that you may use any ES6 standard Promise compliant library - or, of course, native ES6 Promises. 11 | 12 | If you're running the code on a browser or node version that doesn't include native promises you will need to include a polyfill. The following polyfills are tested as part of this module's test suite:- 13 | 14 | - [Jake Archibald](https://twitter.com/jaffathecake)'s [ES6 Promise library](https://github.com/jakearchibald/es6-promise) (which is actually adapted from [Stefan Penner](https://twitter.com/stefanpenner)'s [RSVP.js](https://github.com/tildeio/rsvp.js)). - `require('es6-promise').polyfill();` 15 | 16 | ## Installation 17 | 18 | ``` 19 | npm install promise.prototype.finally --save 20 | ``` 21 | 22 | ## Examples 23 | 24 | ```js 25 | require('es6-promise').polyfill(); 26 | require('promise.prototype.finally'); 27 | 28 | Promise.resolve(6) 29 | .finally(function() { 30 | console.log('this will always be called'); 31 | }); 32 | 33 | Promise.reject(6) 34 | .finally(function() { 35 | console.log('this will always be called'); 36 | }); 37 | ``` 38 | 39 | ## Credits and collaboration 40 | 41 | The lead developer of **Promise.prototype.finally** is [Matt Andrews](http://twitter.com/andrewsmatt) at FT, the code is based on [a snippet](https://github.com/domenic/promises-unwrapping/issues/18#issuecomment-57801572) by [Stefan Penner](https://twitter.com/stefanpenner) - used [with permission](https://twitter.com/stefanpenner/status/520904347073667072). All open source code released by FT Labs is licenced under the MIT licence. We welcome comments, feedback and suggestions. Please feel free to raise an issue or pull request. 42 | -------------------------------------------------------------------------------- /finally.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Promise.prototype.finally 3 | * 4 | * Pulled from https://github.com/domenic/promises-unwrapping/issues/18#issuecomment-57801572 5 | * @author @stefanpenner, @matthew-andrews 6 | */ 7 | 8 | (function() { 9 | // Get a handle on the global object 10 | var local; 11 | if (typeof global !== 'undefined') local = global; 12 | else if (typeof window !== 'undefined' && window.document) local = window; 13 | else local = self; 14 | 15 | // It's replaced unconditionally to preserve the expected behavior 16 | // in programs even if there's ever a native finally. 17 | local.Promise.prototype['finally'] = function finallyPolyfill(callback) { 18 | var constructor = this.constructor; 19 | 20 | return this.then(function(value) { 21 | return constructor.resolve(callback()).then(function() { 22 | return value; 23 | }); 24 | }, function(reason) { 25 | return constructor.resolve(callback()).then(function() { 26 | throw reason; 27 | }); 28 | }); 29 | }; 30 | }()); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promise.prototype.finally", 3 | "description": "A polyfill for Promise.prototype.finally for ES6 compliant promises", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "test": "npm run lintspaces && npm run jshint && ./node_modules/.bin/mocha test", 7 | "files": "find . \\( -name '*.js' -o -name '*.json' \\) ! -path './node_modules/*'", 8 | "lintspaces": "./node_modules/.bin/lintspaces -i js-comments -e .editorconfig `npm run -s files`", 9 | "jshint": "./node_modules/.bin/jshint `npm run -s files`" 10 | }, 11 | "main": "finally.js", 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/matthew-andrews/Promise.prototype.finally" 16 | }, 17 | "devDependencies": { 18 | "es6-promise": "^1.0.0", 19 | "jshint": "^2.5.6", 20 | "lintspaces-cli": "0.0.4", 21 | "mocha": "^1.21.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/es6-promise.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | require('es6-promise').polyfill(); 4 | require('../finally'); 5 | 6 | var o_create = Object.create || function(o, props) { 7 | function F() {} 8 | F.prototype = o; 9 | 10 | if (typeof(props) === "object") { 11 | for (var prop in props) { 12 | if (props.hasOwnProperty((prop))) { 13 | F[prop] = props[prop]; 14 | } 15 | } 16 | } 17 | return new F(); 18 | }; 19 | 20 | describe("Promise.finally", function() { 21 | describe("native finally behaviour", function() { 22 | describe("no value is passed in", function() { 23 | it("does not provide a value to the finally code", function(done) { 24 | var fulfillmentValue = 1; 25 | var promise = Promise.resolve(fulfillmentValue); 26 | 27 | promise['finally'](function() { 28 | assert.equal(arguments.length, 0); 29 | done(); 30 | }); 31 | }); 32 | 33 | it("does not provide a reason to the finally code", function(done) { 34 | var rejectionReason = new Error(); 35 | var promise = Promise.reject(rejectionReason); 36 | 37 | promise['finally'](function(arg) { 38 | assert.equal(arguments.length, 0); 39 | done(); 40 | }); 41 | }); 42 | }); 43 | 44 | describe("non-exceptional cases do not affect the result", function(){ 45 | it("preserves the original fulfillment value even if the finally callback returns a value", function(done) { 46 | var fulfillmentValue = 1; 47 | var promise = Promise.resolve(fulfillmentValue); 48 | 49 | promise['finally'](function() { 50 | return 2; 51 | }).then(function(value) { 52 | assert.equal(fulfillmentValue, value); 53 | done(); 54 | }); 55 | }); 56 | 57 | it("preserves the original rejection reason even if the finally callback returns a value", function(done) { 58 | var rejectionReason = new Error(); 59 | var promise = Promise.reject(rejectionReason); 60 | 61 | promise['finally'](function() { 62 | return 2; 63 | }).then(undefined, function(reason) { 64 | assert.equal(rejectionReason, reason); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | 70 | describe("exception cases do propogate the failure", function(){ 71 | describe("fulfilled promise", function(){ 72 | it("propagates changes via throw", function(done) { 73 | var promise = Promise.resolve(1); 74 | var expectedReason = new Error(); 75 | 76 | promise['finally'](function() { 77 | throw expectedReason; 78 | }).then(undefined, function(reason) { 79 | assert.deepEqual(expectedReason, reason); 80 | done(); 81 | }); 82 | }); 83 | 84 | it("propagates changes via returned rejected promise", function(done){ 85 | var promise = Promise.resolve(1); 86 | var expectedReason = new Error(); 87 | 88 | promise['finally'](function() { 89 | return Promise.reject(expectedReason); 90 | }).then(undefined, function(reason) { 91 | assert.deepEqual(expectedReason, reason); 92 | done(); 93 | }); 94 | }); 95 | }); 96 | 97 | describe("rejected promise", function(){ 98 | it("propagates changes via throw", function(done) { 99 | var promise = Promise.reject(1); 100 | var expectedReason = new Error(); 101 | 102 | promise['finally'](function() { 103 | throw expectedReason; 104 | }).then(undefined, function(reason) { 105 | assert.deepEqual(expectedReason, reason); 106 | done(); 107 | }); 108 | }); 109 | 110 | it("propagates changes via returned rejected promise", function(done){ 111 | var promise = Promise.reject(1); 112 | var expectedReason = new Error(); 113 | 114 | promise['finally'](function() { 115 | return Promise.reject(expectedReason); 116 | }).then(undefined, function(reason) { 117 | assert.deepEqual(expectedReason, reason); 118 | done(); 119 | }); 120 | }); 121 | }); 122 | 123 | describe("inheritance", function() { 124 | function Subclass (resolver) { 125 | this._promise$constructor(resolver); 126 | } 127 | 128 | Subclass.prototype = o_create(Promise.prototype); 129 | Subclass.prototype.constructor = Subclass; 130 | Subclass.prototype._promise$constructor = Promise; 131 | 132 | Subclass.resolve = Promise.resolve; 133 | Subclass.reject = Promise.reject; 134 | Subclass.all = Promise.all; 135 | 136 | it("preserves correct subclass when chained", function() { 137 | var promise = Subclass.resolve().finally(); 138 | assert.ok(promise instanceof Subclass); 139 | assert.equal(promise.constructor, Subclass); 140 | }); 141 | 142 | it("preserves correct subclass when rejected", function() { 143 | var promise = Subclass.resolve().finally(function() { 144 | throw new Error("OMG"); 145 | }); 146 | assert.ok(promise instanceof Subclass); 147 | assert.equal(promise.constructor, Subclass); 148 | }); 149 | 150 | it("preserves correct subclass when someone returns a thenable", function() { 151 | var promise = Subclass.resolve().finally(function() { 152 | return Promise.resolve(1); 153 | }); 154 | assert.ok(promise instanceof Subclass); 155 | assert.equal(promise.constructor, Subclass); 156 | }); 157 | }); 158 | }); 159 | }); 160 | }); 161 | --------------------------------------------------------------------------------