From 4dadbc72986d1e88e5f49ed7cad11c837ccfdbcb Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 16 Jan 2019 16:11:51 +0100 Subject: [PATCH] feat: Promise.prototype.finally shim --- README.md | 7 +++ package.json | 1 + promise/#/finally/implement.js | 10 ++++ promise/#/finally/index.js | 3 + promise/#/finally/is-implemented.js | 7 +++ promise/#/finally/shim.js | 24 ++++++++ promise/#/index.js | 2 +- test/promise/#/finally/implement.js | 7 +++ test/promise/#/finally/index.js | 3 + test/promise/#/finally/is-implemented.js | 5 ++ test/promise/#/finally/shim.js | 75 ++++++++++++++++++++++++ 11 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 promise/#/finally/implement.js create mode 100644 promise/#/finally/index.js create mode 100644 promise/#/finally/is-implemented.js create mode 100644 promise/#/finally/shim.js create mode 100644 test/promise/#/finally/implement.js create mode 100644 test/promise/#/finally/index.js create mode 100644 test/promise/#/finally/is-implemented.js create mode 100644 test/promise/#/finally/shim.js diff --git a/README.md b/README.md index a22cd985..7c2bf450 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ It's about properties introduced with ES6 and those that have been updated in ne - `Object.assign` -> `require('es5-ext/object/assign')` - `Object.keys` -> `require('es5-ext/object/keys')` - `Object.setPrototypeOf` -> `require('es5-ext/object/set-prototype-of')` +- `Promise.prototype.finally` -> `require('es5-ext/promise/#/finally')` - `RegExp.prototype.match` -> `require('es5-ext/reg-exp/#/match')` - `RegExp.prototype.replace` -> `require('es5-ext/reg-exp/#/replace')` - `RegExp.prototype.search` -> `require('es5-ext/reg-exp/#/search')` @@ -842,6 +843,12 @@ Throws error if given value is not an object, otherwise it is returned. Throws error if given value is `null` or `undefined`, otherwise returns value. +### Promise Prototype extensions + +#### promise.finally(onFinally) _(es5-ext/promise/#/finally)_ + +[_Introduced with ECMAScript 2018_](https://tc39.github.io/ecma262/#sec-promise.prototype.finally). + ### RegExp Constructor extensions #### escape(str) _(es5-ext/reg-exp/escape)_ diff --git a/package.json b/package.json index 7eea919a..ca865b83 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "devDependencies": { "eslint": "^5.11.1", "eslint-config-medikoo-es5": "^1.7.3", + "plain-promise": "^0.1.1", "tad": "~0.2.8" }, "eslintConfig": { diff --git a/promise/#/finally/implement.js b/promise/#/finally/implement.js new file mode 100644 index 00000000..77592b3a --- /dev/null +++ b/promise/#/finally/implement.js @@ -0,0 +1,10 @@ +"use strict"; + +if (!require("./is-implemented")()) { + Object.defineProperty(Promise.prototype, "finally", { + value: require("./shim"), + configurable: true, + enumerable: false, + writable: true + }); +} diff --git a/promise/#/finally/index.js b/promise/#/finally/index.js new file mode 100644 index 00000000..f6bb447f --- /dev/null +++ b/promise/#/finally/index.js @@ -0,0 +1,3 @@ +"use strict"; + +module.exports = require("./is-implemented")() ? Promise.prototype.finally : require("./shim"); diff --git a/promise/#/finally/is-implemented.js b/promise/#/finally/is-implemented.js new file mode 100644 index 00000000..0534ce6b --- /dev/null +++ b/promise/#/finally/is-implemented.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = function () { + if (typeof Promise !== "function") return false; + if (typeof Promise.prototype.finally !== "function") return false; + return true; +}; diff --git a/promise/#/finally/shim.js b/promise/#/finally/shim.js new file mode 100644 index 00000000..f29f5b31 --- /dev/null +++ b/promise/#/finally/shim.js @@ -0,0 +1,24 @@ +"use strict"; + +var ensurePlainFunction = require("../../../object/ensure-plain-function") + , isThenable = require("../../../object/is-thenable") + , ensureThenable = require("../../../object/ensure-thenable"); + +var resolveCallback = function (callback, next) { + var callbackResult = callback(); + if (!isThenable(callbackResult)) return next(); + return callbackResult.then(next); +}; + +module.exports = function (callback) { + ensureThenable(this); + ensurePlainFunction(callback); + return this.then( + function (result) { + return resolveCallback(callback, function () { return result; }); + }, + function (error) { + return resolveCallback(callback, function () { throw error; }); + } + ); +}; diff --git a/promise/#/index.js b/promise/#/index.js index ece933af..46018030 100644 --- a/promise/#/index.js +++ b/promise/#/index.js @@ -1,3 +1,3 @@ "use strict"; -module.exports = { asCallback: require("./as-callback") }; +module.exports = { asCallback: require("./as-callback"), finally: require("./finally") }; diff --git a/test/promise/#/finally/implement.js b/test/promise/#/finally/implement.js new file mode 100644 index 00000000..94401862 --- /dev/null +++ b/test/promise/#/finally/implement.js @@ -0,0 +1,7 @@ +"use strict"; + +var isImplemented = require("../../../../promise/#/finally/is-implemented"); + +if (typeof Promise !== "function") global.Promise = require("plain-promise"); + +module.exports = function (a) { a(isImplemented(), true); }; diff --git a/test/promise/#/finally/index.js b/test/promise/#/finally/index.js new file mode 100644 index 00000000..10bb8f65 --- /dev/null +++ b/test/promise/#/finally/index.js @@ -0,0 +1,3 @@ +"use strict"; + +module.exports = require("./shim"); diff --git a/test/promise/#/finally/is-implemented.js b/test/promise/#/finally/is-implemented.js new file mode 100644 index 00000000..5003e7e9 --- /dev/null +++ b/test/promise/#/finally/is-implemented.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = function (t, a) { + a(typeof t(), "boolean"); +}; diff --git a/test/promise/#/finally/shim.js b/test/promise/#/finally/shim.js new file mode 100644 index 00000000..a92fab85 --- /dev/null +++ b/test/promise/#/finally/shim.js @@ -0,0 +1,75 @@ +"use strict"; + +var microtaskDelay = require("../../../../function/#/microtask-delay"); + +if (typeof Promise !== "function") global.Promise = require("plain-promise"); + +module.exports = function (t, a) { + return { + Success: function (d) { + var invoked; + t.call(Promise.resolve("foo"), function () { + invoked = true; + return "bar"; + }).then( + microtaskDelay.call(function (result) { + a(result, "foo"); + a(invoked, true); + d(); + }, microtaskDelay.call(d)) + ); + }, + Failure: function (d) { + var invoked; + var error = new Error("Some error"); + t.call(Promise.reject(error), function () { + invoked = true; + return "bar"; + }).then( + microtaskDelay.call(function () { + a.never(); + d(); + }), + microtaskDelay.call(function (result) { + a(result, error); + a(invoked, true); + d(); + }) + ); + }, + SuccessFinallyError: function (d) { + var invoked, finallyError = new Error("Finally error"); + t.call(Promise.resolve("foo"), function () { + invoked = true; + throw finallyError; + }).then( + microtaskDelay.call(function () { + a.never(); + d(); + }), + microtaskDelay.call(function (result) { + a(result, finallyError); + a(invoked, true); + d(); + }) + ); + }, + FailureFinallyError: function (d) { + var invoked, finallyError = new Error("Finally error"); + t.call(Promise.reject(new Error("Some error")), function () { + invoked = true; + throw finallyError; + }).then( + microtaskDelay.call(function () { + a.never(); + d(); + }), + microtaskDelay.call(function (result) { + a(result, finallyError); + a(invoked, true); + d(); + }) + ); + } + }; +};