[Pkg-javascript-commits] [node-promise] 01/13: New upstream version 8.0.1
Praveen Arimbrathodiyil
praveen at moszumanska.debian.org
Wed Jan 3 13:19:26 UTC 2018
This is an automated email from the git hooks/post-receive script.
praveen pushed a commit to branch master
in repository node-promise.
commit 3345930c91a858a7ce3670c0427b37b03734c412
Author: Pirate Praveen <praveen at debian.org>
Date: Wed Jan 3 18:19:45 2018 +0530
New upstream version 8.0.1
---
.gitignore | 8 ++
.npmignore | 1 +
.travis.yml | 12 +-
Readme.md | 150 ++++++++++---------
build.js | 69 +++++++++
component.json | 8 +-
core.js | 104 +-------------
index.d.ts | 246 ++++++++++++++++++++++++++++++++
index.js | 179 +----------------------
index.js.flow | 44 ++++++
package.json | 24 +++-
polyfill-done.js | 12 ++
polyfill.js | 10 ++
src/core.js | 213 +++++++++++++++++++++++++++
src/done.js | 13 ++
src/es6-extensions.js | 107 ++++++++++++++
src/finally.js | 16 +++
src/index.js | 8 ++
src/node-extensions.js | 130 +++++++++++++++++
src/rejection-tracking.js | 113 +++++++++++++++
src/synchronous.js | 62 ++++++++
test/extensions-tests.js | 125 +++++++++++++++-
test/memory-leak.js | 41 ++++++
test/nested-promises.js | 67 +++++++++
test/rejection-tracking.js | 98 +++++++++++++
test/resolver-tests.js | 4 +-
test/synchronous-inspection-tests.js | 269 +++++++++++++++++++++++++++++++++++
27 files changed, 1771 insertions(+), 362 deletions(-)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b0b5a36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+components
+build
+node_modules
+/lib
+/domains
+/setimmediate
+coverage
+package-lock.json
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
index 59bed65..ad5be4a 100644
--- a/.npmignore
+++ b/.npmignore
@@ -4,3 +4,4 @@ test
.gitignore
.travis.yml
component.json
+coverage
diff --git a/.travis.yml b/.travis.yml
index aa94b8b..0ee1adf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,12 @@
language: node_js
+sudo: false
+
node_js:
- - "0.7"
- - "0.8"
- - "0.9"
- "0.10"
+ - "0.12"
+ - "iojs"
+
+after_success:
+ - npm run coverage
+ - npm i coveralls
+ - cat ./coverage/lcov.info | coveralls
diff --git a/Readme.md b/Readme.md
index 26e2003..9e281a7 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,13 +1,25 @@
-<a href="http://promises-aplus.github.com/promises-spec"><img src="http://promises-aplus.github.com/promises-spec/assets/logo-small.png" align="right" /></a>
+<a href="https://promisesaplus.com/"><img src="https://promisesaplus.com/assets/logo-small.png" align="right" /></a>
# promise
This is a simple implementation of Promises. It is a super set of ES6 Promises designed to have readable, performant code and to provide just the extensions that are absolutely necessary for using promises today.
For detailed tutorials on its use, see www.promisejs.org
-[![Build Status](https://travis-ci.org/then/promise.png)](https://travis-ci.org/then/promise)
-[![Dependency Status](https://gemnasium.com/then/promise.png)](https://gemnasium.com/then/promise)
-[![NPM version](https://badge.fury.io/js/promise.png)](http://badge.fury.io/js/promise)
+**N.B.** This promise exposes internals via underscore (`_`) prefixed properties. If you use these, your code will break with each new release.
+
+[![travis][travis-image]][travis-url]
+[![dep][dep-image]][dep-url]
+[![npm][npm-image]][npm-url]
+[![downloads][downloads-image]][downloads-url]
+
+[travis-image]: https://img.shields.io/travis/then/promise.svg?style=flat
+[travis-url]: https://travis-ci.org/then/promise
+[dep-image]: https://img.shields.io/david/then/promise.svg?style=flat
+[dep-url]: https://david-dm.org/then/promise
+[npm-image]: https://img.shields.io/npm/v/promise.svg?style=flat
+[npm-url]: https://npmjs.org/package/promise
+[downloads-image]: https://img.shields.io/npm/dm/promise.svg?style=flat
+[downloads-url]: https://npmjs.org/package/promise
## Installation
@@ -17,15 +29,21 @@ For detailed tutorials on its use, see www.promisejs.org
**Client:**
-You can use browserify on the client, or use the pre-compiled script that acts as a pollyfill.
+You can use browserify on the client, or use the pre-compiled script that acts as a polyfill.
```html
-<script src="https://www.promisejs.org/polyfills/promise-4.0.0.js"></script>
+<script src="https://www.promisejs.org/polyfills/promise-6.1.0.js"></script>
+```
+
+Note that the [es5-shim](https://github.com/es-shims/es5-shim) must be loaded before this library to support browsers pre IE9.
+
+```html
+<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/3.4.0/es5-shim.min.js"></script>
```
## Usage
-The example below shows how you can load the promise library (in a way that works on both client and server). It then demonstrates creating a promise from scratch. You simply call `new Promise(fn)`. There is a complete specification for what is returned by this method in [Promises/A+](http://promises-aplus.github.com/promises-spec/).
+The example below shows how you can load the promise library (in a way that works on both client and server using node or browserify). It then demonstrates creating a promise from scratch. You simply call `new Promise(fn)`. There is a complete specification for what is returned by this method in [Promises/A+](http://promises-aplus.github.com/promises-spec/).
```javascript
var Promise = require('promise');
@@ -38,6 +56,56 @@ var promise = new Promise(function (resolve, reject) {
});
```
+If you need [domains](https://iojs.org/api/domain.html) support, you should instead use:
+
+```js
+var Promise = require('promise/domains');
+```
+
+If you are in an environment that implements `setImmediate` and don't want the optimisations provided by asap, you can use:
+
+```js
+var Promise = require('promise/setimmediate');
+```
+
+If you only want part of the features, e.g. just a pure ES6 polyfill:
+
+```js
+var Promise = require('promise/lib/es6-extensions');
+// or require('promise/domains/es6-extensions');
+// or require('promise/setimmediate/es6-extensions');
+```
+
+## Unhandled Rejections
+
+By default, promises silence any unhandled rejections.
+
+You can enable logging of unhandled ReferenceErrors and TypeErrors via:
+
+```js
+require('promise/lib/rejection-tracking').enable();
+```
+
+Due to the performance cost, you should only do this during development.
+
+You can enable logging of all unhandled rejections if you need to debug an exception you think is being swallowed by promises:
+
+```js
+require('promise/lib/rejection-tracking').enable(
+ {allRejections: true}
+);
+```
+
+Due to the high probability of false positives, I only recommend using this when debugging specific issues that you think may be being swallowed. For the preferred debugging method, see `Promise#done(onFulfilled, onRejected)`.
+
+`rejection-tracking.enable(options)` takes the following options:
+
+ - allRejections (`boolean`) - track all exceptions, not just reference errors and type errors. Note that this has a high probability of resulting in false positives if your code loads data optimisticly
+ - whitelist (`Array<ErrorConstructor>`) - this defaults to `[ReferenceError, TypeError]` but you can override it with your own list of error constructors to track.
+ - `onUnhandled(id, error)` and `onHandled(id, error)` - you can use these to provide your own customised display for errors. Note that if possible you should indicate that the error was a false positive if `onHandled` is called. `onHandled` is only called if `onUnhandled` has already been called.
+
+To reduce the chance of false-positives there is a delay of up to 2 seconds before errors are logged. This means that if you attach an error handler within 2 seconds, it won't be logged as a false positive. ReferenceErrors and TypeErrors are only subject to a 100ms delay due to the higher likelihood that the error is due to programmer error.
+
## API
Before all examples, you will need:
@@ -63,19 +131,16 @@ This creates and returns a new promise. `resolver` must be a function. The `re
Converts values and foreign promises into Promises/A+ promises. If you pass it a value then it returns a Promise for that value. If you pass it something that is close to a promise (such as a jQuery attempt at a promise) it returns a Promise that takes on the state of `value` (rejected or fulfilled).
+#### Promise.reject(value)
+
+Returns a rejected promise with the given value.
+
#### Promise.all(array)
-Returns a promise for an array. If it is called with a single argument that `Array.isArray` then this returns a promise for a copy of that array with any promises replaced by their fulfilled values. Otherwise it returns a promise for an array that conatins its arguments, except with promises replaced by their resolution values. e.g.
+Returns a promise for an array. If it is called with a single argument that `Array.isArray` then this returns a promise for a copy of that array with any promises replaced by their fulfilled values. e.g.
```js
-Promise.all([Promise.from('a'), 'b', Promise.from('c')])
- .then(function (res) {
- assert(res[0] === 'a')
- assert(res[1] === 'b')
- assert(res[2] === 'c')
- })
-
-Promise.all(Promise.from('a'), 'b', Promise.from('c'))
+Promise.all([Promise.resolve('a'), 'b', Promise.resolve('c')])
.then(function (res) {
assert(res[0] === 'a')
assert(res[1] === 'b')
@@ -132,6 +197,10 @@ If the promise is fulfilled then `onFulfilled` is called. If the promise is rej
The call to `.then` also returns a promise. If the handler that is called returns a promise, the promise returned by `.then` takes on the state of that returned promise. If the handler that is called returns a value that is not a promise, the promise returned by `.then` will be fulfilled with that value. If the handler that is called throws an exception then the promise returned by `.then` is rejected with that exception.
+#### Promise#catch(onRejected)
+
+Sugar for `Promise#then(null, onRejected)`, to mirror `catch` in synchronous code.
+
#### Promise#done(onFulfilled, onRejected)
_Non Standard_
@@ -157,55 +226,6 @@ function awesomeAPI(foo, bar, callback) {
People who use typical node.js style callbacks will be able to just pass a callback and get the expected behavior. The enlightened people can not pass a callback and will get awesome promises.
-## Extending Promises
-
- There are three options for extending the promises created by this library.
-
-### Inheritance
-
- You can use inheritance if you want to create your own complete promise library with this as your basic starting point, perfect if you have lots of cool features you want to add. Here is an example of a promise library called `Awesome`, which is built on top of `Promise` correctly.
-
-```javascript
-var Promise = require('promise');
-function Awesome(fn) {
- if (!(this instanceof Awesome)) return new Awesome(fn);
- Promise.call(this, fn);
-}
-Awesome.prototype = Object.create(Promise.prototype);
-Awesome.prototype.constructor = Awesome;
-
-//Awesome extension
-Awesome.prototype.spread = function (cb) {
- return this.then(function (arr) {
- return cb.apply(this, arr);
- })
-};
-```
-
- N.B. if you fail to set the prototype and constructor properly or fail to do Promise.call, things can fail in really subtle ways.
-
-### Wrap
-
- This is the nuclear option, for when you want to start from scratch. It ensures you won't be impacted by anyone who is extending the prototype (see below).
-
-```javascript
-function Uber(fn) {
- if (!(this instanceof Uber)) return new Uber(fn);
- var _prom = new Promise(fn);
- this.then = _prom.then;
-}
-
-Uber.prototype.spread = function (cb) {
- return this.then(function (arr) {
- return cb.apply(this, arr);
- })
-};
-```
-
-### Extending the Prototype
-
- In general, you should never extend the prototype of this promise implimenation because your extensions could easily conflict with someone elses extensions. However, this organisation will host a library of extensions which do not conflict with each other, so you can safely enable any of those. If you think of an extension that we don't provide and you want to write it, submit an issue on this repository and (if I agree) I'll set you up with a repository and give you permission to co [...]
-
## License
MIT
diff --git a/build.js b/build.js
new file mode 100644
index 0000000..1e028e9
--- /dev/null
+++ b/build.js
@@ -0,0 +1,69 @@
+'use strict';
+
+var fs = require('fs');
+var rimraf = require('rimraf');
+var acorn = require('acorn');
+var walk = require('acorn/dist/walk');
+
+var ids = [];
+var names = {};
+
+function getIdFor(name) {
+ if (name in names) return names[name];
+ var id;
+ do {
+ id = '_' + Math.floor(Math.random() * 100);
+ } while (ids.indexOf(id) !== -1)
+ ids.push(id);
+ names[name] = id;
+ return id;
+}
+
+function fixup(src) {
+ var ast = acorn.parse(src);
+ src = src.split('');
+ walk.simple(ast, {
+ MemberExpression: function (node) {
+ if (node.computed) return;
+ if (node.property.type !== 'Identifier') return;
+ if (node.property.name[0] !== '_') return;
+ replace(node.property, getIdFor(node.property.name));
+ }
+ });
+ function source(node) {
+ return src.slice(node.start, node.end).join('');
+ }
+ function replace(node, str) {
+ for (var i = node.start; i < node.end; i++) {
+ src[i] = '';
+ }
+ src[node.start] = str;
+ }
+ return src.join('');
+}
+rimraf.sync(__dirname + '/lib/');
+fs.mkdirSync(__dirname + '/lib/');
+fs.readdirSync(__dirname + '/src').forEach(function (filename) {
+ var src = fs.readFileSync(__dirname + '/src/' + filename, 'utf8');
+ var out = fixup(src);
+ fs.writeFileSync(__dirname + '/lib/' + filename, out);
+});
+
+rimraf.sync(__dirname + '/domains/');
+fs.mkdirSync(__dirname + '/domains/');
+fs.readdirSync(__dirname + '/src').forEach(function (filename) {
+ var src = fs.readFileSync(__dirname + '/src/' + filename, 'utf8');
+ var out = fixup(src);
+ out = out.replace(/require\(\'asap\/raw\'\)/g, "require('asap')");
+ fs.writeFileSync(__dirname + '/domains/' + filename, out);
+});
+
+rimraf.sync(__dirname + '/setimmediate/');
+fs.mkdirSync(__dirname + '/setimmediate/');
+fs.readdirSync(__dirname + '/src').forEach(function (filename) {
+ var src = fs.readFileSync(__dirname + '/src/' + filename, 'utf8');
+ var out = fixup(src);
+ out = out.replace(/var asap \= require\(\'([a-z\/]+)\'\);/g, '');
+ out = out.replace(/asap/g, "setImmediate");
+ fs.writeFileSync(__dirname + '/setimmediate/' + filename, out);
+});
diff --git a/component.json b/component.json
index c572fc5..b71342c 100644
--- a/component.json
+++ b/component.json
@@ -2,16 +2,20 @@
"name": "promise",
"repo": "then/promise",
"description": "Bare bones Promises/A+ implementation",
- "version": "5.0.0",
+ "version": "8.0.1",
"keywords": [],
"dependencies": {
"johntron/asap": "*"
},
"development": {},
"license": "MIT",
+ "main": "index.js",
"scripts": [
"index.js",
- "core.js"
+ "lib/core.js",
+ "lib/done.js",
+ "lib/es6-extensions.js",
+ "lib/node-extensions.js"
],
"twitter": "@ForbesLindesay"
}
\ No newline at end of file
diff --git a/core.js b/core.js
index 763bebf..5f332a2 100644
--- a/core.js
+++ b/core.js
@@ -1,105 +1,5 @@
'use strict';
-var asap = require('asap')
+module.exports = require('./lib/core.js');
-module.exports = Promise
-function Promise(fn) {
- if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new')
- if (typeof fn !== 'function') throw new TypeError('not a function')
- var state = null
- var value = null
- var deferreds = []
- var self = this
-
- this.then = function(onFulfilled, onRejected) {
- return new Promise(function(resolve, reject) {
- handle(new Handler(onFulfilled, onRejected, resolve, reject))
- })
- }
-
- function handle(deferred) {
- if (state === null) {
- deferreds.push(deferred)
- return
- }
- asap(function() {
- var cb = state ? deferred.onFulfilled : deferred.onRejected
- if (cb === null) {
- (state ? deferred.resolve : deferred.reject)(value)
- return
- }
- var ret
- try {
- ret = cb(value)
- }
- catch (e) {
- deferred.reject(e)
- return
- }
- deferred.resolve(ret)
- })
- }
-
- function resolve(newValue) {
- try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
- if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.')
- if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
- var then = newValue.then
- if (typeof then === 'function') {
- doResolve(then.bind(newValue), resolve, reject)
- return
- }
- }
- state = true
- value = newValue
- finale()
- } catch (e) { reject(e) }
- }
-
- function reject(newValue) {
- state = false
- value = newValue
- finale()
- }
-
- function finale() {
- for (var i = 0, len = deferreds.length; i < len; i++)
- handle(deferreds[i])
- deferreds = null
- }
-
- doResolve(fn, resolve, reject)
-}
-
-
-function Handler(onFulfilled, onRejected, resolve, reject){
- this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null
- this.onRejected = typeof onRejected === 'function' ? onRejected : null
- this.resolve = resolve
- this.reject = reject
-}
-
-/**
- * Take a potentially misbehaving resolver function and make sure
- * onFulfilled and onRejected are only called once.
- *
- * Makes no guarantees about asynchrony.
- */
-function doResolve(fn, onFulfilled, onRejected) {
- var done = false;
- try {
- fn(function (value) {
- if (done) return
- done = true
- onFulfilled(value)
- }, function (reason) {
- if (done) return
- done = true
- onRejected(reason)
- })
- } catch (ex) {
- if (done) return
- done = true
- onRejected(ex)
- }
-}
+console.error('require("promise/core") is deprecated, use require("promise/lib/core") instead.');
diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000..e83d65f
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,246 @@
+/**
+ * Represents the completion of an asynchronous operation
+ */
+interface ThenPromise<T> extends Promise<T> {
+ /**
+ * Attaches callbacks for the resolution and/or rejection of the ThenPromise.
+ * @param onfulfilled The callback to execute when the ThenPromise is resolved.
+ * @param onrejected The callback to execute when the ThenPromise is rejected.
+ * @returns A ThenPromise for the completion of which ever callback is executed.
+ */
+ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): ThenPromise<TResult1 | TResult2>;
+
+ /**
+ * Attaches a callback for only the rejection of the ThenPromise.
+ * @param onrejected The callback to execute when the ThenPromise is rejected.
+ * @returns A ThenPromise for the completion of the callback.
+ */
+ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): ThenPromise<T | TResult>;
+
+ // Extensions specific to then/promise
+
+ /**
+ * Attaches callbacks for the resolution and/or rejection of the ThenPromise, without returning a new promise.
+ * @param onfulfilled The callback to execute when the ThenPromise is resolved.
+ * @param onrejected The callback to execute when the ThenPromise is rejected.
+ */
+ done(onfulfilled?: ((value: T) => any) | undefined | null, onrejected?: ((reason: any) => any) | undefined | null): void;
+
+
+ /**
+ * Calls a node.js style callback. If none is provided, the promise is returned.
+ */
+ nodeify(callback: void | null): ThenPromise<T>;
+ nodeify(callback: (err: Error, value: T) => void): void;
+}
+
+interface ThenPromiseConstructor {
+ /**
+ * A reference to the prototype.
+ */
+ readonly prototype: ThenPromise<any>;
+
+ /**
+ * Creates a new ThenPromise.
+ * @param executor A callback used to initialize the promise. This callback is passed two arguments:
+ * a resolve callback used resolve the promise with a value or the result of another promise,
+ * and a reject callback used to reject the promise with a provided reason or error.
+ */
+ new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => any): ThenPromise<T>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): ThenPromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): ThenPromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T1, T2, T3, T4, T5, T6, T7, T8>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): ThenPromise<[T1, T2, T3, T4, T5, T6, T7, T8]>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T1, T2, T3, T4, T5, T6, T7>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): ThenPromise<[T1, T2, T3, T4, T5, T6, T7]>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T1, T2, T3, T4, T5, T6>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): ThenPromise<[T1, T2, T3, T4, T5, T6]>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T1, T2, T3, T4, T5>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>]): ThenPromise<[T1, T2, T3, T4, T5]>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>]): ThenPromise<[T1, T2, T3, T4]>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): ThenPromise<[T1, T2, T3]>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): ThenPromise<[T1, T2]>;
+
+ /**
+ * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises
+ * resolve, or rejected when any ThenPromise is rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ all<T>(values: (T | PromiseLike<T>)[]): ThenPromise<T[]>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): ThenPromise<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): ThenPromise<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T1, T2, T3, T4, T5, T6, T7, T8>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): ThenPromise<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T1, T2, T3, T4, T5, T6, T7>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): ThenPromise<T1 | T2 | T3 | T4 | T5 | T6 | T7>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T1, T2, T3, T4, T5, T6>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): ThenPromise<T1 | T2 | T3 | T4 | T5 | T6>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T1, T2, T3, T4, T5>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>]): ThenPromise<T1 | T2 | T3 | T4 | T5>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>]): ThenPromise<T1 | T2 | T3 | T4>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): ThenPromise<T1 | T2 | T3>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): ThenPromise<T1 | T2>;
+
+ /**
+ * Creates a ThenPromise that is resolved or rejected when any of the provided Promises are resolved
+ * or rejected.
+ * @param values An array of Promises.
+ * @returns A new ThenPromise.
+ */
+ race<T>(values: (T | PromiseLike<T>)[]): ThenPromise<T>;
+
+ /**
+ * Creates a new rejected promise for the provided reason.
+ * @param reason The reason the promise was rejected.
+ * @returns A new rejected ThenPromise.
+ */
+ reject(reason: any): ThenPromise<never>;
+
+ /**
+ * Creates a new rejected promise for the provided reason.
+ * @param reason The reason the promise was rejected.
+ * @returns A new rejected ThenPromise.
+ */
+ reject<T>(reason: any): ThenPromise<T>;
+
+ /**
+ * Creates a new resolved promise for the provided value.
+ * @param value A promise.
+ * @returns A promise whose internal state matches the provided promise.
+ */
+ resolve<T>(value: T | PromiseLike<T>): ThenPromise<T>;
+
+ /**
+ * Creates a new resolved promise .
+ * @returns A resolved promise.
+ */
+ resolve(): ThenPromise<void>;
+
+ // Extensions specific to then/promise
+
+ denodeify: (fn: Function) => (...args: any[]) => ThenPromise<any>;
+ nodeify: (fn: Function) => Function;
+}
+
+declare var ThenPromise: ThenPromiseConstructor;
+
+export = ThenPromise;
\ No newline at end of file
diff --git a/index.js b/index.js
index d96b7fc..1c38e46 100644
--- a/index.js
+++ b/index.js
@@ -1,180 +1,3 @@
'use strict';
-//This file contains then/promise specific extensions to the core promise API
-
-var Promise = require('./core.js')
-var asap = require('asap')
-
-module.exports = Promise
-
-/* Static Functions */
-
-function ValuePromise(value) {
- this.then = function (onFulfilled) {
- if (typeof onFulfilled !== 'function') return this
- return new Promise(function (resolve, reject) {
- asap(function () {
- try {
- resolve(onFulfilled(value))
- } catch (ex) {
- reject(ex);
- }
- })
- })
- }
-}
-ValuePromise.prototype = Object.create(Promise.prototype)
-
-var TRUE = new ValuePromise(true)
-var FALSE = new ValuePromise(false)
-var NULL = new ValuePromise(null)
-var UNDEFINED = new ValuePromise(undefined)
-var ZERO = new ValuePromise(0)
-var EMPTYSTRING = new ValuePromise('')
-
-Promise.resolve = function (value) {
- if (value instanceof Promise) return value
-
- if (value === null) return NULL
- if (value === undefined) return UNDEFINED
- if (value === true) return TRUE
- if (value === false) return FALSE
- if (value === 0) return ZERO
- if (value === '') return EMPTYSTRING
-
- if (typeof value === 'object' || typeof value === 'function') {
- try {
- var then = value.then
- if (typeof then === 'function') {
- return new Promise(then.bind(value))
- }
- } catch (ex) {
- return new Promise(function (resolve, reject) {
- reject(ex)
- })
- }
- }
-
- return new ValuePromise(value)
-}
-
-Promise.from = Promise.cast = function (value) {
- var err = new Error('Promise.from and Promise.cast are deprecated, use Promise.resolve instead')
- err.name = 'Warning'
- console.warn(err.stack)
- return Promise.resolve(value)
-}
-
-Promise.denodeify = function (fn, argumentCount) {
- argumentCount = argumentCount || Infinity
- return function () {
- var self = this
- var args = Array.prototype.slice.call(arguments)
- return new Promise(function (resolve, reject) {
- while (args.length && args.length > argumentCount) {
- args.pop()
- }
- args.push(function (err, res) {
- if (err) reject(err)
- else resolve(res)
- })
- fn.apply(self, args)
- })
- }
-}
-Promise.nodeify = function (fn) {
- return function () {
- var args = Array.prototype.slice.call(arguments)
- var callback = typeof args[args.length - 1] === 'function' ? args.pop() : null
- try {
- return fn.apply(this, arguments).nodeify(callback)
- } catch (ex) {
- if (callback === null || typeof callback == 'undefined') {
- return new Promise(function (resolve, reject) { reject(ex) })
- } else {
- asap(function () {
- callback(ex)
- })
- }
- }
- }
-}
-
-Promise.all = function () {
- var calledWithArray = arguments.length === 1 && Array.isArray(arguments[0])
- var args = Array.prototype.slice.call(calledWithArray ? arguments[0] : arguments)
-
- if (!calledWithArray) {
- var err = new Error('Promise.all should be called with a single array, calling it with multiple arguments is deprecated')
- err.name = 'Warning'
- console.warn(err.stack)
- }
-
- return new Promise(function (resolve, reject) {
- if (args.length === 0) return resolve([])
- var remaining = args.length
- function res(i, val) {
- try {
- if (val && (typeof val === 'object' || typeof val === 'function')) {
- var then = val.then
- if (typeof then === 'function') {
- then.call(val, function (val) { res(i, val) }, reject)
- return
- }
- }
- args[i] = val
- if (--remaining === 0) {
- resolve(args);
- }
- } catch (ex) {
- reject(ex)
- }
- }
- for (var i = 0; i < args.length; i++) {
- res(i, args[i])
- }
- })
-}
-
-Promise.reject = function (value) {
- return new Promise(function (resolve, reject) {
- reject(value);
- });
-}
-
-Promise.race = function (values) {
- return new Promise(function (resolve, reject) {
- values.forEach(function(value){
- Promise.resolve(value).then(resolve, reject);
- })
- });
-}
-
-/* Prototype Methods */
-
-Promise.prototype.done = function (onFulfilled, onRejected) {
- var self = arguments.length ? this.then.apply(this, arguments) : this
- self.then(null, function (err) {
- asap(function () {
- throw err
- })
- })
-}
-
-Promise.prototype.nodeify = function (callback) {
- if (typeof callback != 'function') return this
-
- this.then(function (value) {
- asap(function () {
- callback(null, value)
- })
- }, function (err) {
- asap(function () {
- callback(err)
- })
- })
-}
-
-Promise.prototype['catch'] = function (onRejected) {
- return this.then(null, onRejected);
-}
+module.exports = require('./lib')
diff --git a/index.js.flow b/index.js.flow
new file mode 100644
index 0000000..78db68c
--- /dev/null
+++ b/index.js.flow
@@ -0,0 +1,44 @@
+// @flow
+
+declare class ThenPromise<+R> extends Promise<R> {
+ constructor(callback: (
+ resolve: (result: Promise<R> | R) => void,
+ reject: (error: any) => void
+ ) => mixed): void;
+
+ then<U>(
+ onFulfill?: (value: R) => Promise<U> | U,
+ onReject?: (error: any) => Promise<U> | U
+ ): ThenPromise<U>;
+
+ catch<U>(
+ onReject?: (error: any) => Promise<U> | U
+ ): ThenPromise<R | U>;
+
+ // Extensions specific to then/promise
+
+ /**
+ * Attaches callbacks for the resolution and/or rejection of the ThenPromise, without returning a new promise.
+ * @param onfulfilled The callback to execute when the ThenPromise is resolved.
+ * @param onrejected The callback to execute when the ThenPromise is rejected.
+ */
+ done(onfulfilled?: (value: R) => any, onrejected?: (reason: any) => any): void;
+
+
+ /**
+ * Calls a node.js style callback. If none is provided, the promise is returned.
+ */
+ nodeify(callback: void | null): ThenPromise<R>;
+ nodeify(callback: (err: Error, value: R) => void): void;
+
+ static resolve<T>(object: Promise<T> | T): ThenPromise<T>;
+ static reject<T>(error?: any): ThenPromise<T>;
+ static all<Elem, T:Iterable<Elem>>(promises: T): ThenPromise<$TupleMap<T, typeof $await>>;
+ static race<T, Elem: Promise<T> | T>(promises: Array<Elem>): ThenPromise<T>;
+
+ // Extensions specific to then/promise
+
+ static denodeify(fn: Function): (...args: any[]) => ThenPromise<any>;
+ static nodeify(fn: Function): Function;
+}
+module.exports = ThenPromise;
diff --git a/package.json b/package.json
index d661b79..2b8e45e 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,19 @@
{
"name": "promise",
- "version": "5.0.0",
+ "version": "8.0.1",
"description": "Bare bones Promises/A+ implementation",
"main": "index.js",
"scripts": {
- "test": "mocha -R spec --timeout 200 --slow 99999",
- "test-resolve": "mocha test/resolver-tests.js -R spec --timeout 200 --slow 999999",
- "test-extensions": "mocha test/extensions-tests.js -R spec --timeout 200 --slow 999999"
+ "prepublish": "node build",
+ "pretest": "node build",
+ "pretest-resolve": "node build",
+ "pretest-extensions": "node build",
+ "pretest-memory-leak": "node build",
+ "test": "mocha --bail --timeout 200 --slow 99999 -R dot && npm run test-memory-leak",
+ "test-resolve": "mocha test/resolver-tests.js --timeout 200 --slow 999999",
+ "test-extensions": "mocha test/extensions-tests.js --timeout 200 --slow 999999",
+ "test-memory-leak": "node --expose-gc test/memory-leak.js",
+ "coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --bail --timeout 200 --slow 99999 -R dot"
},
"repository": {
"type": "git",
@@ -15,11 +22,14 @@
"author": "ForbesLindesay",
"license": "MIT",
"devDependencies": {
- "promises-aplus-tests": "*",
+ "acorn": "^1.0.1",
"better-assert": "*",
- "mocha": "*"
+ "istanbul": "^0.3.13",
+ "mocha": "*",
+ "promises-aplus-tests": "*",
+ "rimraf": "^2.3.2"
},
"dependencies": {
- "asap": "~1.0.0"
+ "asap": "~2.0.3"
}
}
\ No newline at end of file
diff --git a/polyfill-done.js b/polyfill-done.js
new file mode 100644
index 0000000..e50b4c0
--- /dev/null
+++ b/polyfill-done.js
@@ -0,0 +1,12 @@
+// should work in any browser without browserify
+
+if (typeof Promise.prototype.done !== 'function') {
+ Promise.prototype.done = function (onFulfilled, onRejected) {
+ var self = arguments.length ? this.then.apply(this, arguments) : this
+ self.then(null, function (err) {
+ setTimeout(function () {
+ throw err
+ }, 0)
+ })
+ }
+}
\ No newline at end of file
diff --git a/polyfill.js b/polyfill.js
new file mode 100644
index 0000000..db099f8
--- /dev/null
+++ b/polyfill.js
@@ -0,0 +1,10 @@
+// not "use strict" so we can declare global "Promise"
+
+var asap = require('asap');
+
+if (typeof Promise === 'undefined') {
+ Promise = require('./lib/core.js')
+ require('./lib/es6-extensions.js')
+}
+
+require('./polyfill-done.js');
diff --git a/src/core.js b/src/core.js
new file mode 100644
index 0000000..312010d
--- /dev/null
+++ b/src/core.js
@@ -0,0 +1,213 @@
+'use strict';
+
+var asap = require('asap/raw');
+
+function noop() {}
+
+// States:
+//
+// 0 - pending
+// 1 - fulfilled with _value
+// 2 - rejected with _value
+// 3 - adopted the state of another promise, _value
+//
+// once the state is no longer pending (0) it is immutable
+
+// All `_` prefixed properties will be reduced to `_{random number}`
+// at build time to obfuscate them and discourage their use.
+// We don't use symbols or Object.defineProperty to fully hide them
+// because the performance isn't good enough.
+
+
+// to avoid using try/catch inside critical functions, we
+// extract them to here.
+var LAST_ERROR = null;
+var IS_ERROR = {};
+function getThen(obj) {
+ try {
+ return obj.then;
+ } catch (ex) {
+ LAST_ERROR = ex;
+ return IS_ERROR;
+ }
+}
+
+function tryCallOne(fn, a) {
+ try {
+ return fn(a);
+ } catch (ex) {
+ LAST_ERROR = ex;
+ return IS_ERROR;
+ }
+}
+function tryCallTwo(fn, a, b) {
+ try {
+ fn(a, b);
+ } catch (ex) {
+ LAST_ERROR = ex;
+ return IS_ERROR;
+ }
+}
+
+module.exports = Promise;
+
+function Promise(fn) {
+ if (typeof this !== 'object') {
+ throw new TypeError('Promises must be constructed via new');
+ }
+ if (typeof fn !== 'function') {
+ throw new TypeError('Promise constructor\'s argument is not a function');
+ }
+ this._deferredState = 0;
+ this._state = 0;
+ this._value = null;
+ this._deferreds = null;
+ if (fn === noop) return;
+ doResolve(fn, this);
+}
+Promise._onHandle = null;
+Promise._onReject = null;
+Promise._noop = noop;
+
+Promise.prototype.then = function(onFulfilled, onRejected) {
+ if (this.constructor !== Promise) {
+ return safeThen(this, onFulfilled, onRejected);
+ }
+ var res = new Promise(noop);
+ handle(this, new Handler(onFulfilled, onRejected, res));
+ return res;
+};
+
+function safeThen(self, onFulfilled, onRejected) {
+ return new self.constructor(function (resolve, reject) {
+ var res = new Promise(noop);
+ res.then(resolve, reject);
+ handle(self, new Handler(onFulfilled, onRejected, res));
+ });
+}
+function handle(self, deferred) {
+ while (self._state === 3) {
+ self = self._value;
+ }
+ if (Promise._onHandle) {
+ Promise._onHandle(self);
+ }
+ if (self._state === 0) {
+ if (self._deferredState === 0) {
+ self._deferredState = 1;
+ self._deferreds = deferred;
+ return;
+ }
+ if (self._deferredState === 1) {
+ self._deferredState = 2;
+ self._deferreds = [self._deferreds, deferred];
+ return;
+ }
+ self._deferreds.push(deferred);
+ return;
+ }
+ handleResolved(self, deferred);
+}
+
+function handleResolved(self, deferred) {
+ asap(function() {
+ var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
+ if (cb === null) {
+ if (self._state === 1) {
+ resolve(deferred.promise, self._value);
+ } else {
+ reject(deferred.promise, self._value);
+ }
+ return;
+ }
+ var ret = tryCallOne(cb, self._value);
+ if (ret === IS_ERROR) {
+ reject(deferred.promise, LAST_ERROR);
+ } else {
+ resolve(deferred.promise, ret);
+ }
+ });
+}
+function resolve(self, newValue) {
+ // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
+ if (newValue === self) {
+ return reject(
+ self,
+ new TypeError('A promise cannot be resolved with itself.')
+ );
+ }
+ if (
+ newValue &&
+ (typeof newValue === 'object' || typeof newValue === 'function')
+ ) {
+ var then = getThen(newValue);
+ if (then === IS_ERROR) {
+ return reject(self, LAST_ERROR);
+ }
+ if (
+ then === self.then &&
+ newValue instanceof Promise
+ ) {
+ self._state = 3;
+ self._value = newValue;
+ finale(self);
+ return;
+ } else if (typeof then === 'function') {
+ doResolve(then.bind(newValue), self);
+ return;
+ }
+ }
+ self._state = 1;
+ self._value = newValue;
+ finale(self);
+}
+
+function reject(self, newValue) {
+ self._state = 2;
+ self._value = newValue;
+ if (Promise._onReject) {
+ Promise._onReject(self, newValue);
+ }
+ finale(self);
+}
+function finale(self) {
+ if (self._deferredState === 1) {
+ handle(self, self._deferreds);
+ self._deferreds = null;
+ }
+ if (self._deferredState === 2) {
+ for (var i = 0; i < self._deferreds.length; i++) {
+ handle(self, self._deferreds[i]);
+ }
+ self._deferreds = null;
+ }
+}
+
+function Handler(onFulfilled, onRejected, promise){
+ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
+ this.onRejected = typeof onRejected === 'function' ? onRejected : null;
+ this.promise = promise;
+}
+
+/**
+ * Take a potentially misbehaving resolver function and make sure
+ * onFulfilled and onRejected are only called once.
+ *
+ * Makes no guarantees about asynchrony.
+ */
+function doResolve(fn, promise) {
+ var done = false;
+ var res = tryCallTwo(fn, function (value) {
+ if (done) return;
+ done = true;
+ resolve(promise, value);
+ }, function (reason) {
+ if (done) return;
+ done = true;
+ reject(promise, reason);
+ });
+ if (!done && res === IS_ERROR) {
+ done = true;
+ reject(promise, LAST_ERROR);
+ }
+}
diff --git a/src/done.js b/src/done.js
new file mode 100644
index 0000000..f879317
--- /dev/null
+++ b/src/done.js
@@ -0,0 +1,13 @@
+'use strict';
+
+var Promise = require('./core.js');
+
+module.exports = Promise;
+Promise.prototype.done = function (onFulfilled, onRejected) {
+ var self = arguments.length ? this.then.apply(this, arguments) : this;
+ self.then(null, function (err) {
+ setTimeout(function () {
+ throw err;
+ }, 0);
+ });
+};
diff --git a/src/es6-extensions.js b/src/es6-extensions.js
new file mode 100644
index 0000000..0435813
--- /dev/null
+++ b/src/es6-extensions.js
@@ -0,0 +1,107 @@
+'use strict';
+
+//This file contains the ES6 extensions to the core Promises/A+ API
+
+var Promise = require('./core.js');
+
+module.exports = Promise;
+
+/* Static Functions */
+
+var TRUE = valuePromise(true);
+var FALSE = valuePromise(false);
+var NULL = valuePromise(null);
+var UNDEFINED = valuePromise(undefined);
+var ZERO = valuePromise(0);
+var EMPTYSTRING = valuePromise('');
+
+function valuePromise(value) {
+ var p = new Promise(Promise._noop);
+ p._state = 1;
+ p._value = value;
+ return p;
+}
+Promise.resolve = function (value) {
+ if (value instanceof Promise) return value;
+
+ if (value === null) return NULL;
+ if (value === undefined) return UNDEFINED;
+ if (value === true) return TRUE;
+ if (value === false) return FALSE;
+ if (value === 0) return ZERO;
+ if (value === '') return EMPTYSTRING;
+
+ if (typeof value === 'object' || typeof value === 'function') {
+ try {
+ var then = value.then;
+ if (typeof then === 'function') {
+ return new Promise(then.bind(value));
+ }
+ } catch (ex) {
+ return new Promise(function (resolve, reject) {
+ reject(ex);
+ });
+ }
+ }
+ return valuePromise(value);
+};
+
+Promise.all = function (arr) {
+ var args = Array.prototype.slice.call(arr);
+
+ return new Promise(function (resolve, reject) {
+ if (args.length === 0) return resolve([]);
+ var remaining = args.length;
+ function res(i, val) {
+ if (val && (typeof val === 'object' || typeof val === 'function')) {
+ if (val instanceof Promise && val.then === Promise.prototype.then) {
+ while (val._state === 3) {
+ val = val._value;
+ }
+ if (val._state === 1) return res(i, val._value);
+ if (val._state === 2) reject(val._value);
+ val.then(function (val) {
+ res(i, val);
+ }, reject);
+ return;
+ } else {
+ var then = val.then;
+ if (typeof then === 'function') {
+ var p = new Promise(then.bind(val));
+ p.then(function (val) {
+ res(i, val);
+ }, reject);
+ return;
+ }
+ }
+ }
+ args[i] = val;
+ if (--remaining === 0) {
+ resolve(args);
+ }
+ }
+ for (var i = 0; i < args.length; i++) {
+ res(i, args[i]);
+ }
+ });
+};
+
+Promise.reject = function (value) {
+ return new Promise(function (resolve, reject) {
+ reject(value);
+ });
+};
+
+Promise.race = function (values) {
+ return new Promise(function (resolve, reject) {
+ values.forEach(function(value){
+ Promise.resolve(value).then(resolve, reject);
+ });
+ });
+};
+
+/* Prototype Methods */
+
+Promise.prototype['catch'] = function (onRejected) {
+ return this.then(null, onRejected);
+};
diff --git a/src/finally.js b/src/finally.js
new file mode 100644
index 0000000..f5ee0b9
--- /dev/null
+++ b/src/finally.js
@@ -0,0 +1,16 @@
+'use strict';
+
+var Promise = require('./core.js');
+
+module.exports = Promise;
+Promise.prototype['finally'] = function (f) {
+ return this.then(function (value) {
+ return Promise.resolve(f()).then(function () {
+ return value;
+ });
+ }, function (err) {
+ return Promise.resolve(f()).then(function () {
+ throw err;
+ });
+ });
+};
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..6e674f3
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = require('./core.js');
+require('./done.js');
+require('./finally.js');
+require('./es6-extensions.js');
+require('./node-extensions.js');
+require('./synchronous.js');
diff --git a/src/node-extensions.js b/src/node-extensions.js
new file mode 100644
index 0000000..157cddc
--- /dev/null
+++ b/src/node-extensions.js
@@ -0,0 +1,130 @@
+'use strict';
+
+// This file contains then/promise specific extensions that are only useful
+// for node.js interop
+
+var Promise = require('./core.js');
+var asap = require('asap');
+
+module.exports = Promise;
+
+/* Static Functions */
+
+Promise.denodeify = function (fn, argumentCount) {
+ if (
+ typeof argumentCount === 'number' && argumentCount !== Infinity
+ ) {
+ return denodeifyWithCount(fn, argumentCount);
+ } else {
+ return denodeifyWithoutCount(fn);
+ }
+};
+
+var callbackFn = (
+ 'function (err, res) {' +
+ 'if (err) { rj(err); } else { rs(res); }' +
+ '}'
+);
+function denodeifyWithCount(fn, argumentCount) {
+ var args = [];
+ for (var i = 0; i < argumentCount; i++) {
+ args.push('a' + i);
+ }
+ var body = [
+ 'return function (' + args.join(',') + ') {',
+ 'var self = this;',
+ 'return new Promise(function (rs, rj) {',
+ 'var res = fn.call(',
+ ['self'].concat(args).concat([callbackFn]).join(','),
+ ');',
+ 'if (res &&',
+ '(typeof res === "object" || typeof res === "function") &&',
+ 'typeof res.then === "function"',
+ ') {rs(res);}',
+ '});',
+ '};'
+ ].join('');
+ return Function(['Promise', 'fn'], body)(Promise, fn);
+}
+function denodeifyWithoutCount(fn) {
+ var fnLength = Math.max(fn.length - 1, 3);
+ var args = [];
+ for (var i = 0; i < fnLength; i++) {
+ args.push('a' + i);
+ }
+ var body = [
+ 'return function (' + args.join(',') + ') {',
+ 'var self = this;',
+ 'var args;',
+ 'var argLength = arguments.length;',
+ 'if (arguments.length > ' + fnLength + ') {',
+ 'args = new Array(arguments.length + 1);',
+ 'for (var i = 0; i < arguments.length; i++) {',
+ 'args[i] = arguments[i];',
+ '}',
+ '}',
+ 'return new Promise(function (rs, rj) {',
+ 'var cb = ' + callbackFn + ';',
+ 'var res;',
+ 'switch (argLength) {',
+ args.concat(['extra']).map(function (_, index) {
+ return (
+ 'case ' + (index) + ':' +
+ 'res = fn.call(' + ['self'].concat(args.slice(0, index)).concat('cb').join(',') + ');' +
+ 'break;'
+ );
+ }).join(''),
+ 'default:',
+ 'args[argLength] = cb;',
+ 'res = fn.apply(self, args);',
+ '}',
+
+ 'if (res &&',
+ '(typeof res === "object" || typeof res === "function") &&',
+ 'typeof res.then === "function"',
+ ') {rs(res);}',
+ '});',
+ '};'
+ ].join('');
+
+ return Function(
+ ['Promise', 'fn'],
+ body
+ )(Promise, fn);
+}
+
+Promise.nodeify = function (fn) {
+ return function () {
+ var args = Array.prototype.slice.call(arguments);
+ var callback =
+ typeof args[args.length - 1] === 'function' ? args.pop() : null;
+ var ctx = this;
+ try {
+ return fn.apply(this, arguments).nodeify(callback, ctx);
+ } catch (ex) {
+ if (callback === null || typeof callback == 'undefined') {
+ return new Promise(function (resolve, reject) {
+ reject(ex);
+ });
+ } else {
+ asap(function () {
+ callback.call(ctx, ex);
+ })
+ }
+ }
+ }
+};
+
+Promise.prototype.nodeify = function (callback, ctx) {
+ if (typeof callback != 'function') return this;
+
+ this.then(function (value) {
+ asap(function () {
+ callback.call(ctx, null, value);
+ });
+ }, function (err) {
+ asap(function () {
+ callback.call(ctx, err);
+ });
+ });
+};
diff --git a/src/rejection-tracking.js b/src/rejection-tracking.js
new file mode 100644
index 0000000..33a59a1
--- /dev/null
+++ b/src/rejection-tracking.js
@@ -0,0 +1,113 @@
+'use strict';
+
+var Promise = require('./core');
+
+var DEFAULT_WHITELIST = [
+ ReferenceError,
+ TypeError,
+ RangeError
+];
+
+var enabled = false;
+exports.disable = disable;
+function disable() {
+ enabled = false;
+ Promise._onHandle = null;
+ Promise._onReject = null;
+}
+
+exports.enable = enable;
+function enable(options) {
+ options = options || {};
+ if (enabled) disable();
+ enabled = true;
+ var id = 0;
+ var displayId = 0;
+ var rejections = {};
+ Promise._onHandle = function (promise) {
+ if (
+ promise._state === 2 && // IS REJECTED
+ rejections[promise._rejectionId]
+ ) {
+ if (rejections[promise._rejectionId].logged) {
+ onHandled(promise._rejectionId);
+ } else {
+ clearTimeout(rejections[promise._rejectionId].timeout);
+ }
+ delete rejections[promise._rejectionId];
+ }
+ };
+ Promise._onReject = function (promise, err) {
+ if (promise._deferredState === 0) { // not yet handled
+ promise._rejectionId = id++;
+ rejections[promise._rejectionId] = {
+ displayId: null,
+ error: err,
+ timeout: setTimeout(
+ onUnhandled.bind(null, promise._rejectionId),
+ // For reference errors and type errors, this almost always
+ // means the programmer made a mistake, so log them after just
+ // 100ms
+ // otherwise, wait 2 seconds to see if they get handled
+ matchWhitelist(err, DEFAULT_WHITELIST)
+ ? 100
+ : 2000
+ ),
+ logged: false
+ };
+ }
+ };
+ function onUnhandled(id) {
+ if (
+ options.allRejections ||
+ matchWhitelist(
+ rejections[id].error,
+ options.whitelist || DEFAULT_WHITELIST
+ )
+ ) {
+ rejections[id].displayId = displayId++;
+ if (options.onUnhandled) {
+ rejections[id].logged = true;
+ options.onUnhandled(
+ rejections[id].displayId,
+ rejections[id].error
+ );
+ } else {
+ rejections[id].logged = true;
+ logError(
+ rejections[id].displayId,
+ rejections[id].error
+ );
+ }
+ }
+ }
+ function onHandled(id) {
+ if (rejections[id].logged) {
+ if (options.onHandled) {
+ options.onHandled(rejections[id].displayId, rejections[id].error);
+ } else if (!rejections[id].onUnhandled) {
+ console.warn(
+ 'Promise Rejection Handled (id: ' + rejections[id].displayId + '):'
+ );
+ console.warn(
+ ' This means you can ignore any previous messages of the form "Possible Unhandled Promise Rejection" with id ' +
+ rejections[id].displayId + '.'
+ );
+ }
+ }
+ }
+}
+
+function logError(id, error) {
+ console.warn('Possible Unhandled Promise Rejection (id: ' + id + '):');
+ var errStr = (error && (error.stack || error)) + '';
+ errStr.split('\n').forEach(function (line) {
+ console.warn(' ' + line);
+ });
+}
+
+function matchWhitelist(error, list) {
+ return list.some(function (cls) {
+ return error instanceof cls;
+ });
+}
\ No newline at end of file
diff --git a/src/synchronous.js b/src/synchronous.js
new file mode 100644
index 0000000..38b228f
--- /dev/null
+++ b/src/synchronous.js
@@ -0,0 +1,62 @@
+'use strict';
+
+var Promise = require('./core.js');
+
+module.exports = Promise;
+Promise.enableSynchronous = function () {
+ Promise.prototype.isPending = function() {
+ return this.getState() == 0;
+ };
+
+ Promise.prototype.isFulfilled = function() {
+ return this.getState() == 1;
+ };
+
+ Promise.prototype.isRejected = function() {
+ return this.getState() == 2;
+ };
+
+ Promise.prototype.getValue = function () {
+ if (this._state === 3) {
+ return this._value.getValue();
+ }
+
+ if (!this.isFulfilled()) {
+ throw new Error('Cannot get a value of an unfulfilled promise.');
+ }
+
+ return this._value;
+ };
+
+ Promise.prototype.getReason = function () {
+ if (this._state === 3) {
+ return this._value.getReason();
+ }
+
+ if (!this.isRejected()) {
+ throw new Error('Cannot get a rejection reason of a non-rejected promise.');
+ }
+
+ return this._value;
+ };
+
+ Promise.prototype.getState = function () {
+ if (this._state === 3) {
+ return this._value.getState();
+ }
+ if (this._state === -1 || this._state === -2) {
+ return 0;
+ }
+
+ return this._state;
+ };
+};
+
+Promise.disableSynchronous = function() {
+ Promise.prototype.isPending = undefined;
+ Promise.prototype.isFulfilled = undefined;
+ Promise.prototype.isRejected = undefined;
+ Promise.prototype.getValue = undefined;
+ Promise.prototype.getReason = undefined;
+ Promise.prototype.getState = undefined;
+};
diff --git a/test/extensions-tests.js b/test/extensions-tests.js
index 521c97b..c2eff67 100644
--- a/test/extensions-tests.js
+++ b/test/extensions-tests.js
@@ -55,6 +55,20 @@ describe('extensions', function () {
done()
})
})
+ it('resolves correctly when the wrapped function returns a promise anyway', function (done) {
+ function wrap(val, key, callback) {
+ return new Promise(function(resolve, reject) {
+ resolve({val: val, key: key})
+ })
+ }
+ var pwrap = Promise.denodeify(wrap)
+ pwrap(sentinel, 'foo')
+ .then(function (wrapper) {
+ assert(wrapper.val === sentinel)
+ assert(wrapper.key === 'foo')
+ done()
+ })
+ })
})
describe('Promise.nodeify(fn)', function () {
it('converts a promise returning function into a callback function', function (done) {
@@ -101,6 +115,20 @@ describe('extensions', function () {
done()
})
})
+ it('passes through the `this` argument', function (done) {
+ var ctx = {}
+ var add = Promise.nodeify(function (a, b) {
+ return Promise.resolve(a)
+ .then(function (a) {
+ return a + b
+ })
+ })
+ add.call(ctx, 1, 2, function (err, res) {
+ assert(res === 3)
+ assert(this === ctx)
+ done()
+ })
+ })
})
describe('Promise.all(...)', function () {
describe('an array', function () {
@@ -130,15 +158,19 @@ describe('extensions', function () {
})
describe('of promises', function () {
it('returns a promise for an array containing the fulfilled values', function (done) {
- var res = Promise.all([A, B, C])
+ var d = {}
+ var resolveD
+ var res = Promise.all([A, B, C, new Promise(function (resolve) { resolveD = resolve })])
assert(res instanceof Promise)
res.then(function (res) {
assert(Array.isArray(res))
assert(res[0] === a)
assert(res[1] === b)
assert(res[2] === c)
+ assert(res[3] === d)
})
.nodeify(done)
+ resolveD(d)
})
})
describe('of mixed values', function () {
@@ -167,6 +199,46 @@ describe('extensions', function () {
.nodeify(done)
})
})
+ describe('containing at least one eventually rejected promise', function () {
+ it('rejects the resulting promise', function (done) {
+ var rejectB
+ var rejected = new Promise(function (resolve, reject) { rejectB = reject })
+ var res = Promise.all([A, rejected, C])
+ assert(res instanceof Promise)
+ res.then(function (res) {
+ throw new Error('Should be rejected')
+ },
+ function (err) {
+ assert(err === rejection)
+ })
+ .nodeify(done)
+ rejectB(rejection)
+ })
+ })
+ describe('with a promise that resolves twice', function () {
+ it('still waits for all the other promises', function (done) {
+ var fakePromise = {then: function (onFulfilled) { onFulfilled(1); onFulfilled(2) }}
+ var eventuallyRejected = {then: function (_, onRejected) { this.onRejected = onRejected }}
+ var res = Promise.all([fakePromise, eventuallyRejected])
+ assert(res instanceof Promise)
+ res.then(function (res) {
+ throw new Error('Should be rejected')
+ },
+ function (err) {
+ assert(err === rejection)
+ })
+ .nodeify(done)
+ eventuallyRejected.onRejected(rejection);
+ })
+ })
+ describe('when given a foreign promise', function () {
+ it('should provide the correct value of `this`', function (done) {
+ var p = {then: function (onFulfilled) { onFulfilled({self: this}); }};
+ Promise.all([p]).then(function (results) {
+ assert(p === results[0].self);
+ }).nodeify(done);
+ });
+ });
})
})
@@ -174,8 +246,24 @@ describe('extensions', function () {
it.skip('behaves like then except for not returning anything', function () {
//todo
})
- it.skip('rethrows unhandled rejections', function () {
- //todo
+
+ it ('rethrows unhandled rejections', function (done) {
+ var originalTimeout = global.setTimeout
+
+ global.setTimeout = function(callback) {
+ try {
+ callback()
+ } catch (x) {
+ assert(x.message === 'It worked')
+ global.setTimeout = originalTimeout
+ return done()
+ }
+ done(new Error('Callback should have thrown an exception'))
+ }
+
+ Promise.resolve().done(function() {
+ throw new Error('It worked')
+ })
})
})
describe('promise.nodeify(callback)', function () {
@@ -220,5 +308,36 @@ describe('extensions', function () {
done()
})
})
+ it('accepts a `context` argument', function (done) {
+ var ctx = {}
+ function add(a, b, callback) {
+ return Promise.resolve(a)
+ .then(function (a) {
+ return a + b
+ })
+ .nodeify(callback, ctx)
+ }
+ add(1, 2, function (err, res) {
+ assert(res === 3)
+ assert(this === ctx)
+ done()
+ })
+ })
+ })
+
+ describe('inheritance', function () {
+ it('allows its prototype methods to act upon foreign constructors', function () {
+ function Awesome(fn) {
+ if (!(this instanceof Awesome)) return new Awesome(fn)
+ Promise.call(this, fn)
+ }
+ Awesome.prototype = Object.create(Promise.prototype)
+ Awesome.prototype.constructor = Awesome
+
+ var awesome = new Awesome(function () {})
+
+ assert(awesome.constructor === Awesome)
+ assert(awesome.then(function () {}).constructor === Awesome)
+ })
})
})
diff --git a/test/memory-leak.js b/test/memory-leak.js
new file mode 100644
index 0000000..679fcd1
--- /dev/null
+++ b/test/memory-leak.js
@@ -0,0 +1,41 @@
+'use strict';
+
+var assert = require('assert')
+var Promise = require('../')
+
+var i = 0
+var sampleA, sampleB
+
+function next() {
+ return new Promise(function (resolve) {
+ i++
+ /*
+ if (i % 100000 === 0) {
+ global.gc()
+ console.dir(process.memoryUsage())
+ }
+ */
+ if (i === 100000 && typeof global.gc === 'function') {
+ global.gc()
+ sampleA = process.memoryUsage()
+ }
+ if (i > 100000 * 10) {
+ if (typeof global.gc === 'function') {
+ global.gc()
+ sampleB = process.memoryUsage()
+ console.log('Memory usage at start:');
+ console.dir(sampleA)
+ console.log('Memory usage at end:');
+ console.dir(sampleB)
+ assert(sampleA.heapUsed * 1.2 > sampleB.heapUsed, 'heapUsed should not grow by more than 20%')
+ }
+ } else {
+ setImmediate(resolve)
+ }
+ }).then(next)
+}
+
+if (typeof global.gc !== 'function') {
+ console.warn('You must run with --expose-gc to test for memory leak.')
+}
+next().done()
diff --git a/test/nested-promises.js b/test/nested-promises.js
new file mode 100644
index 0000000..b8565d2
--- /dev/null
+++ b/test/nested-promises.js
@@ -0,0 +1,67 @@
+'use strict';
+
+var assert = require('assert');
+var Promise = require('../');
+
+describe('nested promises', function () {
+ it('does not result in any wierd behaviour - 1', function (done) {
+ var resolveA, resolveB, resolveC;
+ var A = new Promise(function (resolve, reject) {
+ resolveA = resolve;
+ });
+ var B = new Promise(function (resolve, reject) {
+ resolveB = resolve;
+ });
+ var C = new Promise(function (resolve, reject) {
+ resolveC = resolve;
+ });
+ resolveA(B);
+ resolveB(C);
+ resolveC('foo');
+ A.done(function (result) {
+ assert(result === 'foo');
+ done();
+ });
+ });
+ it('does not result in any wierd behaviour - 2', function (done) {
+ var resolveA, resolveB, resolveC, resolveD;
+ var A = new Promise(function (resolve, reject) {
+ resolveA = resolve;
+ });
+ var B = new Promise(function (resolve, reject) {
+ resolveB = resolve;
+ });
+ var C = new Promise(function (resolve, reject) {
+ resolveC = resolve;
+ });
+ var D = new Promise(function (resolve, reject) {
+ resolveD = resolve;
+ });
+ var Athen = A.then, Bthen = B.then, Cthen = C.then, Dthen = D.then;
+ resolveA(B);
+ resolveB(C);
+ resolveC(D);
+ resolveD('foo');
+ A.done(function (result) {
+ assert(result === 'foo');
+ done();
+ });
+ });
+ it('does not result in any wierd behaviour - 2', function (done) {
+ var promises = [];
+ var resolveFns = [];
+ for (var i = 0; i < 100; i++) {
+ promises.push(new Promise(function (resolve) {
+ resolveFns.push(resolve);
+ }));
+ }
+ for (var i = 0; i < 99; i++) {
+ resolveFns[i](promises[i + 1]);
+ }
+ resolveFns[99]('foo');
+ promises[0].done(function (result) {
+ assert(result === 'foo');
+ done();
+ });
+ });
+});
\ No newline at end of file
diff --git a/test/rejection-tracking.js b/test/rejection-tracking.js
new file mode 100644
index 0000000..a1ba809
--- /dev/null
+++ b/test/rejection-tracking.js
@@ -0,0 +1,98 @@
+'use strict';
+
+var assert = require('assert');
+var Promise = require('../');
+var tracking = require('../lib/rejection-tracking');
+
+describe('unhandled rejections', function () {
+ it('tracks rejected promises', function (done) {
+ this.timeout(300);
+ var calls = [], promise;
+ tracking.enable({
+ onUnhandled: function (id, err) {
+ calls.push(['unhandled', id, err]);
+ promise.done(null, function (err) {
+ assert.deepEqual(calls, [
+ ['unhandled', 0, err],
+ ['handled', 0, err]
+ ]);
+ done();
+ });
+ },
+ onHandled: function (id, err) {
+ calls.push(['handled', id, err]);
+ }
+ });
+ promise = Promise.reject(new TypeError('A fake type error'));
+ })
+ it('tracks rejected promises', function (done) {
+ this.timeout(2200);
+ var calls = [], promise;
+ tracking.enable({
+ allRejections: true,
+ onUnhandled: function (id, err) {
+ calls.push(['unhandled', id, err]);
+ promise.done(null, function (err) {
+ assert.deepEqual(calls, [
+ ['unhandled', 0, err],
+ ['handled', 0, err]
+ ]);
+ done();
+ });
+ },
+ onHandled: function (id, err) {
+ calls.push(['handled', id, err]);
+ }
+ });
+ promise = Promise.reject({});
+ })
+ it('tracks rejected promises', function (done) {
+ this.timeout(500);
+ var calls = [], promise;
+ tracking.enable({
+ onUnhandled: function (id, err) {
+ done(new Error('Expected exception to be handled in time'));
+ },
+ onHandled: function (id, err) {
+ }
+ });
+ promise = Promise.reject(new TypeError('A fake type error'));
+ var isDone = false;
+ setTimeout(function () {
+ promise.done(null, function (err) {
+ // ignore
+ isDone = true;
+ });
+ }, 50);
+ setTimeout(function () {
+ assert(isDone);
+ done();
+ }, 400);
+ })
+ it('tracks rejected promises', function (done) {
+ this.timeout(2300);
+ var warn = console.warn;
+ var warnings = [];
+ console.warn = function (str) {
+ warnings.push(str);
+ };
+ var expectedWarnings = [
+ 'Possible Unhandled Promise Rejection (id: 0):',
+ ' my',
+ ' multi',
+ ' line',
+ ' error',
+ 'Promise Rejection Handled (id: 0):',
+ ' This means you can ignore any previous messages of the form "Possible Unhandled Promise Rejection" with id 0.'
+ ];
+ tracking.enable({allRejections: true});
+ var promise = Promise.reject('my\nmulti\nline\nerror');
+ setTimeout(function () {
+ promise.done(null, function (err) {
+ console.warn = warn;
+ assert.deepEqual(warnings, expectedWarnings);
+ done();
+ });
+ }, 2100);
+ })
+})
\ No newline at end of file
diff --git a/test/resolver-tests.js b/test/resolver-tests.js
index 9f2905b..9e14aef 100644
--- a/test/resolver-tests.js
+++ b/test/resolver-tests.js
@@ -32,7 +32,7 @@ describe('resolver-tests', function () {
})
})
describe('if resolver is a function', function () {
- it('must be called with the promise\'s resolver arguments', function () {
+ it('must be called with the promise\'s resolver arguments', function (done) {
new Promise(function (resolve, reject) {
assert(typeof resolve === 'function')
assert(typeof reject === 'function')
@@ -158,7 +158,7 @@ describe('resolver-tests', function () {
})
})
describe('otherwise', function () {
- it('is rejected with e as the rejection reason', function () {
+ it('is rejected with e as the rejection reason', function (done) {
new Promise(function (resolve, reject) {
throw sentinel
})
diff --git a/test/synchronous-inspection-tests.js b/test/synchronous-inspection-tests.js
new file mode 100644
index 0000000..1579bc2
--- /dev/null
+++ b/test/synchronous-inspection-tests.js
@@ -0,0 +1,269 @@
+var assert = require('better-assert');
+var Promise = require('../');
+
+describe('synchronous-inspection-tests', function () {
+ it('cannot synchronously inspect before enabling synchronous inspection', function(done) {
+ var finished = null;
+ var fulfilledPromise = new Promise(function(resolve, reject) {
+ setTimeout(function() {
+ resolve();
+ }, 10);
+ });
+ var rejectedPromise = new Promise(function(resolve, reject) {
+ setTimeout(function() {
+ reject();
+ }, 10);
+ });
+
+ assert(fulfilledPromise.getValue == undefined);
+ assert(fulfilledPromise.getReason == undefined);
+ assert(fulfilledPromise.isFulfilled == undefined);
+ assert(fulfilledPromise.isPending == undefined);
+ assert(fulfilledPromise.isRejected == undefined);
+
+ assert(rejectedPromise.getValue == undefined);
+ assert(rejectedPromise.getReason == undefined);
+ assert(rejectedPromise.isFulfilled == undefined);
+ assert(rejectedPromise.isPending == undefined);
+ assert(rejectedPromise.isRejected == undefined);
+
+ setTimeout(function() {
+ assert(fulfilledPromise.getValue == undefined);
+ assert(fulfilledPromise.getReason == undefined);
+ assert(fulfilledPromise.isFulfilled == undefined);
+ assert(fulfilledPromise.isPending == undefined);
+ assert(fulfilledPromise.isRejected == undefined);
+
+ assert(rejectedPromise.getValue == undefined);
+ assert(rejectedPromise.getReason == undefined);
+ assert(rejectedPromise.isFulfilled == undefined);
+ assert(rejectedPromise.isPending == undefined);
+ assert(rejectedPromise.isRejected == undefined);
+
+ done()
+ }, 30);
+
+ });
+
+ it('can poll a promise to see if it is resolved', function (done) {
+ Promise.enableSynchronous();
+ var finished = null;
+ var fulfilledPromise = new Promise(function(resolve, reject) {
+ var interval = setInterval(function() {
+ if (finished !== null) {
+ clearTimeout(interval);
+
+ if (finished) {
+ resolve(true);
+ }
+ else {
+ reject(false);
+ }
+ }
+ }, 10);
+ });
+
+ assert(fulfilledPromise.getState() === 0);
+ assert(fulfilledPromise.isPending());
+ assert(!fulfilledPromise.isFulfilled());
+ assert(!fulfilledPromise.isRejected());
+
+ finished = true;
+
+ setTimeout(function () {
+ assert(fulfilledPromise.getState() === 1);
+ assert(fulfilledPromise.isFulfilled());
+ assert(!fulfilledPromise.isRejected());
+ assert(fulfilledPromise.getValue());
+ assert(!fulfilledPromise.isPending());
+
+ done();
+ }, 30);
+ });
+
+ it('can poll a promise to see if it is rejected', function (done) {
+ Promise.enableSynchronous();
+ var finished = null;
+ var fulfilledPromise = new Promise(function(resolve, reject) {
+ var interval = setInterval(function() {
+ if (finished !== null) {
+ clearTimeout(interval);
+
+ if (finished) {
+ resolve(true);
+ }
+ else {
+ reject(false);
+ }
+ }
+ }, 10);
+ });
+
+ assert(fulfilledPromise.getState() === 0);
+ assert(fulfilledPromise.isPending());
+ assert(!fulfilledPromise.isFulfilled());
+ assert(!fulfilledPromise.isRejected());
+
+ finished = false;
+
+ setTimeout(function () {
+ assert(fulfilledPromise.getState() === 2);
+ assert(!fulfilledPromise.isFulfilled());
+ assert(fulfilledPromise.isRejected());
+ assert(!fulfilledPromise.getReason());
+ assert(!fulfilledPromise.isPending());
+
+ done();
+ }, 30);
+ });
+
+ it('will throw an error if getting a value of an unfulfilled promise', function (done) {
+ Promise.enableSynchronous();
+ var finished = null;
+ var fulfilledPromise = new Promise(function(resolve, reject) {
+ var interval = setInterval(function() {
+ if (finished !== null) {
+ clearTimeout(interval);
+
+ if (finished) {
+ resolve(true);
+ }
+ else {
+ reject(false);
+ }
+ }
+ }, 10);
+ });
+
+ assert(fulfilledPromise.isPending());
+ assert(!fulfilledPromise.isFulfilled());
+ assert(!fulfilledPromise.isRejected());
+
+ try {
+ fulfilledPromise.getValue();
+
+ assert(false);
+ }
+ catch (e) {
+ assert(true);
+ }
+
+ finished = false;
+
+ setTimeout(function () {
+ try {
+ fulfilledPromise.getValue();
+
+ assert(false);
+ }
+ catch (e) {
+ assert(true);
+ }
+
+ done();
+ }, 30);
+ });
+
+ it('will throw an error if getting a reason of a non-rejected promise', function (done) {
+ Promise.enableSynchronous();
+ var finished = null;
+ var fulfilledPromise = new Promise(function(resolve, reject) {
+ var interval = setInterval(function() {
+ if (finished !== null) {
+ clearTimeout(interval);
+
+ if (finished) {
+ resolve(true);
+ }
+ else {
+ reject(false);
+ }
+ }
+ }, 10);
+ });
+
+ assert(fulfilledPromise.isPending());
+ assert(!fulfilledPromise.isFulfilled());
+ assert(!fulfilledPromise.isRejected());
+
+ try {
+ fulfilledPromise.getReason();
+
+ assert(false);
+ }
+ catch (e) {
+ assert(true);
+ }
+
+ finished = true;
+
+ setTimeout(function () {
+ try {
+ fulfilledPromise.getReason();
+
+ assert(false);
+ }
+ catch (e) {
+ assert(true);
+ }
+
+ done()
+ }, 30);
+ });
+
+ it('can disable synchronous inspection', function() {
+ Promise.enableSynchronous();
+ var testPromise = Promise.resolve('someValue');
+ assert(testPromise.getValue() == 'someValue');
+ Promise.disableSynchronous();
+ assert(testPromise.getValue == undefined);
+ });
+
+ it('can synchronously poll a resolving promise chain', function (done) {
+ Promise.enableSynchronous();
+ var fulfilledPromise = new Promise(function(resolve, reject) {
+ var interval = setTimeout(function() {
+ resolve(Promise.resolve(true));
+ }, 10);
+ });
+
+ assert(fulfilledPromise.getState() === 0);
+ assert(fulfilledPromise.isPending());
+ assert(!fulfilledPromise.isFulfilled());
+ assert(!fulfilledPromise.isRejected());
+
+ setTimeout(function() {
+ assert(fulfilledPromise.getState() === 1);
+ assert(fulfilledPromise.isFulfilled());
+ assert(!fulfilledPromise.isRejected());
+ assert(fulfilledPromise.getValue());
+ assert(!fulfilledPromise.isPending());
+
+ done();
+ }, 30);
+ });
+
+ it('can synchronously poll a rejecting promise chain', function(done) {
+ Promise.enableSynchronous();
+ var rejectedPromise = new Promise(function(resolve, reject) {
+ setTimeout(function() {
+ resolve(Promise.reject(false));
+ }, 10);
+ });
+
+ assert(rejectedPromise.getState() === 0);
+ assert(rejectedPromise.isPending());
+ assert(!rejectedPromise.isFulfilled());
+ assert(!rejectedPromise.isRejected());
+
+ setTimeout(function() {
+ assert(rejectedPromise.getState() === 2);
+ assert(!rejectedPromise.isFulfilled());
+ assert(rejectedPromise.isRejected());
+ assert(!rejectedPromise.getReason());
+ assert(!rejectedPromise.isPending());
+
+ done();
+ }, 30);
+ });
+});
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-promise.git
More information about the Pkg-javascript-commits
mailing list