From 70c9b49bf318f47696065fd40bf6a54b41009d23 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Thu, 19 Jun 2014 16:03:53 +0400 Subject: [PATCH] init commit --- .gitignore | 2 + .npmignore | 3 + CHANGELOG.md | 3 + README.md | 65 ++++++++++ bower.json | 11 ++ package.json | 27 ++++ promise.js | 326 +++++++++++++++++++++++++++++++++++++++++++++++++ promise.min.js | 5 + 8 files changed, 442 insertions(+) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 bower.json create mode 100644 package.json create mode 100644 promise.js create mode 100644 promise.min.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e88a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/tmp \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..3a517ea --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +/node_modules/ +/tmp +/test diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..066a06b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 1.0.0 (june 19, 2014) + +* Init release diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c2d191 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# ES6 Promise polyfill + +This is a polyfill of [ES6 Promise](https://github.com/domenic/promises-unwrapping). The implementation based on [Jake Archibald implementation](https://github.com/jakearchibald/es6-promise) a subset of [rsvp.js](https://github.com/tildeio/rsvp.js). If you're wanting extra features and more debugging options, check out the [full library](https://github.com/tildeio/rsvp.js). + +For API details and how to use promises, see the JavaScript Promises HTML5Rocks article. + +## Notes + +The main target: implementation should be conformance with browser's implementations and to be minimal as possible in size. So it's strictly polyfill of ES6 Promise specification and nothing more. + +It passes both [Promises/A+ test suite](https://github.com/promises-aplus/promises-tests) and [rsvp.js test suite](https://github.com/jakearchibald/es6-promise/tree/master/test). And as small as 2,6KB min (or 1KB min+gzip). + +The polyfill uses `setImmediate` if available, or fallback to use `setTimeout`. Use [setImmediate polyfill](https://github.com/YuzuJS/setImmediate) by @YuzuJS to rich better performance. + +## How to use + +### Browser + +To install: + +```sh +bower install es6-promise-polyfill +``` + +To use: + +```htmpl + + +``` + +### Node.js + +To install: + +```sh +npm install es6-promise-polyfill +``` + +To use: + +```js +var Promise = require('es6-promise-polyfill').Promise; +var promise = new Promise(...); +``` + +## Usage in IE<9 + +`catch` is a reserved word in IE<9, meaning `promise.catch(func)` throws a syntax error. To work around this, use a string to access the property: + +```js +promise['catch'](function(err) { + // ... +}); +``` + +Or use `.then` instead: + +```js +promise.then(undefined, function(err) { + // ... +}); +``` diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..8b28ad4 --- /dev/null +++ b/bower.json @@ -0,0 +1,11 @@ +{ + "name": "es6-promise-polyfill", + "version": "1.0.0", + "main": "promise.js", + "ignore": [ + ".*", + "**/.*", + "node_modules", + "test" + ] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..314af33 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "es6-promise-polyfill", + "namespace": "Promise", + "version": "1.0.0", + "author": "Roman Dvornov ", + "description": "A polyfill for ES6 Promise", + "main": "promise.js", + "directories": { + "lib": "lib" + }, + "devDependencies": { + }, + "scripts": { + }, + "repository": { + "type": "git", + "url": "git://github.com/lahmatiy/es6-promise-polyfill.git" + }, + "bugs": { + "url": "https://github.com/lahmatiy/es6-promise-polyfill/issues" + }, + "keywords": [ + "promises", + "futures", + "events" + ] +} diff --git a/promise.js b/promise.js new file mode 100644 index 0000000..c3f0295 --- /dev/null +++ b/promise.js @@ -0,0 +1,326 @@ +(function(global){ + +// +// Check for native Promise and it has correct interface +// + +var NativePromise = global['Promise']; +var nativePromiseSupported = + NativePromise && + // Some of these methods are missing from + // Firefox/Chrome experimental implementations + 'resolve' in NativePromise && + 'reject' in NativePromise && + 'all' in NativePromise && + 'race' in NativePromise && + // Older version of the spec had a resolver object + // as the arg rather than a function + (function(){ + var resolve; + new NativePromise(function(r){ resolve = r; }); + return typeof resolve === 'function'; + })(); + + +// +// export if necessary +// + +if (typeof exports !== 'undefined' && exports) +{ + // node.js + exports.Promise = Promise || NativePromise; +} +else +{ + // in browser add to global + if (!nativePromiseSupported) + global['Promise'] = Promise; +} + + +// +// Polyfill +// + +var PENDING = 'pending'; +var SEALED = 'sealed'; +var FULFILLED = 'fulfilled'; +var REJECTED = 'rejected'; +var NOOP = function(){}; + +// async calls +var asyncSetTimer = typeof setImmediate !== 'undefined' ? setImmediate : setTimeout; +var asyncQueue = []; +var asyncTimer; + +function asyncFlush(){ + // run promise callbacks + for (var i = 0; i < asyncQueue.length; i++) + asyncQueue[i][0](asyncQueue[i][1]); + + // reset async asyncQueue + asyncQueue = []; + asyncTimer = false; +} + +function asyncCall(callback, arg){ + asyncQueue.push([callback, arg]); + + if (!asyncTimer) + { + asyncTimer = true; + asyncSetTimer(asyncFlush, 0); + } +} + + +function invokeResolver(resolver, promise) { + function resolvePromise(value) { + resolve(promise, value); + } + + function rejectPromise(reason) { + reject(promise, reason); + } + + try { + resolver(resolvePromise, rejectPromise); + } catch(e) { + rejectPromise(e); + } +} + +function invokeCallback(subscriber){ + var owner = subscriber.owner; + var settled = owner.state_; + var value = owner.data_; + var callback = subscriber[settled]; + var promise = subscriber.then; + + if (typeof callback === 'function') + { + settled = FULFILLED; + try { + value = callback(value); + } catch(e) { + reject(promise, e); + } + } + + if (!handleThenable(promise, value)) + { + if (settled === FULFILLED) + resolve(promise, value); + + if (settled === REJECTED) + reject(promise, value); + } +} + +function handleThenable(promise, value) { + var resolved; + + try { + if (promise === value) + throw new TypeError('A promises callback cannot return that same promise.'); + + if (value && (typeof value === 'function' || typeof value === 'object')) + { + var then = value.then; // then should be retrived only once + + if (typeof then === 'function') + { + then.call(value, function(val){ + if (!resolved) + { + resolved = true; + + if (value !== val) + resolve(promise, val); + else + fulfill(promise, val); + } + }, function(reason){ + if (!resolved) + { + resolved = true; + + reject(promise, reason); + } + }); + + return true; + } + } + } catch (e) { + if (!resolved) + reject(promise, e); + + return true; + } + + return false; +} + +function resolve(promise, value){ + if (promise === value || !handleThenable(promise, value)) + fulfill(promise, value); +} + +function fulfill(promise, value){ + if (promise.state_ === PENDING) + { + promise.state_ = SEALED; + promise.data_ = value; + + asyncCall(publishFulfillment, promise); + } +} + +function reject(promise, reason){ + if (promise.state_ === PENDING) + { + promise.state_ = SEALED; + promise.data_ = reason; + + asyncCall(publishRejection, promise); + } +} + +function publish(promise) { + promise.then_ = promise.then_.forEach(invokeCallback); +} + +function publishFulfillment(promise){ + promise.state_ = FULFILLED; + publish(promise); +} + +function publishRejection(promise){ + promise.state_ = REJECTED; + publish(promise); +} + +/** +* @class +*/ +function Promise(resolver){ + if (typeof resolver !== 'function') + throw new TypeError('Promise constructor takes a function argument'); + + if (this instanceof Promise === false) + throw new TypeError('Failed to construct \'Promise\': Please use the \'new\' operator, this object constructor cannot be called as a function.'); + + this.then_ = []; + + invokeResolver(resolver, this); +} + +Promise.prototype = { + constructor: Promise, + + state_: PENDING, + then_: null, + data_: undefined, + + then: function(onFulfillment, onRejection){ + var subscriber = { + owner: this, + then: new this.constructor(NOOP), + fulfilled: onFulfillment, + rejected: onRejection + }; + + if (this.state_ === FULFILLED || this.state_ === REJECTED) + { + // already resolved, call callback async + asyncCall(invokeCallback, subscriber); + } + else + { + // subscribe + this.then_.push(subscriber); + } + + return subscriber.then; + }, + + 'catch': function(onRejection) { + return this.then(null, onRejection); + } +}; + +Promise.all = function(promises){ + var Class = this; + + if (!Array.isArray(promises)) + throw new TypeError('You must pass an array to Promise.all().'); + + return new Class(function(resolve, reject){ + var results = []; + var remaining = 0; + + function resolver(index){ + remaining++; + return function(value){ + results[index] = value; + if (!--remaining) + resolve(results); + }; + } + + for (var i = 0, promise; i < promises.length; i++) + { + promise = promises[i]; + + if (promise && typeof promise.then === 'function') + promise.then(resolver(i), reject); + else + results[i] = promise; + } + + if (!remaining) + resolve(results); + }); +}; + +Promise.race = function(promises){ + var Class = this; + + if (!Array.isArray(promises)) + throw new TypeError('You must pass an array to Promise.race().'); + + return new Class(function(resolve, reject) { + for (var i = 0, promise; i < promises.length; i++) + { + promise = promises[i]; + + if (promise && typeof promise.then === 'function') + promise.then(resolve, reject); + else + resolve(promise); + } + }); +}; + +Promise.resolve = function(value){ + var Class = this; + + if (value && typeof value === 'object' && value.constructor === Class) + return value; + + return new Class(function(resolve){ + resolve(value); + }); +}; + +Promise.reject = function(reason){ + var Class = this; + + return new Class(function(resolve, reject){ + reject(reason); + }); +}; + +})(new Function('return this')()); diff --git a/promise.min.js b/promise.min.js new file mode 100644 index 0000000..ee30fcc --- /dev/null +++ b/promise.min.js @@ -0,0 +1,5 @@ +(function(t){function x(){for(var a=0;a