commit be76ba655ce5e1fafb1294e07abcd081d8d2351e Author: zhouganqing Date: Fri Feb 17 11:33:40 2023 +0800 Import Upstream version 3.7.2+dfsg1 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9d02467 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[**.js] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c0cfdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +docs/Gemfile.lock +gh-pages +node_modules +gh-pages +test/browser/bundle.js +test/browser/worker_bundle.js +js/* +zalgo.js +coverage/* +.vscode +.idea diff --git a/.istanbul.yml b/.istanbul.yml new file mode 100644 index 0000000..63e6eee --- /dev/null +++ b/.istanbul.yml @@ -0,0 +1,49 @@ +verbose: false +instrumentation: + root: . + default-excludes: true + excludes: [] + embed-source: false + variable: __coverage__ + compact: true + preserve-comments: false + complete-copy: false + save-baseline: false + baseline-file: ./coverage/coverage-baseline.json + include-all-sources: false +reporting: + print: summary + reports: + - lcov + dir: ./coverage + watermarks: + statements: [50, 80] + lines: [50, 80] + functions: [50, 80] + branches: [50, 80] + report-config: + clover: {file: clover.xml} + cobertura: {file: cobertura-coverage.xml} + json: {file: coverage-final.json} + json-summary: {file: coverage-summary.json} + lcovonly: {file: lcov.info} + teamcity: {file: null} + text: {file: null, maxCols: 0} + text-summary: {file: null} +hooks: + hook-run-in-context: false + post-require-hook: null + handle-sigint: false +check: + global: + statements: 0 + lines: 0 + branches: 0 + functions: 0 + excludes: [] + each: + statements: 0 + lines: 0 + branches: 0 + functions: 0 + excludes: [] diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..fbea002 --- /dev/null +++ b/.jshintignore @@ -0,0 +1 @@ +src/constants.js diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..bc14497 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,161 @@ +{ + "bitwise": false, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "es3": true, + "forin": true, + "immed": true, + "latedef": false, + "newcap": true, + "noarg": true, + "noempty": true, + "nonew": true, + "plusplus": false, + "quotmark": "double", + "undef": true, + "unused": true, + "strict": false, + "maxparams": 6, + "maxlen": 80, + "asi": false, + "boss": true, + "eqnull": true, + "evil": true, + "expr": false, + "funcscope": false, + "globalstrict": false, + "lastsemic": false, + "laxcomma": false, + "laxbreak": false, + "loopfunc": true, + "multistr": true, + "proto": false, + "scripturl": true, + "shadow": true, + "sub": true, + "supernew": false, + "validthis": true, + "browser": true, + "jquery": true, + "devel": true, + "-W014": true, + "-W116": true, + "-W106": true, + "-W064": true, + "-W097": true, + "globals": { + "Symbol": false, + "Map": false, + "JSON": false, + "Error": true, + "args": true, + "chrome": true, + "INLINE_SLICE": false, + "INLINE_SLICE_LEFT_PADDED": false, + "BIT_FIELD_CHECK": false, + "BIT_FIELD_READ": false, + "USE": false, + "global": true, + "setImmediate": true, + "Promise": true, + "WebKitMutationObserver": true, + "TypeError": true, + "RangeError": true, + "__DEBUG__": false, + "__BROWSER__": false, + "process": true, + "self": true, + "console": false, + "require": false, + "module": false, + "define": false, + "LATE_QUEUE_CAPACITY": false, + "NORMAL_QUEUE_CAPACITY": false, + "ERROR_HANDLED_KEY": false, + "OPERATIONAL_ERROR_KEY": false, + "DEFAULT_STATE": false, + "STACK_ATTACHED": false, + "ERROR_HANDLED": false, + "GENERATED_CLASS_COUNT": false, + "USE_BOUND": false, + "DONT_USE_BOUND": false, + "PROPAGATE_CANCEL": false, + "PROPAGATE_BIND": false, + "PROPAGATE_ALL": false, + "CALLBACK_FULFILL_OFFSET": false, + "CALLBACK_REJECT_OFFSET": false, + "CALLBACK_PROMISE_OFFSET": false, + "CALLBACK_RECEIVER_OFFSET": false, + "CALLBACK_SIZE": false, + "ASYNC_GUARANTEE_SHIFT": false, + "NO_STATE": false, + "NO_ASYNC_GUARANTEE": false, + "RETURNED_NON_UNDEFINED": false, + "IS_ASYNC_GUARANTEED": false, + "IS_FOLLOWING": false, + "IS_FULFILLED": false, + "IS_REJECTED": false, + "WILL_BE_CANCELLED": false, + "IS_FINAL": false, + "IS_BOUND": false, + "IS_REJECTION_UNHANDLED": false, + "IS_REJECTION_IGNORED": false, + "IS_UNHANDLED_REJECTION_NOTIFIED": false, + "IS_DISPOSABLE": false, + "IS_CANCELLED": false, + "IS_CANCELLED_OR_WILL_BE_CANCELLED": false, + "LENGTH_MASK": false, + "LENGTH_CLEAR_MASK": false, + "MAX_LENGTH": false, + "IS_REJECTED_OR_CANCELLED": false, + "IS_REJECTED_OR_FULFILLED": false, + "IS_REJECTED_OR_FULFILLED_OR_CANCELLED": false, + "IS_PENDING_AND_WAITING_NEG": false, + "IS_FATE_SEALED": false, + "AFTER_PROMISIFIED_SUFFIX": false, + "UNHANDLED_REJECTION_EVENT": false, + "REJECTION_HANDLED_EVENT": false, + "RESOLVE_UNDEFINED": false, + "RESOLVE_ARRAY": false, + "RESOLVE_OBJECT": false, + "RESOLVE_FOREVER_PENDING": false, + "RESOLVE_CALL_METHOD": false, + "RESOLVE_MAP": false, + "QUEUE_MAX_CAPACITY": false, + "QUEUE_MIN_CAPACITY": false, + "FROM_PREVIOUS_EVENT": false, + "NO_STACK_TRACE": false, + "ADDITIONAL_STACK_TRACE": false, + "UNHANDLED_REJECTION_HEADER": false, + "FINALLY_TYPE": false, + "TAP_TYPE": false, + "THROW": false, + "RETURN": false, + "MAX_PARAM_COUNT": false, + "PARAM_COUNTS_TO_TRY": false, + "BLUEBIRD_ERRORS": false, + "OBJECT_PROMISIFY_DEPRECATED": false, + "SPAWN_DEPRECATED": false, + "LATE_CANCELLATION_OBSERVER": false, + "TIMEOUT_ERROR": false, + "COLLECTION_ERROR": false, + "OBJECT_ERROR": false, + "FUNCTION_ERROR": false, + "CONSTRUCT_ERROR_INVOCATION": false, + "NOT_GENERATOR_ERROR": false, + "LONG_STACK_TRACES_ERROR": false, + "INSPECTION_VALUE_ERROR": false, + "INSPECTION_REASON_ERROR": false, + "PROMISIFY_TYPE_ERROR": false, + "CIRCULAR_RESOLUTION_ERROR": false, + "PROPS_TYPE_ERROR": false, + "POSITIVE_INTEGER_ERROR": false, + "YIELDED_NON_PROMISE_ERROR": false, + "FROM_COROUTINE_CREATED_AT": false, + "UNBOUND_RESOLVER_INVOCATION": false, + "PROMISIFICATION_NORMAL_METHODS_ERROR": false, + "SUFFIX_NOT_IDENTIFIER": false, + "NO_ASYNC_SCHEDULER": false + } +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7adc795 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +language: node_js +sudo: false +matrix: + include: + - node_js: "0.10" + - node_js: "0.12" + - node_js: "4" + - node_js: "5" + - node_js: "6" + - node_js: "7" + - node_js: "8" + - node_js: "10" + fast_finish: true +git: + depth: 5 +env: + - "NODE_FLAGS='--expose-gc' SCRIPT_FLAGS=''" +before_script: +- git submodule update --init --recursive +script: "node $NODE_FLAGS tools/test.js $SCRIPT_FLAGS" +branches: + only: + - master +cache: + directories: + - "$HOME/.npm" diff --git a/API.md b/API.md new file mode 100644 index 0000000..257d52c --- /dev/null +++ b/API.md @@ -0,0 +1 @@ +[http://bluebirdjs.com/docs/api-reference.html](http://bluebirdjs.com/docs/api-reference.html) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..136bb7e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ + +# Questions and issues + +Please see [The Support Page](http://bluebirdjs.com/docs/support.html) +The [github issue tracker](https://github.com/petkaantonov/bluebird/issues) is **_only_** for bug reports and feature requests. + +# Contributing to the library + +Contributions are welcome and appreciated. See the [Contribution Page](http://bluebirdjs.com/docs/contribute.html) on bluebirdjs.com diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b24e635 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2018 Petka Antonov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c1dd66 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ + + Promises/A+ logo + + + +[![Build Status](https://travis-ci.org/petkaantonov/bluebird.svg?branch=master)](https://travis-ci.org/petkaantonov/bluebird) +[![coverage-98%](https://img.shields.io/badge/coverage-98%25-brightgreen.svg?style=flat)](http://petkaantonov.github.io/bluebird/coverage/debug/index.html) + +**Got a question?** Join us on [stackoverflow](http://stackoverflow.com/questions/tagged/bluebird), the [mailing list](https://groups.google.com/forum/#!forum/bluebird-js) or chat on [IRC](https://webchat.freenode.net/?channels=#promises) + +# Introduction + +Bluebird is a fully featured promise library with focus on innovative features and performance + +See the [**bluebird website**](http://bluebirdjs.com/docs/getting-started.html) for further documentation, references and instructions. See the [**API reference**](http://bluebirdjs.com/docs/api-reference.html) here. + +For bluebird 2.x documentation and files, see the [2.x tree](https://github.com/petkaantonov/bluebird/tree/2.x). + +### Note + +Promises in Node.js 10 are significantly faster than before. Bluebird still includes a lot of features like cancellation, iteration methods and warnings that native promises don't. If you are using Bluebird for performance rather than for those - please consider giving native promises a shot and running the benchmarks yourself. + +# Questions and issues + +The [github issue tracker](https://github.com/petkaantonov/bluebird/issues) is **_only_** for bug reports and feature requests. Anything else, such as questions for help in using the library, should be posted in [StackOverflow](http://stackoverflow.com/questions/tagged/bluebird) under tags `promise` and `bluebird`. + + + +## Thanks + +Thanks to BrowserStack for providing us with a free account which lets us support old browsers like IE8. + +# License + +The MIT License (MIT) + +Copyright (c) 2013-2019 Petka Antonov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/bench b/bench new file mode 100755 index 0000000..cfe344d --- /dev/null +++ b/bench @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +./build --release --no-debug +benchmark=$1 +nodepath=${2:-node} +shift 2; +cwd=${PWD} + +export NODE_ENV=production + +trap 'cd "$cwd"' EXIT + +if [ "$benchmark" = "doxbee" ]; then + cd "$cwd/benchmark" + npm install + echo "Doxbee sequential" + $nodepath performance.js --n 10000 --t 1 ./doxbee-sequential/*.js --harmony "$@" + exit 0 +elif [ "$benchmark" = "doxbee-errors" ]; then + cd "$cwd/benchmark" + npm install + echo "Doxbee sequential with 10% errors" + $nodepath performance.js --n 10000 --t 1 --e 0.1 ./doxbee-sequential-errors/*.js --harmony "$@" + exit 0 +elif [ "$benchmark" = "parallel" ]; then + cd "$cwd/benchmark" + npm install + echo "Madeup parallel" + $nodepath performance.js --n 10000 --t 1 --p 25 ./madeup-parallel/*.js --harmony "$@" + exit 0 +elif [ "$benchmark" = "analysis" ]; then + cd "$cwd/benchmark" + npm install + echo "analysis" + $nodepath performance.js --n 10000 --t 1 ./analysis/*.js --harmony "$@" + exit 0 +else + echo "Invalid benchmark name $benchmark" + exit -1 +fi diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..b2eae30 --- /dev/null +++ b/bower.json @@ -0,0 +1,34 @@ +{ + "name": "bluebird", + "version": "3.7.2", + "homepage": "https://github.com/petkaantonov/bluebird", + "authors": [ + "Petka Antonov " + ], + "description": "Bluebird is a full featured promise library with unmatched performance.", + "main": "js/browser/bluebird.js", + "license": "MIT", + "ignore": [ + "**/.*", + "benchmark", + "bower_components", + "./browser", + "node_modules", + "test" + ], + "keywords": [ + "promise", + "performance", + "promises", + "promises-a", + "promises-aplus", + "async", + "await", + "deferred", + "deferreds", + "future", + "flow control", + "dsl", + "fluent interface" + ] +} diff --git a/build b/build new file mode 100755 index 0000000..3d26cbd --- /dev/null +++ b/build @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +node tools/build.js "$@" diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..73b2eb6 --- /dev/null +++ b/changelog.md @@ -0,0 +1 @@ +[http://bluebirdjs.com/docs/changelog.html](http://bluebirdjs.com/docs/changelog.html) diff --git a/deprecated_apis.md b/deprecated_apis.md new file mode 100644 index 0000000..30506e5 --- /dev/null +++ b/deprecated_apis.md @@ -0,0 +1 @@ +[http://bluebirdjs.com/docs/deprecated-apis.html](http://bluebirdjs.com/docs/deprecated-apis.html) diff --git a/issue_template.md b/issue_template.md new file mode 100644 index 0000000..35e5c6c --- /dev/null +++ b/issue_template.md @@ -0,0 +1,12 @@ +(This issue tracker is only for bug reports or feature requests, if this is neither, please choose appropriate channel from http://bluebirdjs.com/docs/support.html) + +Please answer the questions the best you can: + +1) What version of bluebird is the issue happening on? + +2) What platform and version? (For example Node.js 0.12 or Google Chrome 32) + +3) Did this issue happen with earlier version of bluebird? + +(Write description of your issue here, stack traces from errors and code that reproduces the issue are helpful) + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..35328ca --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3209 @@ +{ + "name": "bluebird", + "version": "3.5.4", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "Base64": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", + "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg=", + "dev": true + }, + "JSONStream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", + "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", + "dev": true, + "requires": { + "jsonparse": "0.0.5", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "dev": true + }, + "acorn-node": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.7.0.tgz", + "integrity": "sha512-XhahLSsCB6X6CJbe+uNu3Mn9sJBNFxtBN9NLgAOQovfS6Kh0lDUtmlclhjn9CvEK7A7YyRU13PXlNcpSiLI9Yw==", + "dev": true, + "requires": { + "acorn": "^6.1.1", + "acorn-dynamic-import": "^4.0.0", + "acorn-walk": "^6.1.1", + "xtend": "^4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.3.0.tgz", + "integrity": "sha1-A5OaYiWCqBLMICMgoLmlbJuBWEk=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true, + "optional": true + }, + "astw": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", + "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", + "dev": true, + "requires": { + "acorn": "^4.0.3" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "aws-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/aws-sign/-/aws-sign-0.3.0.tgz", + "integrity": "sha1-PYHKabR0seFlGHKLUcJP8Lvtxuk=", + "dev": true + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true, + "optional": true + }, + "baconjs": { + "version": "0.7.95", + "resolved": "https://registry.npmjs.org/baconjs/-/baconjs-0.7.95.tgz", + "integrity": "sha512-3qp0GuAfEUlJybSVPQ2oai8VYO0aSTJf4wP/jYZpgaffEXi31VBcqSlVmD8ahmXXGzgdO+yFk9onDnt2ZJJXxA==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", + "dev": true + }, + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "dev": true, + "requires": { + "hoek": "0.9.x" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-pack": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-3.2.0.tgz", + "integrity": "sha1-+qHLxBSHsazEdH43PhFIrf/Q4tk=", + "dev": true, + "requires": { + "JSONStream": "~0.8.4", + "combine-source-map": "~0.3.0", + "concat-stream": "~1.4.1", + "defined": "~0.0.0", + "through2": "~0.5.1", + "umd": "^2.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + } + } + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browserify": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-8.1.3.tgz", + "integrity": "sha1-8zpUmjpsNoIZsHX9z+bfGV4Empo=", + "dev": true, + "requires": { + "JSONStream": "~0.8.3", + "assert": "~1.3.0", + "browser-pack": "^3.2.0", + "browser-resolve": "^1.3.0", + "browserify-zlib": "~0.1.2", + "buffer": "^3.0.0", + "builtins": "~0.0.3", + "commondir": "0.0.1", + "concat-stream": "~1.4.1", + "console-browserify": "^1.1.0", + "constants-browserify": "~0.0.1", + "crypto-browserify": "^3.0.0", + "deep-equal": "~0.2.1", + "defined": "~0.0.0", + "deps-sort": "^1.3.5", + "domain-browser": "~1.1.0", + "duplexer2": "~0.0.2", + "events": "~1.0.0", + "glob": "^4.0.5", + "http-browserify": "^1.4.0", + "https-browserify": "~0.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^6.2.0", + "isarray": "0.0.1", + "labeled-stream-splicer": "^1.0.0", + "module-deps": "^3.6.3", + "os-browserify": "~0.1.1", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "^0.10.0", + "punycode": "~1.2.3", + "querystring-es3": "~0.2.0", + "readable-stream": "^1.0.33-1", + "resolve": "~0.7.1", + "shallow-copy": "0.0.1", + "shasum": "^1.0.0", + "shell-quote": "~0.0.1", + "stream-browserify": "^1.0.0", + "string_decoder": "~0.10.0", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^1.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "~0.0.0", + "umd": "~2.1.0", + "url": "~0.10.1", + "util": "~0.10.1", + "vm-browserify": "~0.0.1", + "xtend": "^3.0.0" + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "~0.2.0" + } + }, + "buffer": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz", + "integrity": "sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=", + "dev": true, + "requires": { + "base64-js": "0.0.8", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtins": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz", + "integrity": "sha1-NVIZzWzxjb58Acx/0tznZc/cVJo=", + "dev": true + }, + "buster-core": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", + "integrity": "sha1-J79rrWdCROpyDzEdkAoMoct4YFA=", + "dev": true + }, + "buster-format": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", + "integrity": "sha1-K4bDIuz14bCubm55Bev884fSq5U=", + "dev": true, + "requires": { + "buster-core": "=0.6.4" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "requires": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "dev": true, + "requires": { + "exit": "0.1.2", + "glob": "^7.1.1" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "dev": true, + "requires": { + "colors": "1.0.3" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "combine-source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.3.0.tgz", + "integrity": "sha1-2edPWT2c1DgHMSy12EbUUe+qnrc=", + "dev": true, + "requires": { + "convert-source-map": "~0.3.0", + "inline-source-map": "~0.3.0", + "source-map": "~0.1.31" + } + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true, + "optional": true + }, + "commondir": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-0.0.1.tgz", + "integrity": "sha1-ifAP3NUbUZxXhzP+xWPmptp/W+I=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "constants-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz", + "integrity": "sha1-kld9tSe6bEzwpFaNhLwDH0QeIfI=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", + "dev": true + }, + "cookie-jar": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/cookie-jar/-/cookie-jar-0.3.0.tgz", + "integrity": "sha1-vJon1OK5fhhs1XyeIGPLmfpozMw=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-0.2.9.tgz", + "integrity": "sha1-vWf5bAfvtjA7f+lMHpefiEeOCjk=", + "dev": true, + "requires": { + "lru-cache": "^2.5.0" + } + }, + "cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "dev": true, + "requires": { + "boom": "0.4.x" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true, + "optional": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-equal": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", + "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", + "dev": true + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "deps-sort": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-1.3.9.tgz", + "integrity": "sha1-Kd//U+F7Nq7K51MK27v2IsLtGnE=", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "shasum": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + } + } + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detective": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "dev": true, + "requires": { + "acorn": "^5.2.1", + "defined": "^1.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + } + } + }, + "diff": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz", + "integrity": "sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + }, + "dependencies": { + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + } + } + }, + "domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "escodegen": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.1.tgz", + "integrity": "sha1-MOz89mypjcZ80v0WKr626vqM5vw=", + "dev": true, + "requires": { + "esprima": "^1.2.2", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.5.0", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha1-CZNQL+r2aBODJXVvMPmlH+7sEek=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "esprima": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.5.0.tgz", + "integrity": "sha1-84ekb9NEwbGjm6+MIL+0O20AWMw=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "events": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/events/-/events-1.0.2.tgz", + "integrity": "sha1-dYSdz+k9EPsFfDAFWv29UdBqjiQ=", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "fast-levenshtein": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz", + "integrity": "sha1-AXjc3uAjuSkFGTrwlZ6KdjnP3Lk=", + "dev": true + }, + "fileset": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.2.1.tgz", + "integrity": "sha1-WI74lzxmI7KnbfRlEFaWuWqsgGc=", + "dev": true, + "requires": { + "glob": "5.x", + "minimatch": "2.x" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true + }, + "form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", + "dev": true, + "optional": true, + "requires": { + "async": "~0.9.0", + "combined-stream": "~0.0.4", + "mime": "~1.2.11" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true, + "optional": true + } + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^2.0.1", + "once": "^1.3.0" + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "dev": true + }, + "growl": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz", + "integrity": "sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg=", + "dev": true + }, + "grunt-saucelabs": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/grunt-saucelabs/-/grunt-saucelabs-8.4.1.tgz", + "integrity": "sha1-16F5wt24LRgIoZ2kWEvl1aoeKfU=", + "dev": true, + "requires": { + "colors": "~0.6.2", + "lodash": "~2.4.1", + "q": "~1.0.0", + "request": "~2.35.0", + "sauce-tunnel": "~2.1.1", + "saucelabs": "~0.1.1" + }, + "dependencies": { + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + } + } + }, + "handlebars": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.15.tgz", + "integrity": "sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + } + } + } + }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hawk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.0.0.tgz", + "integrity": "sha1-uQuxaYByhUEdp//LjdJZhQLTtS0=", + "dev": true, + "optional": true, + "requires": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.9.x", + "sntp": "0.2.x" + } + }, + "highland": { + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/highland/-/highland-2.13.4.tgz", + "integrity": "sha512-r+YlbnBhCTcrcVzBpzPcrvB0llVjeDWKuXSZVuNBe5WgQJtN2xGUUMZC9WzHCntNIx0rskVernxLoFJUCkmb/Q==", + "dev": true, + "requires": { + "util-deprecate": "^1.0.2" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", + "dev": true + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "http-browserify": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", + "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=", + "dev": true, + "requires": { + "Base64": "~0.2.0", + "inherits": "~2.0.1" + } + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "optional": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "^0.1.5", + "ctype": "0.5.3" + } + }, + "https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inline-source-map": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.3.1.tgz", + "integrity": "sha1-pSi1FOaJ/OkNswiehw2S9Sestes=", + "dev": true, + "requires": { + "source-map": "~0.3.0" + }, + "dependencies": { + "source-map": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", + "integrity": "sha1-hYb7mloAXltQHiHNGLbyG0V60fk=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "insert-module-globals": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-6.6.3.tgz", + "integrity": "sha1-IGOOKaMPntHKLjqCX7wsulJG3fw=", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "combine-source-map": "~0.6.1", + "concat-stream": "~1.4.1", + "is-buffer": "^1.1.0", + "lexical-scope": "^1.2.0", + "process": "~0.11.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "combine-source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.6.1.tgz", + "integrity": "sha1-m0oJwxYDPXaODxHgKfonMOB5rZY=", + "dev": true, + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.5.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.4.2" + } + }, + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + }, + "inline-source-map": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.5.0.tgz", + "integrity": "sha1-Skxd2OT7Xps82mDIIt+tyu5m4K8=", + "dev": true, + "requires": { + "source-map": "~0.4.0" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true, + "optional": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "istanbul": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.3.22.tgz", + "integrity": "sha1-PhZNhQIf4ZyYXR8OfvDD4i0BLrY=", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.7.x", + "esprima": "2.5.x", + "fileset": "0.2.x", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + } + } + }, + "jshint": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz", + "integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==", + "dev": true, + "requires": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.11", + "minimatch": "~3.0.2", + "shelljs": "0.3.x", + "strip-json-comments": "1.0.x" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "jshint-stylish": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/jshint-stylish/-/jshint-stylish-0.2.0.tgz", + "integrity": "sha1-newAJQrISXlgvk7tb1Bn+x1twH0=", + "dev": true, + "requires": { + "chalk": "~0.4.0", + "text-table": "~0.2.0" + } + }, + "json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true + }, + "kefir": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/kefir/-/kefir-2.8.2.tgz", + "integrity": "sha1-zdnl6C+ymM+FOGWTYiY1SvpYnTE=", + "dev": true + }, + "labeled-stream-splicer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-1.0.2.tgz", + "integrity": "sha1-RhUzFTd4SYHo/SZOHzpDTE4N3WU=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "stream-splicer": "^1.1.0" + } + }, + "levn": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz", + "integrity": "sha1-uo0znQykphDjo/FFucr0iAcVUFQ=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.0", + "type-check": "~0.3.1" + } + }, + "lexical-scope": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", + "dev": true, + "requires": { + "astw": "^2.0.0" + } + }, + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "lodash._arraypool": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._arraypool/-/lodash._arraypool-2.4.1.tgz", + "integrity": "sha1-6I7suS4ruEyQZWEv2VigcZzUf5Q=", + "dev": true + }, + "lodash._basebind": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.4.1.tgz", + "integrity": "sha1-6UC5690nwyfgqNqxtVkWxTQelXU=", + "dev": true, + "requires": { + "lodash._basecreate": "~2.4.1", + "lodash._setbinddata": "~2.4.1", + "lodash._slice": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash._basecreate": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.4.1.tgz", + "integrity": "sha1-+Ob1tXip405UEXm1a47uv0oofgg=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash.isobject": "~2.4.1", + "lodash.noop": "~2.4.1" + } + }, + "lodash._basecreatecallback": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.4.1.tgz", + "integrity": "sha1-fQsmdknLKeehOdAQO3wR+uhOSFE=", + "dev": true, + "requires": { + "lodash._setbinddata": "~2.4.1", + "lodash.bind": "~2.4.1", + "lodash.identity": "~2.4.1", + "lodash.support": "~2.4.1" + } + }, + "lodash._basecreatewrapper": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.4.1.tgz", + "integrity": "sha1-TTHy595+E0+/KAN2K4FQsyUZZm8=", + "dev": true, + "requires": { + "lodash._basecreate": "~2.4.1", + "lodash._setbinddata": "~2.4.1", + "lodash._slice": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash._basemerge": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._basemerge/-/lodash._basemerge-2.4.1.tgz", + "integrity": "sha1-B2Zrr7/AsZqp2vgm2Su+JZuy6RM=", + "dev": true, + "requires": { + "lodash.foreach": "~2.4.1", + "lodash.forown": "~2.4.1", + "lodash.isarray": "~2.4.1", + "lodash.isplainobject": "~2.4.1" + } + }, + "lodash._createwrapper": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.4.1.tgz", + "integrity": "sha1-UdaVeXPaTtVW43KQ2MGhjFPeFgc=", + "dev": true, + "requires": { + "lodash._basebind": "~2.4.1", + "lodash._basecreatewrapper": "~2.4.1", + "lodash._slice": "~2.4.1", + "lodash.isfunction": "~2.4.1" + } + }, + "lodash._getarray": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._getarray/-/lodash._getarray-2.4.1.tgz", + "integrity": "sha1-+vH3+BD6mFolHCGHQESBCUg55e4=", + "dev": true, + "requires": { + "lodash._arraypool": "~2.4.1" + } + }, + "lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", + "dev": true + }, + "lodash._maxpoolsize": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._maxpoolsize/-/lodash._maxpoolsize-2.4.1.tgz", + "integrity": "sha1-nUgvRjuOZq++WcLBTtsRcGAXIzQ=", + "dev": true + }, + "lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", + "dev": true + }, + "lodash._releasearray": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._releasearray/-/lodash._releasearray-2.4.1.tgz", + "integrity": "sha1-phOWMNdtFTawfdyAliiJsIL2pkE=", + "dev": true, + "requires": { + "lodash._arraypool": "~2.4.1", + "lodash._maxpoolsize": "~2.4.1" + } + }, + "lodash._setbinddata": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.4.1.tgz", + "integrity": "sha1-98IAzRuS7yNrOZ7s9zxkjReqlNI=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash.noop": "~2.4.1" + } + }, + "lodash._shimisplainobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimisplainobject/-/lodash._shimisplainobject-2.4.1.tgz", + "integrity": "sha1-AeyTsu5j5Z8aqDiZrG+gkFrHWW8=", + "dev": true, + "requires": { + "lodash.forin": "~2.4.1", + "lodash.isfunction": "~2.4.1" + } + }, + "lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash._slice": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.4.1.tgz", + "integrity": "sha1-dFz0GlNZexj2iImFREBe+isG2Q8=", + "dev": true + }, + "lodash.bind": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.4.1.tgz", + "integrity": "sha1-XRn6AFyMTSNvr0dCx7eh/Kvikmc=", + "dev": true, + "requires": { + "lodash._createwrapper": "~2.4.1", + "lodash._slice": "~2.4.1" + } + }, + "lodash.foreach": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-2.4.1.tgz", + "integrity": "sha1-/j/Do0yGyUyrb5UiVgKCdB4BYwk=", + "dev": true, + "requires": { + "lodash._basecreatecallback": "~2.4.1", + "lodash.forown": "~2.4.1" + } + }, + "lodash.forin": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.forin/-/lodash.forin-2.4.1.tgz", + "integrity": "sha1-gInq7X0lsIZyt8Zv0HrFXQYjIOs=", + "dev": true, + "requires": { + "lodash._basecreatecallback": "~2.4.1", + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.forown": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-2.4.1.tgz", + "integrity": "sha1-eLQer+FAX6lmRZ6kGT/VAtCEUks=", + "dev": true, + "requires": { + "lodash._basecreatecallback": "~2.4.1", + "lodash._objecttypes": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "lodash.identity": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.4.1.tgz", + "integrity": "sha1-ZpTP+mX++TH3wxzobHRZfPVg9PE=", + "dev": true + }, + "lodash.isarray": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-2.4.1.tgz", + "integrity": "sha1-tSoybB9i9tfac6MdVAHfbvRPD6E=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1" + } + }, + "lodash.isfunction": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.4.1.tgz", + "integrity": "sha1-LP1XXHPkmKtX4xm3f6Aq3vE6lNE=", + "dev": true + }, + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.isplainobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-2.4.1.tgz", + "integrity": "sha1-rHOF4uqawDIfMNw7gDKm0iMagBE=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimisplainobject": "~2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, + "lodash.merge": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-2.4.1.tgz", + "integrity": "sha1-TJ/oQ/bXnhsFIhgbzeoEMIhn0h4=", + "dev": true, + "requires": { + "lodash._basecreatecallback": "~2.4.1", + "lodash._basemerge": "~2.4.1", + "lodash._getarray": "~2.4.1", + "lodash._releasearray": "~2.4.1", + "lodash._slice": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash.noop": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.4.1.tgz", + "integrity": "sha1-T7VPgWZS5a4Q6PcvcXo4jHMmU4o=", + "dev": true + }, + "lodash.support": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.4.1.tgz", + "integrity": "sha1-Mg4LZwMWc8KNeiu12eAzGkUkBRU=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1" + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "mocha": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.1.0.tgz", + "integrity": "sha1-d3Uv5ZL7kJJ1aCevRs0+rhuDZxw=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.0.0", + "diff": "1.0.8", + "escape-string-regexp": "1.0.2", + "glob": "3.2.3", + "growl": "1.8.1", + "jade": "0.26.3", + "mkdirp": "0.5.0" + }, + "dependencies": { + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "debug": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.0.0.tgz", + "integrity": "sha1-ib2d9nMrUSVrxnBTQrugLtEhMe8=", + "dev": true, + "requires": { + "ms": "0.6.2" + } + }, + "glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "dev": true, + "requires": { + "graceful-fs": "~2.0.0", + "inherits": "2", + "minimatch": "~0.2.11" + } + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=", + "dev": true + } + } + }, + "module-deps": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-3.9.1.tgz", + "integrity": "sha1-6nXK+RmQkNJbDVUStaysuW5/h/M=", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "browser-resolve": "^1.7.0", + "concat-stream": "~1.4.5", + "defined": "^1.0.0", + "detective": "^4.0.0", + "duplexer2": "0.0.2", + "inherits": "^2.0.1", + "parents": "^1.0.0", + "readable-stream": "^1.1.13", + "resolve": "^1.1.3", + "stream-combiner2": "~1.0.0", + "subarg": "^1.0.0", + "through2": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "resolve": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "open": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", + "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=", + "dev": true + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "optionator": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz", + "integrity": "sha1-t1qJlaLUF98ltuTjhi9QqohlE2g=", + "dev": true, + "requires": { + "deep-is": "~0.1.2", + "fast-levenshtein": "~1.0.0", + "levn": "~0.2.5", + "prelude-ls": "~1.1.1", + "type-check": "~0.3.1", + "wordwrap": "~0.0.2" + } + }, + "os-browserify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", + "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", + "dev": true + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/process/-/process-0.10.1.tgz", + "integrity": "sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU=", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true, + "optional": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "punycode": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.2.4.tgz", + "integrity": "sha1-VACKyXKux0F13vnLpt9/qdORh0A=", + "dev": true + }, + "q": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.0.1.tgz", + "integrity": "sha1-EYcq7t7okmgRCxCnGESP+xARKhQ=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readable-wrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readable-wrap/-/readable-wrap-1.0.0.tgz", + "integrity": "sha1-O1ohHGMeEjA6VJkcgGwX564ga/8=", + "dev": true, + "requires": { + "readable-stream": "^1.1.13-1" + } + }, + "request": { + "version": "2.35.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.35.0.tgz", + "integrity": "sha1-DVwPKTR5oIDLpQj5Q0LNJBWg0pc=", + "dev": true, + "requires": { + "aws-sign2": "~0.5.0", + "forever-agent": "~0.5.0", + "form-data": "~0.1.0", + "hawk": "~1.0.0", + "http-signature": "~0.10.0", + "json-stringify-safe": "~5.0.0", + "lodash.merge": "~2.4.1", + "mime": "~1.2.9", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.3.0", + "qs": "~0.6.0", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" + }, + "dependencies": { + "qs": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz", + "integrity": "sha1-bgFQmP9RlouKPIGQAdXyyJvEsQc=", + "dev": true + } + } + }, + "resolve": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.7.4.tgz", + "integrity": "sha1-OVqe+ehz+/4SvRRAi9kbuTYAPWk=", + "dev": true + }, + "rfile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rfile/-/rfile-1.0.0.tgz", + "integrity": "sha1-WXCM+Qyh50xUw8/Fw2/bmBBDUmE=", + "dev": true, + "requires": { + "callsite": "~1.0.0", + "resolve": "~0.3.0" + }, + "dependencies": { + "resolve": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.3.1.tgz", + "integrity": "sha1-NMY0R8ZkxwWY0cmxJvxDsqJDEKQ=", + "dev": true + } + } + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "ruglify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ruglify/-/ruglify-1.0.0.tgz", + "integrity": "sha1-3Ikw4qlUSidDAcyZcldMDQmGtnU=", + "dev": true, + "requires": { + "rfile": "~1.0", + "uglify-js": "~2.2" + }, + "dependencies": { + "optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "dev": true, + "requires": { + "wordwrap": "~0.0.2" + } + }, + "uglify-js": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", + "integrity": "sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc=", + "dev": true, + "requires": { + "optimist": "~0.3.5", + "source-map": "~0.1.7" + } + } + } + }, + "rx": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz", + "integrity": "sha1-Ia3H2A8CACr1Da6X/Z2/JIdV9WY=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sauce-tunnel": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/sauce-tunnel/-/sauce-tunnel-2.1.1.tgz", + "integrity": "sha1-uKOuuCaZd84IS8LFZrf3nCqKb04=", + "dev": true, + "requires": { + "chalk": "~0.4.0", + "request": "~2.21.0" + }, + "dependencies": { + "assert-plus": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.2.tgz", + "integrity": "sha1-2T/9u2esVQd3m+MWp9ZRRkF77vg=", + "dev": true + }, + "ctype": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.2.tgz", + "integrity": "sha1-/oCR1Gijc6Cwyf+Lv7NCXACXOh0=", + "dev": true + }, + "form-data": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.0.8.tgz", + "integrity": "sha1-CJDNEAXFzOzAudJKiAUskkQtDbU=", + "dev": true, + "requires": { + "async": "~0.2.7", + "combined-stream": "~0.0.4", + "mime": "~1.2.2" + } + }, + "hawk": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-0.13.1.tgz", + "integrity": "sha1-NheViCH1gxHk1/beKR/KZitBLvQ=", + "dev": true, + "requires": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.8.x", + "sntp": "0.2.x" + } + }, + "hoek": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.8.5.tgz", + "integrity": "sha1-Hp/XcO9+vgJ0rfy1sIBqAlpeTp8=", + "dev": true + }, + "http-signature": { + "version": "0.9.11", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.9.11.tgz", + "integrity": "sha1-nognFFcjFeZ5Cl0KeVXv/x8Z5lM=", + "dev": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "0.1.2", + "ctype": "0.5.2" + } + }, + "json-stringify-safe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-4.0.0.tgz", + "integrity": "sha1-d8JxqupUMC5o7+rMtWq78GqbGlQ=", + "dev": true + }, + "qs": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz", + "integrity": "sha1-bgFQmP9RlouKPIGQAdXyyJvEsQc=", + "dev": true + }, + "request": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.21.0.tgz", + "integrity": "sha1-VyirnEXlqHyZ2szVMCmLZnOoaNc=", + "dev": true, + "requires": { + "aws-sign": "~0.3.0", + "cookie-jar": "~0.3.0", + "forever-agent": "~0.5.0", + "form-data": "0.0.8", + "hawk": "~0.13.0", + "http-signature": "~0.9.11", + "json-stringify-safe": "~4.0.0", + "mime": "~1.2.9", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.3.0", + "qs": "~0.6.0", + "tunnel-agent": "~0.3.0" + } + }, + "tunnel-agent": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.3.0.tgz", + "integrity": "sha1-rWgbaPUyGtKCfEz7G31d8s/pQu4=", + "dev": true + } + } + }, + "saucelabs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-0.1.1.tgz", + "integrity": "sha1-Xg6hzz1zXW6hX96Utb2mvBXSwG0=", + "dev": true + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", + "dev": true + }, + "shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "requires": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "shell-quote": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-0.0.1.tgz", + "integrity": "sha1-GkEZbzwDM8SCMjWT1ohuzxU92YY=", + "dev": true + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "sinon": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", + "integrity": "sha1-emnWnNApRYbHQyVO7/G1g6UJl/I=", + "dev": true, + "requires": { + "buster-format": "~0.5" + } + }, + "sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", + "dev": true, + "requires": { + "hoek": "0.9.x" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stream-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz", + "integrity": "sha1-v5tKv7QrJ011FHnkTg/yZWtvEZM=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^1.0.27-1" + } + }, + "stream-combiner2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz", + "integrity": "sha1-unKmtQy/q/qVD8i8h2BL0B62BnE=", + "dev": true, + "requires": { + "duplexer2": "~0.0.2", + "through2": "~0.5.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + } + } + }, + "stream-splicer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-1.3.2.tgz", + "integrity": "sha1-PARBvhW5v04iYnXm3IOWR0VUZmE=", + "dev": true, + "requires": { + "indexof": "0.0.1", + "inherits": "^2.0.1", + "isarray": "~0.0.1", + "readable-stream": "^1.1.13-1", + "readable-wrap": "^1.0.0", + "through2": "^1.0.0" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "requires": { + "minimist": "^1.1.0" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + }, + "syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "requires": { + "acorn-node": "^1.2.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "requires": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "requires": { + "process": "~0.11.0" + }, + "dependencies": { + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + } + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "optional": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "optional": true + } + } + }, + "tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "2.4.24", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz", + "integrity": "sha1-+tV1XB4Vd2WLsG/5q25UjJW+vW4=", + "dev": true, + "requires": { + "async": "~0.2.6", + "source-map": "0.1.34", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.5.4" + }, + "dependencies": { + "source-map": { + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz", + "integrity": "sha1-p8/omux7FoLDsZjQrPtH19CQVms=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true + }, + "umd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/umd/-/umd-2.1.0.tgz", + "integrity": "sha1-SmMHt2LxfwLSAbX6FU5nM5bCY88=", + "dev": true, + "requires": { + "rfile": "~1.0.0", + "ruglify": "~1.0.0", + "through": "~2.3.4", + "uglify-js": "~2.4.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + }, + "yargs": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz", + "integrity": "sha1-2K/49mXpTDS9JZvevRv68N3TU2E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "decamelize": "^1.0.0", + "window-size": "0.1.0", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..33f696c --- /dev/null +++ b/package.json @@ -0,0 +1,78 @@ +{ + "name": "bluebird", + "description": "Full featured Promises/A+ implementation with exceptionally good performance", + "version": "3.7.2", + "keywords": [ + "promise", + "performance", + "promises", + "promises-a", + "promises-aplus", + "async", + "await", + "deferred", + "deferreds", + "future", + "flow control", + "dsl", + "fluent interface" + ], + "scripts": { + "lint": "node scripts/jshint.js", + "test": "node --expose-gc tools/test.js", + "istanbul": "istanbul", + "prepublish": "npm run generate-browser-core && npm run generate-browser-full", + "generate-browser-full": "node tools/build.js --no-clean --no-debug --release --browser --minify", + "generate-browser-core": "node tools/build.js --features=core --no-debug --release --zalgo --browser --minify && mv js/browser/bluebird.js js/browser/bluebird.core.js && mv js/browser/bluebird.min.js js/browser/bluebird.core.min.js" + }, + "homepage": "https://github.com/petkaantonov/bluebird", + "repository": { + "type": "git", + "url": "git://github.com/petkaantonov/bluebird.git" + }, + "bugs": { + "url": "http://github.com/petkaantonov/bluebird/issues" + }, + "license": "MIT", + "author": { + "name": "Petka Antonov", + "email": "petka_antonov@hotmail.com", + "url": "http://github.com/petkaantonov/" + }, + "devDependencies": { + "acorn": "^6.0.2", + "acorn-walk": "^6.1.0", + "baconjs": "^0.7.43", + "bluebird": "^2.9.2", + "body-parser": "^1.10.2", + "browserify": "^8.1.1", + "cli-table": "~0.3.1", + "co": "^4.2.0", + "cross-spawn": "^0.2.3", + "glob": "^4.3.2", + "grunt-saucelabs": "~8.4.1", + "highland": "^2.3.0", + "istanbul": "^0.3.5", + "jshint": "^2.6.0", + "jshint-stylish": "~0.2.0", + "kefir": "^2.4.1", + "mkdirp": "~0.5.0", + "mocha": "~2.1", + "open": "~0.0.5", + "optimist": "~0.6.1", + "rimraf": "~2.2.6", + "rx": "^2.3.25", + "serve-static": "^1.7.1", + "sinon": "~1.7.3", + "uglify-js": "~2.4.16" + }, + "readmeFilename": "README.md", + "main": "./js/release/bluebird.js", + "webpack": "./js/release/bluebird.js", + "browser": "./js/browser/bluebird.js", + "files": [ + "js/browser", + "js/release", + "LICENSE" + ] +} diff --git a/src/any.js b/src/any.js new file mode 100644 index 0000000..7b4d74b --- /dev/null +++ b/src/any.js @@ -0,0 +1,25 @@ +"use strict"; +module.exports = function(Promise) { +var SomePromiseArray = Promise._SomePromiseArray; +var ASSERT = require("./assert"); + +function any(promises) { + var ret = new SomePromiseArray(promises); + var promise = ret.promise(); + ASSERT(promise.isPending()); + ASSERT(ret instanceof SomePromiseArray); + ret.setHowMany(1); + ret.setUnwrap(); + ret.init(); + return promise; +} + +Promise.any = function (promises) { + return any(promises); +}; + +Promise.prototype.any = function () { + return any(this); +}; + +}; diff --git a/src/assert.js b/src/assert.js new file mode 100644 index 0000000..c865847 --- /dev/null +++ b/src/assert.js @@ -0,0 +1,55 @@ +"use strict"; +module.exports = (function(){ +var AssertionError = (function() { + function AssertionError(a) { + this.constructor$(a); + this.message = a; + this.name = "AssertionError"; + } + AssertionError.prototype = new Error(); + AssertionError.prototype.constructor = AssertionError; + AssertionError.prototype.constructor$ = Error; + return AssertionError; +})(); + +function getParams(args) { + var params = []; + for (var i = 0; i < args.length; ++i) params.push("arg" + i); + return params; +} + +function nativeAssert(callName, args, expect) { + try { + var params = getParams(args); + var constructorArgs = params; + constructorArgs.push("return " + + callName + "("+ params.join(",") + ");"); + var fn = Function.apply(null, constructorArgs); + return fn.apply(null, args); + } catch (e) { + if (!(e instanceof SyntaxError)) { + throw e; + } else { + return expect; + } + } +} + +return function assert(boolExpr, message) { + if (boolExpr === true) return; + + if (typeof boolExpr === "string" && + boolExpr.charAt(0) === "%") { + var nativeCallName = boolExpr; + INLINE_SLICE(args, arguments, 2); + if (nativeAssert(nativeCallName, args, message) === message) return; + message = (nativeCallName + " !== " + message); + } + + var ret = new AssertionError(message); + if (Error.captureStackTrace) { + Error.captureStackTrace(ret, assert); + } + throw ret; +}; +})(); diff --git a/src/async.js b/src/async.js new file mode 100644 index 0000000..98873ff --- /dev/null +++ b/src/async.js @@ -0,0 +1,129 @@ +"use strict"; +var firstLineError; +try {throw new Error(); } catch (e) {firstLineError = e;} +var ASSERT = require("./assert"); +var schedule = require("./schedule"); +var Queue = require("./queue"); + +function Async() { + this._customScheduler = false; + this._isTickUsed = false; + this._lateQueue = new Queue(LATE_QUEUE_CAPACITY); + this._normalQueue = new Queue(NORMAL_QUEUE_CAPACITY); + this._haveDrainedQueues = false; + var self = this; + this.drainQueues = function () { + self._drainQueues(); + }; + this._schedule = schedule; +} + +Async.prototype.setScheduler = function(fn) { + var prev = this._schedule; + this._schedule = fn; + this._customScheduler = true; + return prev; +}; + +Async.prototype.hasCustomScheduler = function() { + return this._customScheduler; +}; + +Async.prototype.haveItemsQueued = function () { + return this._isTickUsed || this._haveDrainedQueues; +}; + + +Async.prototype.fatalError = function(e, isNode) { + if (isNode) { + process.stderr.write("Fatal " + (e instanceof Error ? e.stack : e) + + "\n"); + process.exit(2); + } else { + this.throwLater(e); + } +}; + +// Must be used if fn can throw +Async.prototype.throwLater = function(fn, arg) { + if (arguments.length === 1) { + arg = fn; + fn = function () { throw arg; }; + } + if (typeof setTimeout !== "undefined") { + setTimeout(function() { + fn(arg); + }, 0); + } else try { + this._schedule(function() { + fn(arg); + }); + } catch (e) { + throw new Error(NO_ASYNC_SCHEDULER); + } +}; + +//When the fn absolutely needs to be called after +//the queue has been completely flushed +function AsyncInvokeLater(fn, receiver, arg) { + ASSERT(arguments.length === 3); + this._lateQueue.push(fn, receiver, arg); + this._queueTick(); +} + +function AsyncInvoke(fn, receiver, arg) { + ASSERT(arguments.length === 3); + this._normalQueue.push(fn, receiver, arg); + this._queueTick(); +} + +function AsyncSettlePromises(promise) { + this._normalQueue._pushOne(promise); + this._queueTick(); +} + +Async.prototype.invokeLater = AsyncInvokeLater; +Async.prototype.invoke = AsyncInvoke; +Async.prototype.settlePromises = AsyncSettlePromises; + + +function _drainQueue(queue) { + while (queue.length() > 0) { + _drainQueueStep(queue); + } +} + +// Shift the queue in a separate function to allow +// garbage collection after each step +function _drainQueueStep(queue) { + var fn = queue.shift(); + if (typeof fn !== "function") { + fn._settlePromises(); + } else { + var receiver = queue.shift(); + var arg = queue.shift(); + fn.call(receiver, arg); + } +} + +Async.prototype._drainQueues = function () { + ASSERT(this._isTickUsed); + _drainQueue(this._normalQueue); + this._reset(); + this._haveDrainedQueues = true; + _drainQueue(this._lateQueue); +}; + +Async.prototype._queueTick = function () { + if (!this._isTickUsed) { + this._isTickUsed = true; + this._schedule(this.drainQueues); + } +}; + +Async.prototype._reset = function () { + this._isTickUsed = false; +}; + +module.exports = Async; +module.exports.firstLineError = firstLineError; diff --git a/src/bind.js b/src/bind.js new file mode 100644 index 0000000..0a5cf9f --- /dev/null +++ b/src/bind.js @@ -0,0 +1,67 @@ +"use strict"; +module.exports = function(Promise, INTERNAL, tryConvertToPromise, debug) { +var calledBind = false; +var rejectThis = function(_, e) { + this._reject(e); +}; + +var targetRejected = function(e, context) { + context.promiseRejectionQueued = true; + context.bindingPromise._then(rejectThis, rejectThis, null, this, e); +}; + +var bindingResolved = function(thisArg, context) { + if (BIT_FIELD_CHECK(IS_PENDING_AND_WAITING_NEG, this._bitField)) { + this._resolveCallback(context.target); + } +}; + +var bindingRejected = function(e, context) { + if (!context.promiseRejectionQueued) this._reject(e); +}; + +Promise.prototype.bind = function (thisArg) { + if (!calledBind) { + calledBind = true; + Promise.prototype._propagateFrom = debug.propagateFromFunction(); + Promise.prototype._boundValue = debug.boundValueFunction(); + } + var maybePromise = tryConvertToPromise(thisArg); + var ret = new Promise(INTERNAL); + ret._propagateFrom(this, PROPAGATE_CANCEL); + var target = this._target(); + ret._setBoundTo(maybePromise); + if (maybePromise instanceof Promise) { + var context = { + promiseRejectionQueued: false, + promise: ret, + target: target, + bindingPromise: maybePromise + }; + target._then(INTERNAL, targetRejected, undefined, ret, context); + maybePromise._then( + bindingResolved, bindingRejected, undefined, ret, context); + ret._setOnCancel(maybePromise); + } else { + ret._resolveCallback(target); + } + return ret; +}; + +Promise.prototype._setBoundTo = function (obj) { + if (obj !== undefined) { + this._bitField = this._bitField | IS_BOUND; + this._boundTo = obj; + } else { + this._bitField = this._bitField & (~IS_BOUND); + } +}; + +Promise.prototype._isBound = function () { + return (this._bitField & IS_BOUND) === IS_BOUND; +}; + +Promise.bind = function (thisArg, value) { + return Promise.resolve(value).bind(thisArg); +}; +}; diff --git a/src/bluebird.js b/src/bluebird.js new file mode 100644 index 0000000..1c36cf3 --- /dev/null +++ b/src/bluebird.js @@ -0,0 +1,11 @@ +"use strict"; +var old; +if (typeof Promise !== "undefined") old = Promise; +function noConflict() { + try { if (Promise === bluebird) Promise = old; } + catch (e) {} + return bluebird; +} +var bluebird = require("./promise")(); +bluebird.noConflict = noConflict; +module.exports = bluebird; diff --git a/src/call_get.js b/src/call_get.js new file mode 100644 index 0000000..2a5a2e1 --- /dev/null +++ b/src/call_get.js @@ -0,0 +1,123 @@ +"use strict"; +var cr = Object.create; +if (cr) { + var callerCache = cr(null); + var getterCache = cr(null); + callerCache[" size"] = getterCache[" size"] = 0; +} + +module.exports = function(Promise) { +var util = require("./util"); +var canEvaluate = util.canEvaluate; +var isIdentifier = util.isIdentifier; + +var getMethodCaller; +var getGetter; +if (!__BROWSER__) { +var makeMethodCaller = function (methodName) { + return new Function("ensureMethod", " \n\ + return function(obj) { \n\ + 'use strict' \n\ + var len = this.length; \n\ + ensureMethod(obj, 'methodName'); \n\ + switch(len) { \n\ + case 1: return obj.methodName(this[0]); \n\ + case 2: return obj.methodName(this[0], this[1]); \n\ + case 3: return obj.methodName(this[0], this[1], this[2]); \n\ + case 0: return obj.methodName(); \n\ + default: \n\ + return obj.methodName.apply(obj, this); \n\ + } \n\ + }; \n\ + ".replace(/methodName/g, methodName))(ensureMethod); +}; + +var makeGetter = function (propertyName) { + return new Function("obj", " \n\ + 'use strict'; \n\ + return obj.propertyName; \n\ + ".replace("propertyName", propertyName)); +}; + +var getCompiled = function(name, compiler, cache) { + var ret = cache[name]; + if (typeof ret !== "function") { + if (!isIdentifier(name)) { + return null; + } + ret = compiler(name); + cache[name] = ret; + cache[" size"]++; + if (cache[" size"] > 512) { + var keys = Object.keys(cache); + for (var i = 0; i < 256; ++i) delete cache[keys[i]]; + cache[" size"] = keys.length - 256; + } + } + return ret; +}; + +getMethodCaller = function(name) { + return getCompiled(name, makeMethodCaller, callerCache); +}; + +getGetter = function(name) { + return getCompiled(name, makeGetter, getterCache); +}; +} + +function ensureMethod(obj, methodName) { + var fn; + if (obj != null) fn = obj[methodName]; + if (typeof fn !== "function") { + var message = "Object " + util.classString(obj) + " has no method '" + + util.toString(methodName) + "'"; + throw new Promise.TypeError(message); + } + return fn; +} + +function caller(obj) { + var methodName = this.pop(); + var fn = ensureMethod(obj, methodName); + return fn.apply(obj, this); +} +Promise.prototype.call = function (methodName) { + INLINE_SLICE(args, arguments, 1); + if (!__BROWSER__) { + if (canEvaluate) { + var maybeCaller = getMethodCaller(methodName); + if (maybeCaller !== null) { + return this._then( + maybeCaller, undefined, undefined, args, undefined); + } + } + } + args.push(methodName); + return this._then(caller, undefined, undefined, args, undefined); +}; + +function namedGetter(obj) { + return obj[this]; +} +function indexedGetter(obj) { + var index = +this; + if (index < 0) index = Math.max(0, index + obj.length); + return obj[index]; +} +Promise.prototype.get = function (propertyName) { + var isIndex = (typeof propertyName === "number"); + var getter; + if (!isIndex) { + if (canEvaluate) { + var maybeGetter = getGetter(propertyName); + getter = maybeGetter !== null ? maybeGetter : namedGetter; + } else { + getter = namedGetter; + } + } else { + getter = indexedGetter; + } + return this._then(getter, undefined, undefined, propertyName, undefined); +}; +}; diff --git a/src/cancel.js b/src/cancel.js new file mode 100644 index 0000000..2328b54 --- /dev/null +++ b/src/cancel.js @@ -0,0 +1,136 @@ +"use strict"; +module.exports = function(Promise, PromiseArray, apiRejection, debug) { +var ASSERT = require("./assert"); +var util = require("./util"); +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; +var async = Promise._async; + +Promise.prototype["break"] = Promise.prototype.cancel = function() { + if (!debug.cancellation()) return this._warn("cancellation is disabled"); + + var promise = this; + var child = promise; + while (promise._isCancellable()) { + if (!promise._cancelBy(child)) { + if (child._isFollowing()) { + child._followee().cancel(); + } else { + child._cancelBranched(); + } + break; + } + + var parent = promise._cancellationParent; + if (parent == null || !parent._isCancellable()) { + if (promise._isFollowing()) { + promise._followee().cancel(); + } else { + promise._cancelBranched(); + } + break; + } else { + if (promise._isFollowing()) promise._followee().cancel(); + promise._setWillBeCancelled(); + child = promise; + promise = parent; + } + } +}; + +Promise.prototype._branchHasCancelled = function() { + ASSERT(typeof this._branchesRemainingToCancel === "number"); + this._branchesRemainingToCancel--; +}; + +Promise.prototype._enoughBranchesHaveCancelled = function() { + return this._branchesRemainingToCancel === undefined || + this._branchesRemainingToCancel <= 0; +}; + +Promise.prototype._cancelBy = function(canceller) { + if (canceller === this) { + this._branchesRemainingToCancel = 0; + this._invokeOnCancel(); + return true; + } else { + ASSERT(canceller._cancellationParent === this); + this._branchHasCancelled(); + if (this._enoughBranchesHaveCancelled()) { + this._invokeOnCancel(); + return true; + } + } + return false; +}; + +Promise.prototype._cancelBranched = function() { + if (this._enoughBranchesHaveCancelled()) { + this._cancel(); + } +}; + +Promise.prototype._cancel = function() { + if (!this._isCancellable()) return; + ASSERT(!this._isFollowing()); + this._setCancelled(); + async.invoke(this._cancelPromises, this, undefined); +}; + +Promise.prototype._cancelPromises = function() { + if (this._length() > 0) this._settlePromises(); +}; + +Promise.prototype._unsetOnCancel = function() { + ASSERT(this._isCancellable() || this._isCancelled()); + this._onCancelField = undefined; +}; + +Promise.prototype._isCancellable = function() { + return this.isPending() && !this._isCancelled(); +}; + +Promise.prototype.isCancellable = function() { + return this.isPending() && !this.isCancelled(); +}; + +Promise.prototype._doInvokeOnCancel = function(onCancelCallback, internalOnly) { + if (util.isArray(onCancelCallback)) { + for (var i = 0; i < onCancelCallback.length; ++i) { + this._doInvokeOnCancel(onCancelCallback[i], internalOnly); + } + } else if (onCancelCallback !== undefined) { + if (typeof onCancelCallback === "function") { + if (!internalOnly) { + var e = tryCatch(onCancelCallback).call(this._boundValue()); + if (e === errorObj) { + this._attachExtraTrace(e.e); + async.throwLater(e.e); + } + } + } else { + onCancelCallback._resultCancelled(this); + } + } +}; + +Promise.prototype._invokeOnCancel = function() { + var onCancelCallback = this._onCancel(); + // The existence of onCancel handler on a promise signals that the handler + // has not been queued for invocation yet. + this._unsetOnCancel(); + async.invoke(this._doInvokeOnCancel, this, onCancelCallback); +}; + +Promise.prototype._invokeInternalOnCancel = function() { + if (this._isCancellable()) { + this._doInvokeOnCancel(this._onCancel(), true); + this._unsetOnCancel(); + } +}; + +Promise.prototype._resultCancelled = function() { + this.cancel(); +}; + +}; diff --git a/src/catch_filter.js b/src/catch_filter.js new file mode 100644 index 0000000..0f24ce2 --- /dev/null +++ b/src/catch_filter.js @@ -0,0 +1,42 @@ +"use strict"; +module.exports = function(NEXT_FILTER) { +var util = require("./util"); +var getKeys = require("./es5").keys; +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; + +function catchFilter(instances, cb, promise) { + return function(e) { + var boundTo = promise._boundValue(); + predicateLoop: for (var i = 0; i < instances.length; ++i) { + var item = instances[i]; + + if (item === Error || + (item != null && item.prototype instanceof Error)) { + if (e instanceof item) { + return tryCatch(cb).call(boundTo, e); + } + } else if (typeof item === "function") { + var matchesPredicate = tryCatch(item).call(boundTo, e); + if (matchesPredicate === errorObj) { + return matchesPredicate; + } else if (matchesPredicate) { + return tryCatch(cb).call(boundTo, e); + } + } else if (util.isObject(e)) { + var keys = getKeys(item); + for (var j = 0; j < keys.length; ++j) { + var key = keys[j]; + if (item[key] != e[key]) { + continue predicateLoop; + } + } + return tryCatch(cb).call(boundTo, e); + } + } + return NEXT_FILTER; + }; +} + +return catchFilter; +}; diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..a011036 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,154 @@ +//This is pretty lame but what you gonna do + +//async.js +CONSTANT(LATE_QUEUE_CAPACITY, 16); +CONSTANT(NORMAL_QUEUE_CAPACITY, 16); + +//errors.js +CONSTANT(ERROR_HANDLED_KEY, "__promiseHandled__"); +CONSTANT(OPERATIONAL_ERROR_KEY, "isOperational"); +CONSTANT(DEFAULT_STATE, 0); +CONSTANT(STACK_ATTACHED, 1); +CONSTANT(ERROR_HANDLED, 2); + +//join.js +CONSTANT(GENERATED_CLASS_COUNT, 8); + +//promise.js +CONSTANT(USE_BOUND, true); +CONSTANT(DONT_USE_BOUND, false); + +CONSTANT(PROPAGATE_CANCEL, 1); +CONSTANT(PROPAGATE_BIND, 2); +CONSTANT(PROPAGATE_ALL, PROPAGATE_CANCEL | PROPAGATE_BIND); + +CONSTANT(CALLBACK_FULFILL_OFFSET, 0); +CONSTANT(CALLBACK_REJECT_OFFSET, 1); +CONSTANT(CALLBACK_PROMISE_OFFSET, 2); +CONSTANT(CALLBACK_RECEIVER_OFFSET, 3); +CONSTANT(CALLBACK_SIZE, 4); +//Layout for ._bitField +//[RR]XO GWFN CTBH IUDE LLLL LLLL LLLL LLLL +//[RR] = [Reserved] (Both bits are either on or off to represent +// 1 bit due to 31-bit integers in 32-bit v8) +//R = [Reserved] +//X = noAsyncGuarantee +//O = returnedNonUndefined +//G = isAsyncGuaranteed +//W = isFollowing (The promise that is being followed is not stored explicitly) +//F = isFulfilled +//N = isRejected +//C = willBeCancelled +//T = isFinal (used for .done() implementation) +//B = isBound +//I = isRejectionIgnored +//H = isRejectionUnhandled +//U = isUnhanldedRejectionNotified +//D = isDisposable +//E = isCancelled +//L = Length, 16 bit unsigned +CONSTANT(ASYNC_GUARANTEE_SHIFT, 2) +CONSTANT(NO_STATE, 0x0|0); +CONSTANT(NO_ASYNC_GUARANTEE, 0x20000000|0); +CONSTANT(RETURNED_NON_UNDEFINED, 0x10000000|0); +CONSTANT(IS_ASYNC_GUARANTEED, 0x8000000|0); +CONSTANT(IS_FOLLOWING, 0x4000000|0); +CONSTANT(IS_FULFILLED, 0x2000000|0); +CONSTANT(IS_REJECTED, 0x1000000|0); +CONSTANT(WILL_BE_CANCELLED, 0x800000|0); +CONSTANT(IS_FINAL, 0x400000|0); +CONSTANT(IS_BOUND, 0x200000|0); +CONSTANT(IS_REJECTION_UNHANDLED, 0x100000|0); +CONSTANT(IS_REJECTION_IGNORED, 0x80000|0); +CONSTANT(IS_UNHANDLED_REJECTION_NOTIFIED, 0x40000|0); +CONSTANT(IS_DISPOSABLE, 0x20000|0); +CONSTANT(IS_CANCELLED, 0x10000|0); +CONSTANT(IS_CANCELLED_OR_WILL_BE_CANCELLED, IS_CANCELLED | WILL_BE_CANCELLED) +CONSTANT(LENGTH_MASK, 0xFFFF|0); +CONSTANT(LENGTH_CLEAR_MASK, ~LENGTH_MASK); +CONSTANT(MAX_LENGTH, LENGTH_MASK); +CONSTANT(IS_REJECTED_OR_CANCELLED, IS_REJECTED | IS_CANCELLED); +CONSTANT(IS_REJECTED_OR_FULFILLED, IS_REJECTED | IS_FULFILLED); +CONSTANT(IS_REJECTED_OR_FULFILLED_OR_CANCELLED, IS_REJECTED | IS_FULFILLED| IS_CANCELLED); +CONSTANT(IS_PENDING_AND_WAITING_NEG, IS_REJECTED_OR_FULFILLED_OR_CANCELLED); + +CONSTANT(IS_FATE_SEALED, IS_REJECTED | IS_FULFILLED | IS_FOLLOWING | IS_CANCELLED); + +CONSTANT(AFTER_PROMISIFIED_SUFFIX, "Async"); +CONSTANT(UNHANDLED_REJECTION_EVENT, "unhandledRejection"); +CONSTANT(REJECTION_HANDLED_EVENT, "rejectionHandled"); + +//promise_array.js +//MUST BE NEGATIVE NUMBERS +CONSTANT(RESOLVE_UNDEFINED, -1); +CONSTANT(RESOLVE_ARRAY, -2); +CONSTANT(RESOLVE_OBJECT, -3); +CONSTANT(RESOLVE_FOREVER_PENDING, -4); +CONSTANT(RESOLVE_CALL_METHOD, -5); +CONSTANT(RESOLVE_MAP, -6); + +//queue.js +CONSTANT(QUEUE_MAX_CAPACITY, (1 << 30) | 0); +CONSTANT(QUEUE_MIN_CAPACITY, 16); + +//captured_trace.js +CONSTANT(FROM_PREVIOUS_EVENT, "From previous event:"); +CONSTANT(NO_STACK_TRACE, " (No stack trace)"); +CONSTANT(ADDITIONAL_STACK_TRACE, "^--- With additional stack trace: "); +CONSTANT(UNHANDLED_REJECTION_HEADER, "Unhandled rejection "); + +//finally.js +CONSTANT(FINALLY_TYPE, 0); +CONSTANT(TAP_TYPE, 1); + +//direct_resolve.js +CONSTANT(THROW, 1); +CONSTANT(RETURN, 2); + +//promisify.js +CONSTANT(MAX_PARAM_COUNT, 1023); +CONSTANT(PARAM_COUNTS_TO_TRY, 3); + +//error.js +CONSTANT(BLUEBIRD_ERRORS, "__BluebirdErrorTypes__"); + +//deprecated +CONSTANT(OBJECT_PROMISIFY_DEPRECATED, "Promise.promisify for promisifying entire objects is deprecated. Use Promise.promisifyAll instead.\n\n\ + See http://goo.gl/MqrFmX"); +CONSTANT(SPAWN_DEPRECATED, "Promise.spawn is deprecated. Use Promise.coroutine instead."); + +//errors +CONSTANT(LATE_CANCELLATION_OBSERVER, "late cancellation observer"); +CONSTANT(TIMEOUT_ERROR, "operation timed out"); +CONSTANT(COLLECTION_ERROR, "expecting an array or an iterable object but got "); +CONSTANT(OBJECT_ERROR, "expecting an object but got "); +CONSTANT(FUNCTION_ERROR, "expecting a function but got "); +CONSTANT(CONSTRUCT_ERROR_INVOCATION, "the promise constructor cannot be invoked directly\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(NOT_GENERATOR_ERROR, "generatorFunction must be a function\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(LONG_STACK_TRACES_ERROR, "cannot enable long stack traces after promises have been created\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(INSPECTION_VALUE_ERROR, "cannot get fulfillment value of a non-fulfilled promise\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(INSPECTION_REASON_ERROR, "cannot get rejection reason of a non-rejected promise\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(PROMISIFY_TYPE_ERROR, "the target of promisifyAll must be an object or a function\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(CIRCULAR_RESOLUTION_ERROR, "circular promise resolution chain\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(PROPS_TYPE_ERROR, "cannot await properties of a non-object\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(POSITIVE_INTEGER_ERROR, "expecting a positive integer\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(YIELDED_NON_PROMISE_ERROR, "A value %s was yielded that could not be treated as a promise\n\n\ + See http://goo.gl/MqrFmX\n\n"); +CONSTANT(FROM_COROUTINE_CREATED_AT, "From coroutine:\n"); +CONSTANT(UNBOUND_RESOLVER_INVOCATION, "Illegal invocation, resolver resolve/reject must be called within a resolver context. Consider using the promise constructor instead.\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(PROMISIFICATION_NORMAL_METHODS_ERROR, "Cannot promisify an API that has normal methods with '%s'-suffix\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(SUFFIX_NOT_IDENTIFIER, "suffix must be a valid identifier\n\n\ + See http://goo.gl/MqrFmX\n"); +CONSTANT(NO_ASYNC_SCHEDULER, "No async scheduler available\n\n\ + See http://goo.gl/MqrFmX\n"); diff --git a/src/context.js b/src/context.js new file mode 100644 index 0000000..c307414 --- /dev/null +++ b/src/context.js @@ -0,0 +1,69 @@ +"use strict"; +module.exports = function(Promise) { +var longStackTraces = false; +var contextStack = []; + +Promise.prototype._promiseCreated = function() {}; +Promise.prototype._pushContext = function() {}; +Promise.prototype._popContext = function() {return null;}; +Promise._peekContext = Promise.prototype._peekContext = function() {}; + +function Context() { + this._trace = new Context.CapturedTrace(peekContext()); +} +Context.prototype._pushContext = function () { + if (this._trace !== undefined) { + this._trace._promiseCreated = null; + contextStack.push(this._trace); + } +}; + +Context.prototype._popContext = function () { + if (this._trace !== undefined) { + var trace = contextStack.pop(); + var ret = trace._promiseCreated; + trace._promiseCreated = null; + return ret; + } + return null; +}; + +function createContext() { + if (longStackTraces) return new Context(); +} + +function peekContext() { + var lastIndex = contextStack.length - 1; + if (lastIndex >= 0) { + return contextStack[lastIndex]; + } + return undefined; +} +Context.CapturedTrace = null; +Context.create = createContext; +Context.deactivateLongStackTraces = function() {}; +Context.activateLongStackTraces = function() { + var Promise_pushContext = Promise.prototype._pushContext; + var Promise_popContext = Promise.prototype._popContext; + var Promise_PeekContext = Promise._peekContext; + var Promise_peekContext = Promise.prototype._peekContext; + var Promise_promiseCreated = Promise.prototype._promiseCreated; + Context.deactivateLongStackTraces = function() { + Promise.prototype._pushContext = Promise_pushContext; + Promise.prototype._popContext = Promise_popContext; + Promise._peekContext = Promise_PeekContext; + Promise.prototype._peekContext = Promise_peekContext; + Promise.prototype._promiseCreated = Promise_promiseCreated; + longStackTraces = false; + }; + longStackTraces = true; + Promise.prototype._pushContext = Context.prototype._pushContext; + Promise.prototype._popContext = Context.prototype._popContext; + Promise._peekContext = Promise.prototype._peekContext = peekContext; + Promise.prototype._promiseCreated = function() { + var ctx = this._peekContext(); + if (ctx && ctx._promiseCreated == null) ctx._promiseCreated = this; + }; +}; +return Context; +}; diff --git a/src/debuggability.js b/src/debuggability.js new file mode 100644 index 0000000..ba49deb --- /dev/null +++ b/src/debuggability.js @@ -0,0 +1,1043 @@ +"use strict"; +module.exports = function(Promise, Context, + enableAsyncHooks, disableAsyncHooks) { +var async = Promise._async; +var Warning = require("./errors").Warning; +var util = require("./util"); +var es5 = require("./es5"); +var ASSERT = require("./assert"); +var canAttachTrace = util.canAttachTrace; +var unhandledRejectionHandled; +var possiblyUnhandledRejection; +var bluebirdFramePattern = + /[\\\/]bluebird[\\\/]js[\\\/](release|debug|instrumented)/; +var nodeFramePattern = /\((?:timers\.js):\d+:\d+\)/; +var parseLinePattern = /[\/<\(](.+?):(\d+):(\d+)\)?\s*$/; +var stackFramePattern = null; +var formatStack = null; +var indentStackFrames = false; +var printWarning; +var debugging = !!(util.env("BLUEBIRD_DEBUG") != 0 && + (__DEBUG__ || + util.env("BLUEBIRD_DEBUG") || + util.env("NODE_ENV") === "development")); + +var warnings = !!(util.env("BLUEBIRD_WARNINGS") != 0 && + (debugging || util.env("BLUEBIRD_WARNINGS"))); + +var longStackTraces = !!(util.env("BLUEBIRD_LONG_STACK_TRACES") != 0 && + (debugging || util.env("BLUEBIRD_LONG_STACK_TRACES"))); + +var wForgottenReturn = util.env("BLUEBIRD_W_FORGOTTEN_RETURN") != 0 && + (warnings || !!util.env("BLUEBIRD_W_FORGOTTEN_RETURN")); + +var deferUnhandledRejectionCheck; +(function() { + var promises = []; + + function unhandledRejectionCheck() { + for (var i = 0; i < promises.length; ++i) { + promises[i]._notifyUnhandledRejection(); + } + unhandledRejectionClear(); + } + + function unhandledRejectionClear() { + promises.length = 0; + } + + deferUnhandledRejectionCheck = function(promise) { + promises.push(promise); + setTimeout(unhandledRejectionCheck, 1); + }; + + es5.defineProperty(Promise, "_unhandledRejectionCheck", { + value: unhandledRejectionCheck + }); + es5.defineProperty(Promise, "_unhandledRejectionClear", { + value: unhandledRejectionClear + }); +})(); + +Promise.prototype.suppressUnhandledRejections = function() { + var target = this._target(); + target._bitField = ((target._bitField & (~IS_REJECTION_UNHANDLED)) | + IS_REJECTION_IGNORED); +}; + +Promise.prototype._ensurePossibleRejectionHandled = function () { + if ((this._bitField & IS_REJECTION_IGNORED) !== 0) return; + this._setRejectionIsUnhandled(); + deferUnhandledRejectionCheck(this); +}; + +Promise.prototype._notifyUnhandledRejectionIsHandled = function () { + fireRejectionEvent(REJECTION_HANDLED_EVENT, + unhandledRejectionHandled, undefined, this); +}; + +Promise.prototype._setReturnedNonUndefined = function() { + this._bitField = this._bitField | RETURNED_NON_UNDEFINED; +}; + +Promise.prototype._returnedNonUndefined = function() { + return (this._bitField & RETURNED_NON_UNDEFINED) !== 0; +}; + +Promise.prototype._notifyUnhandledRejection = function () { + if (this._isRejectionUnhandled()) { + var reason = this._settledValue(); + this._setUnhandledRejectionIsNotified(); + fireRejectionEvent(UNHANDLED_REJECTION_EVENT, + possiblyUnhandledRejection, reason, this); + } +}; + +Promise.prototype._setUnhandledRejectionIsNotified = function () { + this._bitField = this._bitField | IS_UNHANDLED_REJECTION_NOTIFIED; +}; + +Promise.prototype._unsetUnhandledRejectionIsNotified = function () { + this._bitField = this._bitField & (~IS_UNHANDLED_REJECTION_NOTIFIED); +}; + +Promise.prototype._isUnhandledRejectionNotified = function () { + return (this._bitField & IS_UNHANDLED_REJECTION_NOTIFIED) > 0; +}; + +Promise.prototype._setRejectionIsUnhandled = function () { + ASSERT(!this._isFollowing()); + this._bitField = this._bitField | IS_REJECTION_UNHANDLED; +}; + +Promise.prototype._unsetRejectionIsUnhandled = function () { + ASSERT(!this._isFollowing()); + this._bitField = this._bitField & (~IS_REJECTION_UNHANDLED); + if (this._isUnhandledRejectionNotified()) { + this._unsetUnhandledRejectionIsNotified(); + this._notifyUnhandledRejectionIsHandled(); + } +}; + +Promise.prototype._isRejectionUnhandled = function () { + ASSERT(!this._isFollowing()); + return (this._bitField & IS_REJECTION_UNHANDLED) > 0; +}; + +Promise.prototype._warn = function(message, shouldUseOwnTrace, promise) { + return warn(message, shouldUseOwnTrace, promise || this); +}; + +Promise.onPossiblyUnhandledRejection = function (fn) { + var context = Promise._getContext(); + possiblyUnhandledRejection = util.contextBind(context, fn); +}; + +Promise.onUnhandledRejectionHandled = function (fn) { + var context = Promise._getContext(); + unhandledRejectionHandled = util.contextBind(context, fn); +}; + +var disableLongStackTraces = function() {}; +Promise.longStackTraces = function () { + if (async.haveItemsQueued() && !config.longStackTraces) { + throw new Error(LONG_STACK_TRACES_ERROR); + } + if (!config.longStackTraces && longStackTracesIsSupported()) { + var Promise_captureStackTrace = Promise.prototype._captureStackTrace; + var Promise_attachExtraTrace = Promise.prototype._attachExtraTrace; + var Promise_dereferenceTrace = Promise.prototype._dereferenceTrace; + config.longStackTraces = true; + disableLongStackTraces = function() { + if (async.haveItemsQueued() && !config.longStackTraces) { + throw new Error(LONG_STACK_TRACES_ERROR); + } + Promise.prototype._captureStackTrace = Promise_captureStackTrace; + Promise.prototype._attachExtraTrace = Promise_attachExtraTrace; + Promise.prototype._dereferenceTrace = Promise_dereferenceTrace; + Context.deactivateLongStackTraces(); + config.longStackTraces = false; + }; + Promise.prototype._captureStackTrace = longStackTracesCaptureStackTrace; + Promise.prototype._attachExtraTrace = longStackTracesAttachExtraTrace; + Promise.prototype._dereferenceTrace = longStackTracesDereferenceTrace; + Context.activateLongStackTraces(); + } +}; + +Promise.hasLongStackTraces = function () { + return config.longStackTraces && longStackTracesIsSupported(); +}; + + +var legacyHandlers = { + unhandledrejection: { + before: function() { + var ret = util.global.onunhandledrejection; + util.global.onunhandledrejection = null; + return ret; + }, + after: function(fn) { + util.global.onunhandledrejection = fn; + } + }, + rejectionhandled: { + before: function() { + var ret = util.global.onrejectionhandled; + util.global.onrejectionhandled = null; + return ret; + }, + after: function(fn) { + util.global.onrejectionhandled = fn; + } + } +}; + +var fireDomEvent = (function() { + var dispatch = function(legacy, e) { + if (legacy) { + var fn; + try { + fn = legacy.before(); + return !util.global.dispatchEvent(e); + } finally { + legacy.after(fn); + } + } else { + return !util.global.dispatchEvent(e); + } + }; + try { + if (typeof CustomEvent === "function") { + var event = new CustomEvent("CustomEvent"); + util.global.dispatchEvent(event); + return function(name, event) { + name = name.toLowerCase(); + var eventData = { + detail: event, + cancelable: true + }; + var domEvent = new CustomEvent(name, eventData); + es5.defineProperty( + domEvent, "promise", {value: event.promise}); + es5.defineProperty( + domEvent, "reason", {value: event.reason}); + + return dispatch(legacyHandlers[name], domEvent); + }; + // In Firefox < 48 CustomEvent is not available in workers but + // Event is. + } else if (typeof Event === "function") { + var event = new Event("CustomEvent"); + util.global.dispatchEvent(event); + return function(name, event) { + name = name.toLowerCase(); + var domEvent = new Event(name, { + cancelable: true + }); + domEvent.detail = event; + es5.defineProperty(domEvent, "promise", {value: event.promise}); + es5.defineProperty(domEvent, "reason", {value: event.reason}); + return dispatch(legacyHandlers[name], domEvent); + }; + } else { + var event = document.createEvent("CustomEvent"); + event.initCustomEvent("testingtheevent", false, true, {}); + util.global.dispatchEvent(event); + return function(name, event) { + name = name.toLowerCase(); + var domEvent = document.createEvent("CustomEvent"); + domEvent.initCustomEvent(name, false, true, + event); + return dispatch(legacyHandlers[name], domEvent); + }; + } + } catch (e) {} + return function() { + return false; + }; +})(); + +var fireGlobalEvent = (function() { + if (util.isNode) { + return function() { + return process.emit.apply(process, arguments); + }; + } else { + if (!util.global) { + return function() { + return false; + }; + } + return function(name) { + var methodName = "on" + name.toLowerCase(); + var method = util.global[methodName]; + if (!method) return false; + method.apply(util.global, [].slice.call(arguments, 1)); + return true; + }; + } +})(); + +function generatePromiseLifecycleEventObject(name, promise) { + return {promise: promise}; +} + +var eventToObjectGenerator = { + promiseCreated: generatePromiseLifecycleEventObject, + promiseFulfilled: generatePromiseLifecycleEventObject, + promiseRejected: generatePromiseLifecycleEventObject, + promiseResolved: generatePromiseLifecycleEventObject, + promiseCancelled: generatePromiseLifecycleEventObject, + promiseChained: function(name, promise, child) { + return {promise: promise, child: child}; + }, + warning: function(name, warning) { + return {warning: warning}; + }, + unhandledRejection: function (name, reason, promise) { + return {reason: reason, promise: promise}; + }, + rejectionHandled: generatePromiseLifecycleEventObject +}; + +var activeFireEvent = function (name) { + var globalEventFired = false; + try { + globalEventFired = fireGlobalEvent.apply(null, arguments); + } catch (e) { + async.throwLater(e); + globalEventFired = true; + } + + var domEventFired = false; + try { + domEventFired = fireDomEvent(name, + eventToObjectGenerator[name].apply(null, arguments)); + } catch (e) { + async.throwLater(e); + domEventFired = true; + } + + return domEventFired || globalEventFired; +}; + +Promise.config = function(opts) { + opts = Object(opts); + if ("longStackTraces" in opts) { + if (opts.longStackTraces) { + Promise.longStackTraces(); + } else if (!opts.longStackTraces && Promise.hasLongStackTraces()) { + disableLongStackTraces(); + } + } + if ("warnings" in opts) { + var warningsOption = opts.warnings; + config.warnings = !!warningsOption; + wForgottenReturn = config.warnings; + + if (util.isObject(warningsOption)) { + if ("wForgottenReturn" in warningsOption) { + wForgottenReturn = !!warningsOption.wForgottenReturn; + } + } + } + if ("cancellation" in opts && opts.cancellation && !config.cancellation) { + if (async.haveItemsQueued()) { + throw new Error( + "cannot enable cancellation after promises are in use"); + } + Promise.prototype._clearCancellationData = + cancellationClearCancellationData; + Promise.prototype._propagateFrom = cancellationPropagateFrom; + Promise.prototype._onCancel = cancellationOnCancel; + Promise.prototype._setOnCancel = cancellationSetOnCancel; + Promise.prototype._attachCancellationCallback = + cancellationAttachCancellationCallback; + Promise.prototype._execute = cancellationExecute; + propagateFromFunction = cancellationPropagateFrom; + config.cancellation = true; + } + if ("monitoring" in opts) { + if (opts.monitoring && !config.monitoring) { + config.monitoring = true; + Promise.prototype._fireEvent = activeFireEvent; + } else if (!opts.monitoring && config.monitoring) { + config.monitoring = false; + Promise.prototype._fireEvent = defaultFireEvent; + } + } + if ("asyncHooks" in opts && util.nodeSupportsAsyncResource) { + var prev = config.asyncHooks; + var cur = !!opts.asyncHooks; + if (prev !== cur) { + config.asyncHooks = cur; + if (cur) { + enableAsyncHooks(); + } else { + disableAsyncHooks(); + } + } + } + return Promise; +}; + +function defaultFireEvent() { return false; } + +Promise.prototype._fireEvent = defaultFireEvent; +Promise.prototype._execute = function(executor, resolve, reject) { + try { + executor(resolve, reject); + } catch (e) { + return e; + } +}; +Promise.prototype._onCancel = function () {}; +Promise.prototype._setOnCancel = function (handler) { USE(handler); }; +Promise.prototype._attachCancellationCallback = function(onCancel) { + USE(onCancel); +}; +Promise.prototype._captureStackTrace = function () {}; +Promise.prototype._attachExtraTrace = function () {}; +Promise.prototype._dereferenceTrace = function () {}; +Promise.prototype._clearCancellationData = function() {}; +Promise.prototype._propagateFrom = function (parent, flags) { + USE(parent); + USE(flags); +}; + +function cancellationExecute(executor, resolve, reject) { + var promise = this; + try { + executor(resolve, reject, function(onCancel) { + if (typeof onCancel !== "function") { + throw new TypeError("onCancel must be a function, got: " + + util.toString(onCancel)); + } + promise._attachCancellationCallback(onCancel); + }); + } catch (e) { + return e; + } +} + +function cancellationAttachCancellationCallback(onCancel) { + if (!this._isCancellable()) return this; + + var previousOnCancel = this._onCancel(); + if (previousOnCancel !== undefined) { + if (util.isArray(previousOnCancel)) { + previousOnCancel.push(onCancel); + } else { + this._setOnCancel([previousOnCancel, onCancel]); + } + } else { + this._setOnCancel(onCancel); + } +} + +function cancellationOnCancel() { + ASSERT(this._isCancellable()); + return this._onCancelField; +} + +function cancellationSetOnCancel(onCancel) { + ASSERT(this._isCancellable()); + this._onCancelField = onCancel; +} + +function cancellationClearCancellationData() { + this._cancellationParent = undefined; + this._onCancelField = undefined; +} + +function cancellationPropagateFrom(parent, flags) { + ASSERT(flags !== 0); + if ((flags & PROPAGATE_CANCEL) !== 0) { + this._cancellationParent = parent; + var branchesRemainingToCancel = parent._branchesRemainingToCancel; + if (branchesRemainingToCancel === undefined) { + branchesRemainingToCancel = 0; + } + parent._branchesRemainingToCancel = branchesRemainingToCancel + 1; + } + if ((flags & PROPAGATE_BIND) !== 0 && parent._isBound()) { + this._setBoundTo(parent._boundTo); + } +} + +function bindingPropagateFrom(parent, flags) { + ASSERT(flags !== 0); + if ((flags & PROPAGATE_BIND) !== 0 && parent._isBound()) { + this._setBoundTo(parent._boundTo); + } +} +var propagateFromFunction = bindingPropagateFrom; + +function boundValueFunction() { + var ret = this._boundTo; + if (ret !== undefined) { + if (ret instanceof Promise) { + if (ret.isFulfilled()) { + return ret.value(); + } else { + return undefined; + } + } + } + return ret; +} + +function longStackTracesCaptureStackTrace() { + ASSERT(this._trace == null); + this._trace = new CapturedTrace(this._peekContext()); +} + +function longStackTracesAttachExtraTrace(error, ignoreSelf) { + if (canAttachTrace(error)) { + var trace = this._trace; + if (trace !== undefined) { + if (ignoreSelf) trace = trace._parent; + } + if (trace !== undefined) { + trace.attachExtraTrace(error); + } else if (!error.__stackCleaned__) { + var parsed = parseStackAndMessage(error); + util.notEnumerableProp(error, "stack", + parsed.message + "\n" + parsed.stack.join("\n")); + util.notEnumerableProp(error, "__stackCleaned__", true); + } + } +} + +function longStackTracesDereferenceTrace() { + this._trace = undefined; +} + +function checkForgottenReturns(returnValue, promiseCreated, name, promise, + parent) { + if (returnValue === undefined && promiseCreated !== null && + wForgottenReturn) { + if (parent !== undefined && parent._returnedNonUndefined()) return; + if (BIT_FIELD_READ(LENGTH_MASK, promise._bitField) === 0) return; + + if (name) name = name + " "; + var handlerLine = ""; + var creatorLine = ""; + if (promiseCreated._trace) { + var traceLines = promiseCreated._trace.stack.split("\n"); + var stack = cleanStack(traceLines); + for (var i = stack.length - 1; i >= 0; --i) { + var line = stack[i]; + if (!nodeFramePattern.test(line)) { + var lineMatches = line.match(parseLinePattern); + if (lineMatches) { + handlerLine = "at " + lineMatches[1] + + ":" + lineMatches[2] + ":" + lineMatches[3] + " "; + } + break; + } + } + + if (stack.length > 0) { + var firstUserLine = stack[0]; + for (var i = 0; i < traceLines.length; ++i) { + + if (traceLines[i] === firstUserLine) { + if (i > 0) { + creatorLine = "\n" + traceLines[i - 1]; + } + break; + } + } + + } + } + var msg = "a promise was created in a " + name + + "handler " + handlerLine + "but was not returned from it, " + + "see http://goo.gl/rRqMUw" + + creatorLine; + promise._warn(msg, true, promiseCreated); + } +} + +function deprecated(name, replacement) { + var message = name + + " is deprecated and will be removed in a future version."; + if (replacement) message += " Use " + replacement + " instead."; + return warn(message); +} + +function warn(message, shouldUseOwnTrace, promise) { + if (!config.warnings) return; + var warning = new Warning(message); + var ctx; + if (shouldUseOwnTrace) { + promise._attachExtraTrace(warning); + } else if (config.longStackTraces && (ctx = Promise._peekContext())) { + ctx.attachExtraTrace(warning); + } else { + var parsed = parseStackAndMessage(warning); + warning.stack = parsed.message + "\n" + parsed.stack.join("\n"); + } + + if (!activeFireEvent("warning", warning)) { + formatAndLogError(warning, "", true); + } +} + +function reconstructStack(message, stacks) { + for (var i = 0; i < stacks.length - 1; ++i) { + stacks[i].push(FROM_PREVIOUS_EVENT); + stacks[i] = stacks[i].join("\n"); + } + if (i < stacks.length) { + stacks[i] = stacks[i].join("\n"); + } + return message + "\n" + stacks.join("\n"); +} + +function removeDuplicateOrEmptyJumps(stacks) { + for (var i = 0; i < stacks.length; ++i) { + if (stacks[i].length === 0 || + ((i + 1 < stacks.length) && stacks[i][0] === stacks[i+1][0])) { + stacks.splice(i, 1); + i--; + } + } +} + +function removeCommonRoots(stacks) { + var current = stacks[0]; + for (var i = 1; i < stacks.length; ++i) { + var prev = stacks[i]; + var currentLastIndex = current.length - 1; + var currentLastLine = current[currentLastIndex]; + var commonRootMeetPoint = -1; + + for (var j = prev.length - 1; j >= 0; --j) { + if (prev[j] === currentLastLine) { + commonRootMeetPoint = j; + break; + } + } + + for (var j = commonRootMeetPoint; j >= 0; --j) { + var line = prev[j]; + if (current[currentLastIndex] === line) { + current.pop(); + currentLastIndex--; + } else { + break; + } + } + current = prev; + } +} + +function cleanStack(stack) { + var ret = []; + for (var i = 0; i < stack.length; ++i) { + var line = stack[i]; + var isTraceLine = NO_STACK_TRACE === line || + stackFramePattern.test(line); + var isInternalFrame = isTraceLine && shouldIgnore(line); + if (isTraceLine && !isInternalFrame) { + if (indentStackFrames && line.charAt(0) !== " ") { + // Make Firefox stack traces readable...it is almost + // impossible to see the event boundaries without + // indentation. + line = " " + line; + } + ret.push(line); + } + } + return ret; +} + +function stackFramesAsArray(error) { + var stack = error.stack.replace(/\s+$/g, "").split("\n"); + for (var i = 0; i < stack.length; ++i) { + var line = stack[i]; + if (NO_STACK_TRACE === line || stackFramePattern.test(line)) { + break; + } + } + // Chrome and IE include the error message in the stack + if (i > 0 && error.name != "SyntaxError") { + stack = stack.slice(i); + } + return stack; +} + +function parseStackAndMessage(error) { + var stack = error.stack; + var message = error.toString(); + stack = typeof stack === "string" && stack.length > 0 + ? stackFramesAsArray(error) : [NO_STACK_TRACE]; + return { + message: message, + stack: error.name == "SyntaxError" ? stack : cleanStack(stack) + }; +} + +function formatAndLogError(error, title, isSoft) { + if (typeof console !== "undefined") { + var message; + if (util.isObject(error)) { + var stack = error.stack; + message = title + formatStack(stack, error); + } else { + message = title + String(error); + } + if (typeof printWarning === "function") { + printWarning(message, isSoft); + } else if (typeof console.log === "function" || + typeof console.log === "object") { + console.log(message); + } + } +} + +function fireRejectionEvent(name, localHandler, reason, promise) { + var localEventFired = false; + try { + if (typeof localHandler === "function") { + localEventFired = true; + if (name === REJECTION_HANDLED_EVENT) { + localHandler(promise); + } else { + localHandler(reason, promise); + } + } + } catch (e) { + async.throwLater(e); + } + + if (name === UNHANDLED_REJECTION_EVENT) { + if (!activeFireEvent(name, reason, promise) && !localEventFired) { + formatAndLogError(reason, UNHANDLED_REJECTION_HEADER); + } + } else { + activeFireEvent(name, promise); + } +} + +function formatNonError(obj) { + var str; + if (typeof obj === "function") { + str = "[function " + + (obj.name || "anonymous") + + "]"; + } else { + str = obj && typeof obj.toString === "function" + ? obj.toString() : util.toString(obj); + var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/; + if (ruselessToString.test(str)) { + try { + var newStr = JSON.stringify(obj); + str = newStr; + } + catch(e) { + + } + } + if (str.length === 0) { + str = "(empty array)"; + } + } + return ("(<" + snip(str) + ">, no stack trace)"); +} + +function snip(str) { + var maxChars = 41; + if (str.length < maxChars) { + return str; + } + return str.substr(0, maxChars - 3) + "..."; +} + +function longStackTracesIsSupported() { + return typeof captureStackTrace === "function"; +} + +// For filtering out internal calls from stack traces +var shouldIgnore = function() { return false; }; +var parseLineInfoRegex = /[\/<\(]([^:\/]+):(\d+):(?:\d+)\)?\s*$/; +function parseLineInfo(line) { + var matches = line.match(parseLineInfoRegex); + if (matches) { + return { + fileName: matches[1], + line: parseInt(matches[2], 10) + }; + } +} + +function setBounds(firstLineError, lastLineError) { + if (!longStackTracesIsSupported()) return; + var firstStackLines = (firstLineError.stack || "").split("\n"); + var lastStackLines = (lastLineError.stack || "").split("\n"); + var firstIndex = -1; + var lastIndex = -1; + var firstFileName; + var lastFileName; + for (var i = 0; i < firstStackLines.length; ++i) { + var result = parseLineInfo(firstStackLines[i]); + if (result) { + firstFileName = result.fileName; + firstIndex = result.line; + break; + } + } + for (var i = 0; i < lastStackLines.length; ++i) { + var result = parseLineInfo(lastStackLines[i]); + if (result) { + lastFileName = result.fileName; + lastIndex = result.line; + break; + } + } + if (firstIndex < 0 || lastIndex < 0 || !firstFileName || !lastFileName || + firstFileName !== lastFileName || firstIndex >= lastIndex) { + return; + } + + shouldIgnore = function(line) { + if (bluebirdFramePattern.test(line)) return true; + var info = parseLineInfo(line); + if (info) { + if (info.fileName === firstFileName && + (firstIndex <= info.line && info.line <= lastIndex)) { + return true; + } + } + return false; + }; +} + +function CapturedTrace(parent) { + ASSERT(parent === undefined || parent instanceof CapturedTrace); + this._parent = parent; + this._promisesCreated = 0; + var length = this._length = 1 + (parent === undefined ? 0 : parent._length); + captureStackTrace(this, CapturedTrace); + // Unless the user manually nested > 32 indentation levels, + // there must be cycles + if (length > 32) this.uncycle(); +} +util.inherits(CapturedTrace, Error); +Context.CapturedTrace = CapturedTrace; + +CapturedTrace.prototype.uncycle = function() { + var length = this._length; + if (length < 2) return; + var nodes = []; + var stackToIndex = {}; + + for (var i = 0, node = this; node !== undefined; ++i) { + nodes.push(node); + node = node._parent; + } + // the node length is only used as heuristic to decide when to decycle, as + // there may be multiple linked lists that share members and decycling one + // will fail to update lenghts in the other. This is the correct length. + length = this._length = i; + ASSERT(nodes[0] === this); + ASSERT(nodes[nodes.length - 1] instanceof CapturedTrace); + + for (var i = length - 1; i >= 0; --i) { + var stack = nodes[i].stack; + if (stackToIndex[stack] === undefined) { + stackToIndex[stack] = i; + } + } + for (var i = 0; i < length; ++i) { + var currentStack = nodes[i].stack; + var index = stackToIndex[currentStack]; + ASSERT(currentStack === nodes[index].stack); + + if (index !== undefined && index !== i) { + if (index > 0) { + ASSERT(nodes[index - 1]._parent === nodes[index]); + nodes[index - 1]._parent = undefined; + nodes[index - 1]._length = 1; + } + nodes[i]._parent = undefined; + nodes[i]._length = 1; + var cycleEdgeNode = i > 0 ? nodes[i - 1] : this; + + if (index < length - 1) { + cycleEdgeNode._parent = nodes[index + 1]; + cycleEdgeNode._parent.uncycle(); + cycleEdgeNode._length = + cycleEdgeNode._parent._length + 1; + } else { + cycleEdgeNode._parent = undefined; + cycleEdgeNode._length = 1; + } + var currentChildLength = cycleEdgeNode._length + 1; + for (var j = i - 2; j >= 0; --j) { + nodes[j]._length = currentChildLength; + currentChildLength++; + } + return; + } + } +}; + +CapturedTrace.prototype.attachExtraTrace = function(error) { + if (error.__stackCleaned__) return; + this.uncycle(); + var parsed = parseStackAndMessage(error); + var message = parsed.message; + var stacks = [parsed.stack]; + + var trace = this; + while (trace !== undefined) { + stacks.push(cleanStack(trace.stack.split("\n"))); + trace = trace._parent; + } + removeCommonRoots(stacks); + removeDuplicateOrEmptyJumps(stacks); + util.notEnumerableProp(error, "stack", reconstructStack(message, stacks)); + util.notEnumerableProp(error, "__stackCleaned__", true); +}; + +var captureStackTrace = (function stackDetection() { + var v8stackFramePattern = /^\s*at\s*/; + var v8stackFormatter = function(stack, error) { + ASSERT(error !== null); + + if (typeof stack === "string") return stack; + + if (error.name !== undefined && + error.message !== undefined) { + return error.toString(); + } + return formatNonError(error); + }; + + //V8 + if (typeof Error.stackTraceLimit === "number" && + typeof Error.captureStackTrace === "function") { + Error.stackTraceLimit += 6; + stackFramePattern = v8stackFramePattern; + formatStack = v8stackFormatter; + var captureStackTrace = Error.captureStackTrace; + + // For node + shouldIgnore = function(line) { + return bluebirdFramePattern.test(line); + }; + return function(receiver, ignoreUntil) { + Error.stackTraceLimit += 6; + captureStackTrace(receiver, ignoreUntil); + Error.stackTraceLimit -= 6; + }; + } + var err = new Error(); + + //SpiderMonkey + if (typeof err.stack === "string" && + err.stack.split("\n")[0].indexOf("stackDetection@") >= 0) { + stackFramePattern = /@/; + formatStack = v8stackFormatter; + indentStackFrames = true; + return function captureStackTrace(o) { + o.stack = new Error().stack; + }; + } + + var hasStackAfterThrow; + try { throw new Error(); } + catch(e) { + hasStackAfterThrow = ("stack" in e); + } + // IE 10+ + if (!("stack" in err) && hasStackAfterThrow && + typeof Error.stackTraceLimit === "number") { + stackFramePattern = v8stackFramePattern; + formatStack = v8stackFormatter; + return function captureStackTrace(o) { + Error.stackTraceLimit += 6; + try { throw new Error(); } + catch(e) { o.stack = e.stack; } + Error.stackTraceLimit -= 6; + }; + } + + formatStack = function(stack, error) { + if (typeof stack === "string") return stack; + + if ((typeof error === "object" || + typeof error === "function") && + error.name !== undefined && + error.message !== undefined) { + return error.toString(); + } + return formatNonError(error); + }; + + return null; + +})([]); + +if (typeof console !== "undefined" && typeof console.warn !== "undefined") { + printWarning = function (message) { + console.warn(message); + }; + if (util.isNode && process.stderr.isTTY) { + printWarning = function(message, isSoft) { + var color = isSoft ? "\u001b[33m" : "\u001b[31m"; + console.warn(color + message + "\u001b[0m\n"); + }; + } else if (!util.isNode && typeof (new Error().stack) === "string") { + printWarning = function(message, isSoft) { + console.warn("%c" + message, + isSoft ? "color: darkorange" : "color: red"); + }; + } +} + +var config = { + warnings: warnings, + longStackTraces: false, + cancellation: false, + monitoring: false, + asyncHooks: false +}; + +if (longStackTraces) Promise.longStackTraces(); + +return { + asyncHooks: function() { + return config.asyncHooks; + }, + longStackTraces: function() { + return config.longStackTraces; + }, + warnings: function() { + return config.warnings; + }, + cancellation: function() { + return config.cancellation; + }, + monitoring: function() { + return config.monitoring; + }, + propagateFromFunction: function() { + return propagateFromFunction; + }, + boundValueFunction: function() { + return boundValueFunction; + }, + checkForgottenReturns: checkForgottenReturns, + setBounds: setBounds, + warn: warn, + deprecated: deprecated, + CapturedTrace: CapturedTrace, + fireDomEvent: fireDomEvent, + fireGlobalEvent: fireGlobalEvent +}; +}; diff --git a/src/direct_resolve.js b/src/direct_resolve.js new file mode 100644 index 0000000..a890298 --- /dev/null +++ b/src/direct_resolve.js @@ -0,0 +1,46 @@ +"use strict"; +module.exports = function(Promise) { +function returner() { + return this.value; +} +function thrower() { + throw this.reason; +} + +Promise.prototype["return"] = +Promise.prototype.thenReturn = function (value) { + if (value instanceof Promise) value.suppressUnhandledRejections(); + return this._then( + returner, undefined, undefined, {value: value}, undefined); +}; + +Promise.prototype["throw"] = +Promise.prototype.thenThrow = function (reason) { + return this._then( + thrower, undefined, undefined, {reason: reason}, undefined); +}; + +Promise.prototype.catchThrow = function (reason) { + if (arguments.length <= 1) { + return this._then( + undefined, thrower, undefined, {reason: reason}, undefined); + } else { + var _reason = arguments[1]; + var handler = function() {throw _reason;}; + return this.caught(reason, handler); + } +}; + +Promise.prototype.catchReturn = function (value) { + if (arguments.length <= 1) { + if (value instanceof Promise) value.suppressUnhandledRejections(); + return this._then( + undefined, returner, undefined, {value: value}, undefined); + } else { + var _value = arguments[1]; + if (_value instanceof Promise) _value.suppressUnhandledRejections(); + var handler = function() {return _value;}; + return this.caught(value, handler); + } +}; +}; diff --git a/src/each.js b/src/each.js new file mode 100644 index 0000000..e4f3d05 --- /dev/null +++ b/src/each.js @@ -0,0 +1,30 @@ +"use strict"; +module.exports = function(Promise, INTERNAL) { +var PromiseReduce = Promise.reduce; +var PromiseAll = Promise.all; + +function promiseAllThis() { + return PromiseAll(this); +} + +function PromiseMapSeries(promises, fn) { + return PromiseReduce(promises, fn, INTERNAL, INTERNAL); +} + +Promise.prototype.each = function (fn) { + return PromiseReduce(this, fn, INTERNAL, 0) + ._then(promiseAllThis, undefined, undefined, this, undefined); +}; + +Promise.prototype.mapSeries = function (fn) { + return PromiseReduce(this, fn, INTERNAL, INTERNAL); +}; + +Promise.each = function (promises, fn) { + return PromiseReduce(promises, fn, INTERNAL, 0) + ._then(promiseAllThis, undefined, undefined, promises, undefined); +}; + +Promise.mapSeries = PromiseMapSeries; +}; + diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 0000000..7762216 --- /dev/null +++ b/src/errors.js @@ -0,0 +1,117 @@ +"use strict"; +var es5 = require("./es5"); +var Objectfreeze = es5.freeze; +var util = require("./util"); +var inherits = util.inherits; +var notEnumerableProp = util.notEnumerableProp; + +function subError(nameProperty, defaultMessage) { + function SubError(message) { + if (!(this instanceof SubError)) return new SubError(message); + notEnumerableProp(this, "message", + typeof message === "string" ? message : defaultMessage); + notEnumerableProp(this, "name", nameProperty); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } else { + Error.call(this); + } + } + inherits(SubError, Error); + return SubError; +} + +var _TypeError, _RangeError; +var Warning = subError("Warning", "warning"); +var CancellationError = subError("CancellationError", "cancellation error"); +var TimeoutError = subError("TimeoutError", "timeout error"); +var AggregateError = subError("AggregateError", "aggregate error"); +try { + _TypeError = TypeError; + _RangeError = RangeError; +} catch(e) { + _TypeError = subError("TypeError", "type error"); + _RangeError = subError("RangeError", "range error"); +} + +var methods = ("join pop push shift unshift slice filter forEach some " + + "every map indexOf lastIndexOf reduce reduceRight sort reverse").split(" "); + +for (var i = 0; i < methods.length; ++i) { + if (typeof Array.prototype[methods[i]] === "function") { + AggregateError.prototype[methods[i]] = Array.prototype[methods[i]]; + } +} + +es5.defineProperty(AggregateError.prototype, "length", { + value: 0, + configurable: false, + writable: true, + enumerable: true +}); +AggregateError.prototype[OPERATIONAL_ERROR_KEY] = true; +var level = 0; +AggregateError.prototype.toString = function() { + var indent = Array(level * 4 + 1).join(" "); + var ret = "\n" + indent + "AggregateError of:" + "\n"; + level++; + indent = Array(level * 4 + 1).join(" "); + for (var i = 0; i < this.length; ++i) { + var str = this[i] === this ? "[Circular AggregateError]" : this[i] + ""; + var lines = str.split("\n"); + for (var j = 0; j < lines.length; ++j) { + lines[j] = indent + lines[j]; + } + str = lines.join("\n"); + ret += str + "\n"; + } + level--; + return ret; +}; + +function OperationalError(message) { + if (!(this instanceof OperationalError)) + return new OperationalError(message); + notEnumerableProp(this, "name", "OperationalError"); + notEnumerableProp(this, "message", message); + this.cause = message; + this[OPERATIONAL_ERROR_KEY] = true; + + if (message instanceof Error) { + notEnumerableProp(this, "message", message.message); + notEnumerableProp(this, "stack", message.stack); + } else if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + +} +inherits(OperationalError, Error); + +//Ensure all copies of the library throw the same error types +var errorTypes = Error[BLUEBIRD_ERRORS]; +if (!errorTypes) { + errorTypes = Objectfreeze({ + CancellationError: CancellationError, + TimeoutError: TimeoutError, + OperationalError: OperationalError, + RejectionError: OperationalError, + AggregateError: AggregateError + }); + es5.defineProperty(Error, BLUEBIRD_ERRORS, { + value: errorTypes, + writable: false, + enumerable: false, + configurable: false + }); +} + +module.exports = { + Error: Error, + TypeError: _TypeError, + RangeError: _RangeError, + CancellationError: errorTypes.CancellationError, + OperationalError: errorTypes.OperationalError, + TimeoutError: errorTypes.TimeoutError, + AggregateError: errorTypes.AggregateError, + Warning: Warning +}; diff --git a/src/es5.js b/src/es5.js new file mode 100644 index 0000000..ea41d5a --- /dev/null +++ b/src/es5.js @@ -0,0 +1,80 @@ +var isES5 = (function(){ + "use strict"; + return this === undefined; +})(); + +if (isES5) { + module.exports = { + freeze: Object.freeze, + defineProperty: Object.defineProperty, + getDescriptor: Object.getOwnPropertyDescriptor, + keys: Object.keys, + names: Object.getOwnPropertyNames, + getPrototypeOf: Object.getPrototypeOf, + isArray: Array.isArray, + isES5: isES5, + propertyIsWritable: function(obj, prop) { + var descriptor = Object.getOwnPropertyDescriptor(obj, prop); + return !!(!descriptor || descriptor.writable || descriptor.set); + } + }; +} else { + var has = {}.hasOwnProperty; + var str = {}.toString; + var proto = {}.constructor.prototype; + + var ObjectKeys = function (o) { + var ret = []; + for (var key in o) { + if (has.call(o, key)) { + ret.push(key); + } + } + return ret; + }; + + var ObjectGetDescriptor = function(o, key) { + return {value: o[key]}; + }; + + var ObjectDefineProperty = function (o, key, desc) { + o[key] = desc.value; + return o; + }; + + var ObjectFreeze = function (obj) { + return obj; + }; + + var ObjectGetPrototypeOf = function (obj) { + try { + return Object(obj).constructor.prototype; + } + catch (e) { + return proto; + } + }; + + var ArrayIsArray = function (obj) { + try { + return str.call(obj) === "[object Array]"; + } + catch(e) { + return false; + } + }; + + module.exports = { + isArray: ArrayIsArray, + keys: ObjectKeys, + names: ObjectKeys, + defineProperty: ObjectDefineProperty, + getDescriptor: ObjectGetDescriptor, + freeze: ObjectFreeze, + getPrototypeOf: ObjectGetPrototypeOf, + isES5: isES5, + propertyIsWritable: function() { + return true; + } + }; +} diff --git a/src/filter.js b/src/filter.js new file mode 100644 index 0000000..ed57bf0 --- /dev/null +++ b/src/filter.js @@ -0,0 +1,12 @@ +"use strict"; +module.exports = function(Promise, INTERNAL) { +var PromiseMap = Promise.map; + +Promise.prototype.filter = function (fn, options) { + return PromiseMap(this, fn, options, INTERNAL); +}; + +Promise.filter = function (promises, fn, options) { + return PromiseMap(promises, fn, options, INTERNAL); +}; +}; diff --git a/src/finally.js b/src/finally.js new file mode 100644 index 0000000..f699573 --- /dev/null +++ b/src/finally.js @@ -0,0 +1,146 @@ +"use strict"; +module.exports = function(Promise, tryConvertToPromise, NEXT_FILTER) { +var util = require("./util"); +var CancellationError = Promise.CancellationError; +var errorObj = util.errorObj; +var catchFilter = require("./catch_filter")(NEXT_FILTER); + +function PassThroughHandlerContext(promise, type, handler) { + this.promise = promise; + this.type = type; + this.handler = handler; + this.called = false; + this.cancelPromise = null; +} + +PassThroughHandlerContext.prototype.isFinallyHandler = function() { + return this.type === FINALLY_TYPE; +}; + +function FinallyHandlerCancelReaction(finallyHandler) { + this.finallyHandler = finallyHandler; +} + +FinallyHandlerCancelReaction.prototype._resultCancelled = function() { + checkCancel(this.finallyHandler); +}; + +function checkCancel(ctx, reason) { + if (ctx.cancelPromise != null) { + if (arguments.length > 1) { + ctx.cancelPromise._reject(reason); + } else { + ctx.cancelPromise._cancel(); + } + ctx.cancelPromise = null; + return true; + } + return false; +} + +function succeed() { + return finallyHandler.call(this, this.promise._target()._settledValue()); +} +function fail(reason) { + if (checkCancel(this, reason)) return; + errorObj.e = reason; + return errorObj; +} +function finallyHandler(reasonOrValue) { + var promise = this.promise; + var handler = this.handler; + + if (!this.called) { + this.called = true; + var ret = this.isFinallyHandler() + ? handler.call(promise._boundValue()) + : handler.call(promise._boundValue(), reasonOrValue); + if (ret === NEXT_FILTER) { + return ret; + } else if (ret !== undefined) { + promise._setReturnedNonUndefined(); + var maybePromise = tryConvertToPromise(ret, promise); + if (maybePromise instanceof Promise) { + if (this.cancelPromise != null) { + if (maybePromise._isCancelled()) { + var reason = + new CancellationError(LATE_CANCELLATION_OBSERVER); + promise._attachExtraTrace(reason); + errorObj.e = reason; + return errorObj; + } else if (maybePromise.isPending()) { + maybePromise._attachCancellationCallback( + new FinallyHandlerCancelReaction(this)); + } + } + return maybePromise._then( + succeed, fail, undefined, this, undefined); + } + } + } + + if (promise.isRejected()) { + checkCancel(this); + errorObj.e = reasonOrValue; + return errorObj; + } else { + checkCancel(this); + return reasonOrValue; + } +} + +Promise.prototype._passThrough = function(handler, type, success, fail) { + if (typeof handler !== "function") return this.then(); + return this._then(success, + fail, + undefined, + new PassThroughHandlerContext(this, type, handler), + undefined); +}; + +Promise.prototype.lastly = +Promise.prototype["finally"] = function (handler) { + return this._passThrough(handler, + FINALLY_TYPE, + finallyHandler, + finallyHandler); +}; + + +Promise.prototype.tap = function (handler) { + return this._passThrough(handler, TAP_TYPE, finallyHandler); +}; + +Promise.prototype.tapCatch = function (handlerOrPredicate) { + var len = arguments.length; + if(len === 1) { + return this._passThrough(handlerOrPredicate, + TAP_TYPE, + undefined, + finallyHandler); + } else { + var catchInstances = new Array(len - 1), + j = 0, i; + for (i = 0; i < len - 1; ++i) { + var item = arguments[i]; + if (util.isObject(item)) { + catchInstances[j++] = item; + } else { + return Promise.reject(new TypeError( + "tapCatch statement predicate: " + + OBJECT_ERROR + util.classString(item) + )); + } + } + catchInstances.length = j; + var handler = arguments[i]; + return this._passThrough(catchFilter(catchInstances, handler, this), + TAP_TYPE, + undefined, + finallyHandler); + } + +}; + +return PassThroughHandlerContext; +}; diff --git a/src/generators.js b/src/generators.js new file mode 100644 index 0000000..2e6029c --- /dev/null +++ b/src/generators.js @@ -0,0 +1,230 @@ +"use strict"; +module.exports = function(Promise, + apiRejection, + INTERNAL, + tryConvertToPromise, + Proxyable, + debug) { +var errors = require("./errors"); +var TypeError = errors.TypeError; +var ASSERT = require("./assert"); +var util = require("./util"); +var errorObj = util.errorObj; +var tryCatch = util.tryCatch; +var yieldHandlers = []; + +function promiseFromYieldHandler(value, yieldHandlers, traceParent) { + for (var i = 0; i < yieldHandlers.length; ++i) { + traceParent._pushContext(); + var result = tryCatch(yieldHandlers[i])(value); + traceParent._popContext(); + if (result === errorObj) { + traceParent._pushContext(); + var ret = Promise.reject(errorObj.e); + traceParent._popContext(); + return ret; + } + var maybePromise = tryConvertToPromise(result, traceParent); + if (maybePromise instanceof Promise) return maybePromise; + } + return null; +} + +function PromiseSpawn(generatorFunction, receiver, yieldHandler, stack) { + if (debug.cancellation()) { + var internal = new Promise(INTERNAL); + var _finallyPromise = this._finallyPromise = new Promise(INTERNAL); + this._promise = internal.lastly(function() { + return _finallyPromise; + }); + internal._captureStackTrace(); + internal._setOnCancel(this); + } else { + var promise = this._promise = new Promise(INTERNAL); + promise._captureStackTrace(); + } + this._stack = stack; + this._generatorFunction = generatorFunction; + this._receiver = receiver; + this._generator = undefined; + this._yieldHandlers = typeof yieldHandler === "function" + ? [yieldHandler].concat(yieldHandlers) + : yieldHandlers; + this._yieldedPromise = null; + this._cancellationPhase = false; +} +util.inherits(PromiseSpawn, Proxyable); + +PromiseSpawn.prototype._isResolved = function() { + return this._promise === null; +}; + +PromiseSpawn.prototype._cleanup = function() { + this._promise = this._generator = null; + if (debug.cancellation() && this._finallyPromise !== null) { + this._finallyPromise._fulfill(); + this._finallyPromise = null; + } +}; + +PromiseSpawn.prototype._promiseCancelled = function() { + if (this._isResolved()) return; + var implementsReturn = typeof this._generator["return"] !== "undefined"; + + var result; + if (!implementsReturn) { + var reason = new Promise.CancellationError( + "generator .return() sentinel"); + Promise.coroutine.returnSentinel = reason; + this._promise._attachExtraTrace(reason); + this._promise._pushContext(); + result = tryCatch(this._generator["throw"]).call(this._generator, + reason); + this._promise._popContext(); + } else { + this._promise._pushContext(); + result = tryCatch(this._generator["return"]).call(this._generator, + undefined); + this._promise._popContext(); + } + this._cancellationPhase = true; + this._yieldedPromise = null; + this._continue(result); +}; + +PromiseSpawn.prototype._promiseFulfilled = function(value) { + this._yieldedPromise = null; + this._promise._pushContext(); + var result = tryCatch(this._generator.next).call(this._generator, value); + this._promise._popContext(); + this._continue(result); +}; + +PromiseSpawn.prototype._promiseRejected = function(reason) { + this._yieldedPromise = null; + this._promise._attachExtraTrace(reason); + this._promise._pushContext(); + var result = tryCatch(this._generator["throw"]) + .call(this._generator, reason); + this._promise._popContext(); + this._continue(result); +}; + +PromiseSpawn.prototype._resultCancelled = function() { + if (this._yieldedPromise instanceof Promise) { + var promise = this._yieldedPromise; + this._yieldedPromise = null; + promise.cancel(); + } +}; + +PromiseSpawn.prototype.promise = function () { + return this._promise; +}; + +PromiseSpawn.prototype._run = function () { + this._generator = this._generatorFunction.call(this._receiver); + this._receiver = + this._generatorFunction = undefined; + this._promiseFulfilled(undefined); +}; + +PromiseSpawn.prototype._continue = function (result) { + ASSERT(this._yieldedPromise == null); + var promise = this._promise; + if (result === errorObj) { + this._cleanup(); + if (this._cancellationPhase) { + return promise.cancel(); + } else { + return promise._rejectCallback(result.e, false); + } + } + + var value = result.value; + if (result.done === true) { + this._cleanup(); + if (this._cancellationPhase) { + return promise.cancel(); + } else { + return promise._resolveCallback(value); + } + } else { + var maybePromise = tryConvertToPromise(value, this._promise); + if (!(maybePromise instanceof Promise)) { + maybePromise = + promiseFromYieldHandler(maybePromise, + this._yieldHandlers, + this._promise); + ASSERT(maybePromise === null || maybePromise instanceof Promise); + if (maybePromise === null) { + this._promiseRejected( + new TypeError( + YIELDED_NON_PROMISE_ERROR.replace("%s", String(value)) + + FROM_COROUTINE_CREATED_AT + + this._stack.split("\n").slice(1, -7).join("\n") + ) + ); + return; + } + } + maybePromise = maybePromise._target(); + var bitField = maybePromise._bitField; + USE(bitField); + if (BIT_FIELD_CHECK(IS_PENDING_AND_WAITING_NEG)) { + this._yieldedPromise = maybePromise; + maybePromise._proxy(this, null); + } else if (BIT_FIELD_CHECK(IS_FULFILLED)) { + Promise._async.invoke( + this._promiseFulfilled, this, maybePromise._value() + ); + } else if (BIT_FIELD_CHECK(IS_REJECTED)) { + Promise._async.invoke( + this._promiseRejected, this, maybePromise._reason() + ); + } else { + this._promiseCancelled(); + } + } +}; + +Promise.coroutine = function (generatorFunction, options) { + //Throw synchronously because Promise.coroutine is semantically + //something you call at "compile time" to annotate static functions + if (typeof generatorFunction !== "function") { + throw new TypeError(NOT_GENERATOR_ERROR); + } + var yieldHandler = Object(options).yieldHandler; + var PromiseSpawn$ = PromiseSpawn; + var stack = new Error().stack; + return function () { + var generator = generatorFunction.apply(this, arguments); + var spawn = new PromiseSpawn$(undefined, undefined, yieldHandler, + stack); + var ret = spawn.promise(); + spawn._generator = generator; + spawn._promiseFulfilled(undefined); + return ret; + }; +}; + +Promise.coroutine.addYieldHandler = function(fn) { + if (typeof fn !== "function") { + throw new TypeError(FUNCTION_ERROR + util.classString(fn)); + } + yieldHandlers.push(fn); +}; + +Promise.spawn = function (generatorFunction) { + debug.deprecated("Promise.spawn()", "Promise.coroutine()"); + //Return rejected promise because Promise.spawn is semantically + //something that will be called at runtime with possibly dynamic values + if (typeof generatorFunction !== "function") { + return apiRejection(NOT_GENERATOR_ERROR); + } + var spawn = new PromiseSpawn(generatorFunction, this); + var ret = spawn.promise(); + spawn._run(Promise.spawn); + return ret; +}; +}; diff --git a/src/join.js b/src/join.js new file mode 100644 index 0000000..b34af43 --- /dev/null +++ b/src/join.js @@ -0,0 +1,165 @@ +"use strict"; +module.exports = +function(Promise, PromiseArray, tryConvertToPromise, INTERNAL, async) { +var util = require("./util"); +var canEvaluate = util.canEvaluate; +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; +var reject; + +if (!__BROWSER__) { +if (canEvaluate) { + var thenCallback = function(i) { + return new Function("value", "holder", " \n\ + 'use strict'; \n\ + holder.pIndex = value; \n\ + holder.checkFulfillment(this); \n\ + ".replace(/Index/g, i)); + }; + + var promiseSetter = function(i) { + return new Function("promise", "holder", " \n\ + 'use strict'; \n\ + holder.pIndex = promise; \n\ + ".replace(/Index/g, i)); + }; + + var generateHolderClass = function(total) { + var props = new Array(total); + for (var i = 0; i < props.length; ++i) { + props[i] = "this.p" + (i+1); + } + var assignment = props.join(" = ") + " = null;"; + var cancellationCode= "var promise;\n" + props.map(function(prop) { + return " \n\ + promise = " + prop + "; \n\ + if (promise instanceof Promise) { \n\ + promise.cancel(); \n\ + } \n\ + "; + }).join("\n"); + var passedArguments = props.join(", "); + var name = "Holder$" + total; + + + var code = "return function(tryCatch, errorObj, Promise, async) { \n\ + 'use strict'; \n\ + function [TheName](fn) { \n\ + [TheProperties] \n\ + this.fn = fn; \n\ + this.asyncNeeded = true; \n\ + this.now = 0; \n\ + } \n\ + \n\ + [TheName].prototype._callFunction = function(promise) { \n\ + promise._pushContext(); \n\ + var ret = tryCatch(this.fn)([ThePassedArguments]); \n\ + promise._popContext(); \n\ + if (ret === errorObj) { \n\ + promise._rejectCallback(ret.e, false); \n\ + } else { \n\ + promise._resolveCallback(ret); \n\ + } \n\ + }; \n\ + \n\ + [TheName].prototype.checkFulfillment = function(promise) { \n\ + var now = ++this.now; \n\ + if (now === [TheTotal]) { \n\ + if (this.asyncNeeded) { \n\ + async.invoke(this._callFunction, this, promise); \n\ + } else { \n\ + this._callFunction(promise); \n\ + } \n\ + \n\ + } \n\ + }; \n\ + \n\ + [TheName].prototype._resultCancelled = function() { \n\ + [CancellationCode] \n\ + }; \n\ + \n\ + return [TheName]; \n\ + }(tryCatch, errorObj, Promise, async); \n\ + "; + + code = code.replace(/\[TheName\]/g, name) + .replace(/\[TheTotal\]/g, total) + .replace(/\[ThePassedArguments\]/g, passedArguments) + .replace(/\[TheProperties\]/g, assignment) + .replace(/\[CancellationCode\]/g, cancellationCode); + + return new Function("tryCatch", "errorObj", "Promise", "async", code) + (tryCatch, errorObj, Promise, async); + }; + + var holderClasses = []; + var thenCallbacks = []; + var promiseSetters = []; + + for (var i = 0; i < GENERATED_CLASS_COUNT; ++i) { + holderClasses.push(generateHolderClass(i + 1)); + thenCallbacks.push(thenCallback(i + 1)); + promiseSetters.push(promiseSetter(i + 1)); + } + + reject = function (reason) { + this._reject(reason); + }; +}} + +Promise.join = function () { + var last = arguments.length - 1; + var fn; + if (last > 0 && typeof arguments[last] === "function") { + fn = arguments[last]; + if (!__BROWSER__) { + if (last <= GENERATED_CLASS_COUNT && canEvaluate) { + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + var HolderClass = holderClasses[last - 1]; + var holder = new HolderClass(fn); + var callbacks = thenCallbacks; + + for (var i = 0; i < last; ++i) { + var maybePromise = tryConvertToPromise(arguments[i], ret); + if (maybePromise instanceof Promise) { + maybePromise = maybePromise._target(); + var bitField = maybePromise._bitField; + USE(bitField); + if (BIT_FIELD_CHECK(IS_PENDING_AND_WAITING_NEG)) { + maybePromise._then(callbacks[i], reject, + undefined, ret, holder); + promiseSetters[i](maybePromise, holder); + holder.asyncNeeded = false; + } else if (BIT_FIELD_CHECK(IS_FULFILLED)) { + callbacks[i].call(ret, + maybePromise._value(), holder); + } else if (BIT_FIELD_CHECK(IS_REJECTED)) { + ret._reject(maybePromise._reason()); + } else { + ret._cancel(); + } + } else { + callbacks[i].call(ret, maybePromise, holder); + } + } + + if (!ret._isFateSealed()) { + if (holder.asyncNeeded) { + var context = Promise._getContext(); + holder.fn = util.contextBind(context, holder.fn); + } + ret._setAsyncGuaranteed(); + ret._setOnCancel(holder); + } + return ret; + } + } + } + INLINE_SLICE(args, arguments); + if (fn) args.pop(); + var ret = new PromiseArray(args).promise(); + return fn !== undefined ? ret.spread(fn) : ret; +}; + +}; diff --git a/src/map.js b/src/map.js new file mode 100644 index 0000000..793210d --- /dev/null +++ b/src/map.js @@ -0,0 +1,192 @@ +"use strict"; +module.exports = function(Promise, + PromiseArray, + apiRejection, + tryConvertToPromise, + INTERNAL, + debug) { +var ASSERT = require("./assert"); +var util = require("./util"); +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; +var async = Promise._async; + +function MappingPromiseArray(promises, fn, limit, _filter) { + this.constructor$(promises); + this._promise._captureStackTrace(); + var context = Promise._getContext(); + this._callback = util.contextBind(context, fn); + this._preservedValues = _filter === INTERNAL + ? new Array(this.length()) + : null; + this._limit = limit; + this._inFlight = 0; + this._queue = []; + async.invoke(this._asyncInit, this, undefined); + if (util.isArray(promises)) { + for (var i = 0; i < promises.length; ++i) { + var maybePromise = promises[i]; + if (maybePromise instanceof Promise) { + maybePromise.suppressUnhandledRejections(); + } + } + } +} +util.inherits(MappingPromiseArray, PromiseArray); + +MappingPromiseArray.prototype._asyncInit = function() { + this._init$(undefined, RESOLVE_ARRAY); +}; + +// The following hack is required because the super constructor +// might call promiseFulfilled before this.callback = fn is set +// +// The super constructor call must always be first so that fields +// are initialized in the same order so that the sub-class instances +// will share same memory layout as the super class instances + +// Override +MappingPromiseArray.prototype._init = function () {}; + +// Override +MappingPromiseArray.prototype._promiseFulfilled = function (value, index) { + ASSERT(!this._isResolved()); + var values = this._values; + var length = this.length(); + var preservedValues = this._preservedValues; + var limit = this._limit; + + // Callback has been called for this index if it's negative + if (index < 0) { + // Restore the actual index value + index = (index * -1) - 1; + values[index] = value; + if (limit >= 1) { + this._inFlight--; + this._drainQueue(); + if (this._isResolved()) return true; + } + } else { + if (limit >= 1 && this._inFlight >= limit) { + values[index] = value; + this._queue.push(index); + return false; + } + if (preservedValues !== null) preservedValues[index] = value; + + var promise = this._promise; + var callback = this._callback; + var receiver = promise._boundValue(); + promise._pushContext(); + var ret = tryCatch(callback).call(receiver, value, index, length); + var promiseCreated = promise._popContext(); + debug.checkForgottenReturns( + ret, + promiseCreated, + preservedValues !== null ? "Promise.filter" : "Promise.map", + promise + ); + if (ret === errorObj) { + this._reject(ret.e); + return true; + } + + // If the mapper function returned a promise we simply reuse + // The MappingPromiseArray as a PromiseArray for round 2. + // To mark an index as "round 2" its inverted by adding +1 and + // multiplying by -1 + var maybePromise = tryConvertToPromise(ret, this._promise); + if (maybePromise instanceof Promise) { + maybePromise = maybePromise._target(); + var bitField = maybePromise._bitField; + USE(bitField); + if (BIT_FIELD_CHECK(IS_PENDING_AND_WAITING_NEG)) { + if (limit >= 1) this._inFlight++; + values[index] = maybePromise; + maybePromise._proxy(this, (index + 1) * -1); + return false; + } else if (BIT_FIELD_CHECK(IS_FULFILLED)) { + ret = maybePromise._value(); + } else if (BIT_FIELD_CHECK(IS_REJECTED)) { + this._reject(maybePromise._reason()); + return true; + } else { + this._cancel(); + return true; + } + } + values[index] = ret; + } + var totalResolved = ++this._totalResolved; + if (totalResolved >= length) { + if (preservedValues !== null) { + this._filter(values, preservedValues); + } else { + this._resolve(values); + } + return true; + } + return false; +}; + +MappingPromiseArray.prototype._drainQueue = function () { + var queue = this._queue; + var limit = this._limit; + var values = this._values; + while (queue.length > 0 && this._inFlight < limit) { + if (this._isResolved()) return; + var index = queue.pop(); + this._promiseFulfilled(values[index], index); + } +}; + +MappingPromiseArray.prototype._filter = function (booleans, values) { + var len = values.length; + var ret = new Array(len); + var j = 0; + for (var i = 0; i < len; ++i) { + if (booleans[i]) ret[j++] = values[i]; + } + ret.length = j; + this._resolve(ret); +}; + +MappingPromiseArray.prototype.preservedValues = function () { + return this._preservedValues; +}; + +function map(promises, fn, options, _filter) { + if (typeof fn !== "function") { + return apiRejection(FUNCTION_ERROR + util.classString(fn)); + } + + var limit = 0; + if (options !== undefined) { + if (typeof options === "object" && options !== null) { + if (typeof options.concurrency !== "number") { + return Promise.reject( + new TypeError("'concurrency' must be a number but it is " + + util.classString(options.concurrency))); + } + limit = options.concurrency; + } else { + return Promise.reject(new TypeError( + "options argument must be an object but it is " + + util.classString(options))); + } + } + limit = typeof limit === "number" && + isFinite(limit) && limit >= 1 ? limit : 0; + return new MappingPromiseArray(promises, fn, limit, _filter).promise(); +} + +Promise.prototype.map = function (fn, options) { + return map(this, fn, options, null); +}; + +Promise.map = function (promises, fn, options, _filter) { + return map(promises, fn, options, _filter); +}; + + +}; diff --git a/src/method.js b/src/method.js new file mode 100644 index 0000000..5255f36 --- /dev/null +++ b/src/method.js @@ -0,0 +1,57 @@ +"use strict"; +module.exports = +function(Promise, INTERNAL, tryConvertToPromise, apiRejection, debug) { +var util = require("./util"); +var ASSERT = require("./assert"); +var tryCatch = util.tryCatch; + +Promise.method = function (fn) { + if (typeof fn !== "function") { + throw new Promise.TypeError(FUNCTION_ERROR + util.classString(fn)); + } + return function () { + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + ret._pushContext(); + var value = tryCatch(fn).apply(this, arguments); + var promiseCreated = ret._popContext(); + debug.checkForgottenReturns( + value, promiseCreated, "Promise.method", ret); + ret._resolveFromSyncValue(value); + return ret; + }; +}; + +Promise.attempt = Promise["try"] = function (fn) { + if (typeof fn !== "function") { + return apiRejection(FUNCTION_ERROR + util.classString(fn)); + } + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + ret._pushContext(); + var value; + if (arguments.length > 1) { + debug.deprecated("calling Promise.try with more than 1 argument"); + var arg = arguments[1]; + var ctx = arguments[2]; + value = util.isArray(arg) ? tryCatch(fn).apply(ctx, arg) + : tryCatch(fn).call(ctx, arg); + } else { + value = tryCatch(fn)(); + } + var promiseCreated = ret._popContext(); + debug.checkForgottenReturns( + value, promiseCreated, "Promise.try", ret); + ret._resolveFromSyncValue(value); + return ret; +}; + +Promise.prototype._resolveFromSyncValue = function (value) { + ASSERT(!this._isFollowing()); + if (value === util.errorObj) { + this._rejectCallback(value.e, false); + } else { + this._resolveCallback(value, true); + } +}; +}; diff --git a/src/nodeback.js b/src/nodeback.js new file mode 100644 index 0000000..e753c31 --- /dev/null +++ b/src/nodeback.js @@ -0,0 +1,51 @@ +"use strict"; +var util = require("./util"); +var maybeWrapAsError = util.maybeWrapAsError; +var errors = require("./errors"); +var OperationalError = errors.OperationalError; +var es5 = require("./es5"); + +function isUntypedError(obj) { + return obj instanceof Error && + es5.getPrototypeOf(obj) === Error.prototype; +} + +var rErrorKey = /^(?:name|message|stack|cause)$/; +function wrapAsOperationalError(obj) { + var ret; + if (isUntypedError(obj)) { + ret = new OperationalError(obj); + ret.name = obj.name; + ret.message = obj.message; + ret.stack = obj.stack; + var keys = es5.keys(obj); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + if (!rErrorKey.test(key)) { + ret[key] = obj[key]; + } + } + return ret; + } + util.markAsOriginatingFromRejection(obj); + return obj; +} + +function nodebackForPromise(promise, multiArgs) { + return function(err, value) { + if (promise === null) return; + if (err) { + var wrapped = wrapAsOperationalError(maybeWrapAsError(err)); + promise._attachExtraTrace(wrapped); + promise._reject(wrapped); + } else if (!multiArgs) { + promise._fulfill(value); + } else { + INLINE_SLICE(args, arguments, 1); + promise._fulfill(args); + } + promise = null; + }; +} + +module.exports = nodebackForPromise; diff --git a/src/nodeify.js b/src/nodeify.js new file mode 100644 index 0000000..787efce --- /dev/null +++ b/src/nodeify.js @@ -0,0 +1,62 @@ +"use strict"; +module.exports = function(Promise) { +var util = require("./util"); +var async = Promise._async; +var ASSERT = require("./assert"); +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; + +function spreadAdapter(val, nodeback) { + var promise = this; + if (!util.isArray(val)) return successAdapter.call(promise, val, nodeback); + var ret = + tryCatch(nodeback).apply(promise._boundValue(), [null].concat(val)); + if (ret === errorObj) { + async.throwLater(ret.e); + } +} + +function successAdapter(val, nodeback) { + var promise = this; + var receiver = promise._boundValue(); + ASSERT(typeof nodeback == "function"); + var ret = val === undefined + ? tryCatch(nodeback).call(receiver, null) + : tryCatch(nodeback).call(receiver, null, val); + if (ret === errorObj) { + async.throwLater(ret.e); + } +} +function errorAdapter(reason, nodeback) { + var promise = this; + if (!reason) { + var newReason = new Error(reason + ""); + newReason.cause = reason; + reason = newReason; + ASSERT(!!reason); + } + ASSERT(typeof nodeback == "function"); + var ret = tryCatch(nodeback).call(promise._boundValue(), reason); + if (ret === errorObj) { + async.throwLater(ret.e); + } +} + +Promise.prototype.asCallback = Promise.prototype.nodeify = function (nodeback, + options) { + if (typeof nodeback == "function") { + var adapter = successAdapter; + if (options !== undefined && Object(options).spread) { + adapter = spreadAdapter; + } + this._then( + adapter, + errorAdapter, + undefined, + this, + nodeback + ); + } + return this; +}; +}; diff --git a/src/promise.js b/src/promise.js new file mode 100644 index 0000000..f511f48 --- /dev/null +++ b/src/promise.js @@ -0,0 +1,827 @@ +"use strict"; +module.exports = function() { +var makeSelfResolutionError = function () { + return new TypeError(CIRCULAR_RESOLUTION_ERROR); +}; +var reflectHandler = function() { + return new Promise.PromiseInspection(this._target()); +}; +var apiRejection = function(msg) { + return Promise.reject(new TypeError(msg)); +}; +function Proxyable() {} +var UNDEFINED_BINDING = {}; +var ASSERT = require("./assert"); +var util = require("./util"); +util.setReflectHandler(reflectHandler); + +var getDomain = function() { + var domain = process.domain; + if (domain === undefined) { + return null; + } + return domain; +}; +var getContextDefault = function() { + return null; +}; +var getContextDomain = function() { + return { + domain: getDomain(), + async: null + }; +}; +var AsyncResource = util.isNode && util.nodeSupportsAsyncResource ? + require("async_hooks").AsyncResource : null; +var getContextAsyncHooks = function() { + return { + domain: getDomain(), + async: new AsyncResource("Bluebird::Promise") + }; +}; +var getContext = util.isNode ? getContextDomain : getContextDefault; +util.notEnumerableProp(Promise, "_getContext", getContext); +var enableAsyncHooks = function() { + getContext = getContextAsyncHooks; + util.notEnumerableProp(Promise, "_getContext", getContextAsyncHooks); +}; +var disableAsyncHooks = function() { + getContext = getContextDomain; + util.notEnumerableProp(Promise, "_getContext", getContextDomain); +}; + +var es5 = require("./es5"); +var Async = require("./async"); +var async = new Async(); +es5.defineProperty(Promise, "_async", {value: async}); +var errors = require("./errors"); +var TypeError = Promise.TypeError = errors.TypeError; +Promise.RangeError = errors.RangeError; +var CancellationError = Promise.CancellationError = errors.CancellationError; +Promise.TimeoutError = errors.TimeoutError; +Promise.OperationalError = errors.OperationalError; +Promise.RejectionError = errors.OperationalError; +Promise.AggregateError = errors.AggregateError; +var INTERNAL = function(){}; +var APPLY = {}; +var NEXT_FILTER = {}; +var tryConvertToPromise = require("./thenables")(Promise, INTERNAL); +var PromiseArray = + require("./promise_array")(Promise, INTERNAL, + tryConvertToPromise, apiRejection, Proxyable); +var Context = require("./context")(Promise); + /*jshint unused:false*/ +var createContext = Context.create; + +var debug = require("./debuggability")(Promise, Context, + enableAsyncHooks, disableAsyncHooks); +var CapturedTrace = debug.CapturedTrace; +var PassThroughHandlerContext = + require("./finally")(Promise, tryConvertToPromise, NEXT_FILTER); +var catchFilter = require("./catch_filter")(NEXT_FILTER); +var nodebackForPromise = require("./nodeback"); +var errorObj = util.errorObj; +var tryCatch = util.tryCatch; +function check(self, executor) { + if (self == null || self.constructor !== Promise) { + throw new TypeError(CONSTRUCT_ERROR_INVOCATION); + } + if (typeof executor !== "function") { + throw new TypeError(FUNCTION_ERROR + util.classString(executor)); + } + +} + +function Promise(executor) { + if (executor !== INTERNAL) { + check(this, executor); + } + this._bitField = NO_STATE; + this._fulfillmentHandler0 = undefined; + this._rejectionHandler0 = undefined; + this._promise0 = undefined; + this._receiver0 = undefined; + this._resolveFromExecutor(executor); + this._promiseCreated(); + this._fireEvent("promiseCreated", this); +} + +Promise.prototype.toString = function () { + return "[object Promise]"; +}; + +Promise.prototype.caught = Promise.prototype["catch"] = function (fn) { + var len = arguments.length; + if (len > 1) { + var catchInstances = new Array(len - 1), + j = 0, i; + for (i = 0; i < len - 1; ++i) { + var item = arguments[i]; + if (util.isObject(item)) { + catchInstances[j++] = item; + } else { + return apiRejection("Catch statement predicate: " + + OBJECT_ERROR + util.classString(item)); + } + } + catchInstances.length = j; + fn = arguments[i]; + + if (typeof fn !== "function") { + throw new TypeError("The last argument to .catch() " + + "must be a function, got " + util.toString(fn)); + } + return this.then(undefined, catchFilter(catchInstances, fn, this)); + } + return this.then(undefined, fn); +}; + +Promise.prototype.reflect = function () { + return this._then(reflectHandler, + reflectHandler, undefined, this, undefined); +}; + +Promise.prototype.then = function (didFulfill, didReject) { + if (debug.warnings() && arguments.length > 0 && + typeof didFulfill !== "function" && + typeof didReject !== "function") { + var msg = ".then() only accepts functions but was passed: " + + util.classString(didFulfill); + if (arguments.length > 1) { + msg += ", " + util.classString(didReject); + } + this._warn(msg); + } + return this._then(didFulfill, didReject, undefined, undefined, undefined); +}; + +Promise.prototype.done = function (didFulfill, didReject) { + var promise = + this._then(didFulfill, didReject, undefined, undefined, undefined); + promise._setIsFinal(); +}; + +Promise.prototype.spread = function (fn) { + if (typeof fn !== "function") { + return apiRejection(FUNCTION_ERROR + util.classString(fn)); + } + return this.all()._then(fn, undefined, undefined, APPLY, undefined); +}; + +Promise.prototype.toJSON = function () { + var ret = { + isFulfilled: false, + isRejected: false, + fulfillmentValue: undefined, + rejectionReason: undefined + }; + if (this.isFulfilled()) { + ret.fulfillmentValue = this.value(); + ret.isFulfilled = true; + } else if (this.isRejected()) { + ret.rejectionReason = this.reason(); + ret.isRejected = true; + } + return ret; +}; + +Promise.prototype.all = function () { + if (arguments.length > 0) { + this._warn(".all() was passed arguments but it does not take any"); + } + return new PromiseArray(this).promise(); +}; + +Promise.prototype.error = function (fn) { + return this.caught(util.originatesFromRejection, fn); +}; + +Promise.getNewLibraryCopy = module.exports; + +Promise.is = function (val) { + return val instanceof Promise; +}; + +Promise.fromNode = Promise.fromCallback = function(fn) { + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + var multiArgs = arguments.length > 1 ? !!Object(arguments[1]).multiArgs + : false; + var result = tryCatch(fn)(nodebackForPromise(ret, multiArgs)); + if (result === errorObj) { + ret._rejectCallback(result.e, true); + } + if (!ret._isFateSealed()) ret._setAsyncGuaranteed(); + return ret; +}; + +Promise.all = function (promises) { + return new PromiseArray(promises).promise(); +}; + +Promise.cast = function (obj) { + var ret = tryConvertToPromise(obj); + if (!(ret instanceof Promise)) { + ret = new Promise(INTERNAL); + ret._captureStackTrace(); + ret._setFulfilled(); + ret._rejectionHandler0 = obj; + } + return ret; +}; + +Promise.resolve = Promise.fulfilled = Promise.cast; + +Promise.reject = Promise.rejected = function (reason) { + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + ret._rejectCallback(reason, true); + return ret; +}; + +Promise.setScheduler = function(fn) { + if (typeof fn !== "function") { + throw new TypeError(FUNCTION_ERROR + util.classString(fn)); + } + return async.setScheduler(fn); +}; + +Promise.prototype._then = function ( + didFulfill, + didReject, + _, // For fast-cast compatibility between bluebird versions + receiver, + internalData +) { + ASSERT(arguments.length === 5); + var haveInternalData = internalData !== undefined; + var promise = haveInternalData ? internalData : new Promise(INTERNAL); + var target = this._target(); + var bitField = target._bitField; + + if (!haveInternalData) { + promise._propagateFrom(this, PROPAGATE_ALL); + promise._captureStackTrace(); + if (receiver === undefined && + BIT_FIELD_CHECK(IS_BOUND, this._bitField)) { + if (!BIT_FIELD_CHECK(IS_PENDING_AND_WAITING_NEG)) { + receiver = this._boundValue(); + } else { + receiver = target === this ? undefined : this._boundTo; + } + } + this._fireEvent("promiseChained", this, promise); + } + + var context = getContext(); + if (!BIT_FIELD_CHECK(IS_PENDING_AND_WAITING_NEG)) { + var handler, value, settler = target._settlePromiseCtx; + if (BIT_FIELD_CHECK(IS_FULFILLED)) { + value = target._rejectionHandler0; + handler = didFulfill; + } else if (BIT_FIELD_CHECK(IS_REJECTED)) { + value = target._fulfillmentHandler0; + handler = didReject; + target._unsetRejectionIsUnhandled(); + } else { + settler = target._settlePromiseLateCancellationObserver; + value = new CancellationError(LATE_CANCELLATION_OBSERVER); + target._attachExtraTrace(value); + handler = didReject; + } + + async.invoke(settler, target, { + handler: util.contextBind(context, handler), + promise: promise, + receiver: receiver, + value: value + }); + } else { + target._addCallbacks(didFulfill, didReject, promise, + receiver, context); + } + + return promise; +}; + +Promise.prototype._length = function () { + ASSERT(arguments.length === 0); + return this._bitField & LENGTH_MASK; +}; + +Promise.prototype._isFateSealed = function () { + return (this._bitField & IS_FATE_SEALED) !== 0; +}; + +Promise.prototype._isFollowing = function () { + return (this._bitField & IS_FOLLOWING) === IS_FOLLOWING; +}; + +Promise.prototype._setLength = function (len) { + this._bitField = (this._bitField & LENGTH_CLEAR_MASK) | + (len & LENGTH_MASK); +}; + +Promise.prototype._setFulfilled = function () { + this._bitField = this._bitField | IS_FULFILLED; + this._fireEvent("promiseFulfilled", this); +}; + +Promise.prototype._setRejected = function () { + this._bitField = this._bitField | IS_REJECTED; + this._fireEvent("promiseRejected", this); +}; + +Promise.prototype._setFollowing = function () { + this._bitField = this._bitField | IS_FOLLOWING; + this._fireEvent("promiseResolved", this); +}; + +Promise.prototype._setIsFinal = function () { + this._bitField = this._bitField | IS_FINAL; +}; + +Promise.prototype._isFinal = function () { + return (this._bitField & IS_FINAL) > 0; +}; + +Promise.prototype._unsetCancelled = function() { + this._bitField = this._bitField & (~IS_CANCELLED); +}; + +Promise.prototype._setCancelled = function() { + this._bitField = this._bitField | IS_CANCELLED; + this._fireEvent("promiseCancelled", this); +}; + +Promise.prototype._setWillBeCancelled = function() { + this._bitField = this._bitField | WILL_BE_CANCELLED; +}; + +Promise.prototype._setAsyncGuaranteed = function() { + if (async.hasCustomScheduler()) return; + var bitField = this._bitField; + this._bitField = bitField | + (((bitField & NO_ASYNC_GUARANTEE) >> ASYNC_GUARANTEE_SHIFT) ^ + IS_ASYNC_GUARANTEED); +}; + +Promise.prototype._setNoAsyncGuarantee = function() { + this._bitField = (this._bitField | NO_ASYNC_GUARANTEE) & + (~IS_ASYNC_GUARANTEED); +}; + +Promise.prototype._receiverAt = function (index) { + ASSERT(!this._isFollowing()); + var ret = index === 0 ? this._receiver0 : this[ + index * CALLBACK_SIZE - CALLBACK_SIZE + CALLBACK_RECEIVER_OFFSET]; + //Only use the bound value when not calling internal methods + if (ret === UNDEFINED_BINDING) { + return undefined; + } else if (ret === undefined && this._isBound()) { + return this._boundValue(); + } + return ret; +}; + +Promise.prototype._promiseAt = function (index) { + ASSERT(index > 0); + ASSERT(!this._isFollowing()); + return this[ + index * CALLBACK_SIZE - CALLBACK_SIZE + CALLBACK_PROMISE_OFFSET]; +}; + +Promise.prototype._fulfillmentHandlerAt = function (index) { + ASSERT(!this._isFollowing()); + ASSERT(index > 0); + return this[ + index * CALLBACK_SIZE - CALLBACK_SIZE + CALLBACK_FULFILL_OFFSET]; +}; + +Promise.prototype._rejectionHandlerAt = function (index) { + ASSERT(!this._isFollowing()); + ASSERT(index > 0); + return this[ + index * CALLBACK_SIZE - CALLBACK_SIZE + CALLBACK_REJECT_OFFSET]; +}; + +Promise.prototype._boundValue = function() {}; + +Promise.prototype._migrateCallback0 = function (follower) { + var bitField = follower._bitField; + var fulfill = follower._fulfillmentHandler0; + var reject = follower._rejectionHandler0; + var promise = follower._promise0; + var receiver = follower._receiverAt(0); + if (receiver === undefined) receiver = UNDEFINED_BINDING; + this._addCallbacks(fulfill, reject, promise, receiver, null); +}; + +Promise.prototype._migrateCallbackAt = function (follower, index) { + ASSERT(index > 0); + var fulfill = follower._fulfillmentHandlerAt(index); + var reject = follower._rejectionHandlerAt(index); + var promise = follower._promiseAt(index); + var receiver = follower._receiverAt(index); + if (receiver === undefined) receiver = UNDEFINED_BINDING; + this._addCallbacks(fulfill, reject, promise, receiver, null); +}; + +Promise.prototype._addCallbacks = function ( + fulfill, + reject, + promise, + receiver, + context +) { + ASSERT(typeof context === "object"); + ASSERT(!this._isFateSealed()); + ASSERT(!this._isFollowing()); + var index = this._length(); + + if (index >= MAX_LENGTH - CALLBACK_SIZE) { + index = 0; + this._setLength(0); + } + + if (index === 0) { + ASSERT(this._promise0 === undefined); + ASSERT(this._receiver0 === undefined); + ASSERT(this._fulfillmentHandler0 === undefined); + ASSERT(this._rejectionHandler0 === undefined); + + this._promise0 = promise; + this._receiver0 = receiver; + if (typeof fulfill === "function") { + this._fulfillmentHandler0 = util.contextBind(context, fulfill); + } + if (typeof reject === "function") { + this._rejectionHandler0 = util.contextBind(context, reject); + } + } else { + ASSERT(this[base + CALLBACK_PROMISE_OFFSET] === undefined); + ASSERT(this[base + CALLBACK_RECEIVER_OFFSET] === undefined); + ASSERT(this[base + CALLBACK_FULFILL_OFFSET] === undefined); + ASSERT(this[base + CALLBACK_REJECT_OFFSET] === undefined); + var base = index * CALLBACK_SIZE - CALLBACK_SIZE; + this[base + CALLBACK_PROMISE_OFFSET] = promise; + this[base + CALLBACK_RECEIVER_OFFSET] = receiver; + if (typeof fulfill === "function") { + this[base + CALLBACK_FULFILL_OFFSET] = + util.contextBind(context, fulfill); + } + if (typeof reject === "function") { + this[base + CALLBACK_REJECT_OFFSET] = + util.contextBind(context, reject); + } + } + this._setLength(index + 1); + return index; +}; + +Promise.prototype._proxy = function (proxyable, arg) { + ASSERT(proxyable instanceof Proxyable); + ASSERT(!(arg instanceof Promise)); + ASSERT(!this._isFollowing()); + ASSERT(arguments.length === 2); + ASSERT(!this._isFateSealed()); + this._addCallbacks(undefined, undefined, arg, proxyable, null); +}; + +Promise.prototype._resolveCallback = function(value, shouldBind) { + if (BIT_FIELD_CHECK(IS_FATE_SEALED, this._bitField)) return; + if (value === this) + return this._rejectCallback(makeSelfResolutionError(), false); + var maybePromise = tryConvertToPromise(value, this); + if (!(maybePromise instanceof Promise)) return this._fulfill(value); + + if (shouldBind) this._propagateFrom(maybePromise, PROPAGATE_BIND); + + + var promise = maybePromise._target(); + + if (promise === this) { + this._reject(makeSelfResolutionError()); + return; + } + + var bitField = promise._bitField; + if (BIT_FIELD_CHECK(IS_PENDING_AND_WAITING_NEG)) { + var len = this._length(); + if (len > 0) promise._migrateCallback0(this); + for (var i = 1; i < len; ++i) { + promise._migrateCallbackAt(this, i); + } + this._setFollowing(); + this._setLength(0); + this._setFollowee(maybePromise); + } else if (BIT_FIELD_CHECK(IS_FULFILLED)) { + this._fulfill(promise._value()); + } else if (BIT_FIELD_CHECK(IS_REJECTED)) { + this._reject(promise._reason()); + } else { + var reason = new CancellationError(LATE_CANCELLATION_OBSERVER); + promise._attachExtraTrace(reason); + this._reject(reason); + } +}; + +Promise.prototype._rejectCallback = +function(reason, synchronous, ignoreNonErrorWarnings) { + var trace = util.ensureErrorObject(reason); + var hasStack = trace === reason; + if (!hasStack && !ignoreNonErrorWarnings && debug.warnings()) { + var message = "a promise was rejected with a non-error: " + + util.classString(reason); + this._warn(message, true); + } + this._attachExtraTrace(trace, synchronous ? hasStack : false); + this._reject(reason); +}; + +Promise.prototype._resolveFromExecutor = function (executor) { + if (executor === INTERNAL) return; + ASSERT(typeof executor === "function"); + var promise = this; + this._captureStackTrace(); + this._pushContext(); + var synchronous = true; + var r = this._execute(executor, function(value) { + promise._resolveCallback(value); + }, function (reason) { + promise._rejectCallback(reason, synchronous); + }); + synchronous = false; + this._popContext(); + + if (r !== undefined) { + promise._rejectCallback(r, true); + } +}; + +Promise.prototype._settlePromiseFromHandler = function ( + handler, receiver, value, promise +) { + var bitField = promise._bitField; + if (BIT_FIELD_CHECK(IS_CANCELLED)) return; + promise._pushContext(); + var x; + if (receiver === APPLY) { + if (!value || typeof value.length !== "number") { + x = errorObj; + x.e = new TypeError("cannot .spread() a non-array: " + + util.classString(value)); + } else { + x = tryCatch(handler).apply(this._boundValue(), value); + } + } else { + x = tryCatch(handler).call(receiver, value); + } + var promiseCreated = promise._popContext(); + bitField = promise._bitField; + if (BIT_FIELD_CHECK(IS_CANCELLED)) return; + + ASSERT(!promise._isFateSealed()); + + if (x === NEXT_FILTER) { + promise._reject(value); + } else if (x === errorObj) { + promise._rejectCallback(x.e, false); + } else { + debug.checkForgottenReturns(x, promiseCreated, "", promise, this); + promise._resolveCallback(x); + } +}; + +Promise.prototype._target = function() { + var ret = this; + while (ret._isFollowing()) ret = ret._followee(); + return ret; +}; + +Promise.prototype._followee = function() { + ASSERT(this._isFollowing()); + ASSERT(this._rejectionHandler0 instanceof Promise); + return this._rejectionHandler0; +}; + +Promise.prototype._setFollowee = function(promise) { + ASSERT(this._isFollowing()); + ASSERT(!(this._rejectionHandler0 instanceof Promise)); + this._rejectionHandler0 = promise; +}; + +Promise.prototype._settlePromise = function(promise, handler, receiver, value) { + ASSERT(!this._isFollowing()); + var isPromise = promise instanceof Promise; + var bitField = this._bitField; + var asyncGuaranteed = BIT_FIELD_CHECK(IS_ASYNC_GUARANTEED); + if (BIT_FIELD_CHECK(IS_CANCELLED)) { + if (isPromise) promise._invokeInternalOnCancel(); + + if (receiver instanceof PassThroughHandlerContext && + receiver.isFinallyHandler()) { + receiver.cancelPromise = promise; + if (tryCatch(handler).call(receiver, value) === errorObj) { + promise._reject(errorObj.e); + } + } else if (handler === reflectHandler) { + promise._fulfill(reflectHandler.call(receiver)); + } else if (receiver instanceof Proxyable) { + receiver._promiseCancelled(promise); + } else if (isPromise || promise instanceof PromiseArray) { + promise._cancel(); + } else { + receiver.cancel(); + } + } else if (typeof handler === "function") { + //if promise is not instanceof Promise + //it is internally smuggled data + if (!isPromise) { + handler.call(receiver, value, promise); + } else { + if (asyncGuaranteed) promise._setAsyncGuaranteed(); + this._settlePromiseFromHandler(handler, receiver, value, promise); + } + } else if (receiver instanceof Proxyable) { + if (!receiver._isResolved()) { + if (BIT_FIELD_CHECK(IS_FULFILLED)) { + receiver._promiseFulfilled(value, promise); + } else { + receiver._promiseRejected(value, promise); + } + } + } else if (isPromise) { + if (asyncGuaranteed) promise._setAsyncGuaranteed(); + if (BIT_FIELD_CHECK(IS_FULFILLED)) { + promise._fulfill(value); + } else { + promise._reject(value); + } + } +}; + +Promise.prototype._settlePromiseLateCancellationObserver = function(ctx) { + var handler = ctx.handler; + var promise = ctx.promise; + var receiver = ctx.receiver; + var value = ctx.value; + if (typeof handler === "function") { + if (!(promise instanceof Promise)) { + handler.call(receiver, value, promise); + } else { + this._settlePromiseFromHandler(handler, receiver, value, promise); + } + } else if (promise instanceof Promise) { + promise._reject(value); + } +}; + +Promise.prototype._settlePromiseCtx = function(ctx) { + this._settlePromise(ctx.promise, ctx.handler, ctx.receiver, ctx.value); +}; + +Promise.prototype._settlePromise0 = function(handler, value, bitField) { + var promise = this._promise0; + var receiver = this._receiverAt(0); + this._promise0 = undefined; + this._receiver0 = undefined; + this._settlePromise(promise, handler, receiver, value); +}; + +Promise.prototype._clearCallbackDataAtIndex = function(index) { + ASSERT(!this._isFollowing()); + ASSERT(index > 0); + var base = index * CALLBACK_SIZE - CALLBACK_SIZE; + this[base + CALLBACK_PROMISE_OFFSET] = + this[base + CALLBACK_RECEIVER_OFFSET] = + this[base + CALLBACK_FULFILL_OFFSET] = + this[base + CALLBACK_REJECT_OFFSET] = undefined; +}; + +Promise.prototype._fulfill = function (value) { + var bitField = this._bitField; + if (BIT_FIELD_READ(IS_FATE_SEALED)) return; + if (value === this) { + var err = makeSelfResolutionError(); + this._attachExtraTrace(err); + return this._reject(err); + } + this._setFulfilled(); + this._rejectionHandler0 = value; + + if (BIT_FIELD_READ(LENGTH_MASK) > 0) { + if (BIT_FIELD_CHECK(IS_ASYNC_GUARANTEED)) { + this._settlePromises(); + } else { + async.settlePromises(this); + } + this._dereferenceTrace(); + } +}; + +Promise.prototype._reject = function (reason) { + var bitField = this._bitField; + if (BIT_FIELD_READ(IS_FATE_SEALED)) return; + this._setRejected(); + this._fulfillmentHandler0 = reason; + + if (this._isFinal()) { + ASSERT(this._length() === 0); + return async.fatalError(reason, util.isNode); + } + + if (BIT_FIELD_READ(LENGTH_MASK) > 0) { + async.settlePromises(this); + } else { + this._ensurePossibleRejectionHandled(); + } +}; + +Promise.prototype._fulfillPromises = function (len, value) { + for (var i = 1; i < len; i++) { + var handler = this._fulfillmentHandlerAt(i); + var promise = this._promiseAt(i); + var receiver = this._receiverAt(i); + this._clearCallbackDataAtIndex(i); + this._settlePromise(promise, handler, receiver, value); + } +}; + +Promise.prototype._rejectPromises = function (len, reason) { + for (var i = 1; i < len; i++) { + var handler = this._rejectionHandlerAt(i); + var promise = this._promiseAt(i); + var receiver = this._receiverAt(i); + this._clearCallbackDataAtIndex(i); + this._settlePromise(promise, handler, receiver, reason); + } +}; + +Promise.prototype._settlePromises = function () { + var bitField = this._bitField; + var len = BIT_FIELD_READ(LENGTH_MASK); + + if (len > 0) { + if (BIT_FIELD_CHECK(IS_REJECTED_OR_CANCELLED)) { + var reason = this._fulfillmentHandler0; + this._settlePromise0(this._rejectionHandler0, reason, bitField); + this._rejectPromises(len, reason); + } else { + var value = this._rejectionHandler0; + this._settlePromise0(this._fulfillmentHandler0, value, bitField); + this._fulfillPromises(len, value); + } + this._setLength(0); + } + this._clearCancellationData(); +}; + +Promise.prototype._settledValue = function() { + ASSERT(!this._isFollowing()); + ASSERT(this._isFateSealed()); + var bitField = this._bitField; + if (BIT_FIELD_CHECK(IS_FULFILLED)) { + return this._rejectionHandler0; + } else if (BIT_FIELD_CHECK(IS_REJECTED)) { + return this._fulfillmentHandler0; + } + // Implicit undefined for cancelled promise. +}; + +if (typeof Symbol !== "undefined" && Symbol.toStringTag) { + es5.defineProperty(Promise.prototype, Symbol.toStringTag, { + get: function () { + return "Object"; + } + }); +} + +function deferResolve(v) {this.promise._resolveCallback(v);} +function deferReject(v) {this.promise._rejectCallback(v, false);} + +Promise.defer = Promise.pending = function() { + debug.deprecated("Promise.defer", "new Promise"); + var promise = new Promise(INTERNAL); + return { + promise: promise, + resolve: deferResolve, + reject: deferReject + }; +}; + +util.notEnumerableProp(Promise, + "_makeSelfResolutionError", + makeSelfResolutionError); + +require("./method")(Promise, INTERNAL, tryConvertToPromise, apiRejection, + debug); +require("./bind")(Promise, INTERNAL, tryConvertToPromise, debug); +require("./cancel")(Promise, PromiseArray, apiRejection, debug); +require("./direct_resolve")(Promise); +require("./synchronous_inspection")(Promise); +require("./join")( + Promise, PromiseArray, tryConvertToPromise, INTERNAL, async); +Promise.Promise = Promise; +Promise.version = "__VERSION__"; +}; diff --git a/src/promise_array.js b/src/promise_array.js new file mode 100644 index 0000000..6256889 --- /dev/null +++ b/src/promise_array.js @@ -0,0 +1,203 @@ +"use strict"; +module.exports = function(Promise, INTERNAL, tryConvertToPromise, + apiRejection, Proxyable) { +var ASSERT = require("./assert"); +var util = require("./util"); +var isArray = util.isArray; + +//To avoid eagerly allocating the objects +//and also because undefined cannot be smuggled +function toResolutionValue(val) { + switch(val) { + case RESOLVE_ARRAY: return []; + case RESOLVE_OBJECT: return {}; + case RESOLVE_MAP: return new Map(); + } + ASSERT(false); +} + +function PromiseArray(values) { + ASSERT(arguments.length === 1); + var promise = this._promise = new Promise(INTERNAL); + if (values instanceof Promise) { + promise._propagateFrom(values, PROPAGATE_ALL); + values.suppressUnhandledRejections(); + } + promise._setOnCancel(this); + this._values = values; + this._length = 0; + this._totalResolved = 0; + this._init(undefined, RESOLVE_ARRAY); +} +util.inherits(PromiseArray, Proxyable); + +PromiseArray.prototype.length = function () { + return this._length; +}; + +PromiseArray.prototype.promise = function () { + return this._promise; +}; + +PromiseArray.prototype._init = function init(_, resolveValueIfEmpty) { + var values = tryConvertToPromise(this._values, this._promise); + if (values instanceof Promise) { + values = values._target(); + var bitField = values._bitField; + USE(bitField); + this._values = values; + + if (BIT_FIELD_CHECK(IS_PENDING_AND_WAITING_NEG)) { + ASSERT(typeof resolveValueIfEmpty === "number"); + ASSERT(resolveValueIfEmpty < 0); + this._promise._setAsyncGuaranteed(); + return values._then( + init, + this._reject, + undefined, + this, + resolveValueIfEmpty + ); + } else if (BIT_FIELD_CHECK(IS_FULFILLED)) { + values = values._value(); + } else if (BIT_FIELD_CHECK(IS_REJECTED)) { + return this._reject(values._reason()); + } else { + return this._cancel(); + } + } + values = util.asArray(values); + if (values === null) { + var err = apiRejection( + COLLECTION_ERROR + util.classString(values)).reason(); + this._promise._rejectCallback(err, false); + return; + } + + if (values.length === 0) { + if (resolveValueIfEmpty === RESOLVE_CALL_METHOD) { + this._resolveEmptyArray(); + } + else { + this._resolve(toResolutionValue(resolveValueIfEmpty)); + } + return; + } + this._iterate(values); +}; + +PromiseArray.prototype._iterate = function(values) { + var len = this.getActualLength(values.length); + this._length = len; + this._values = this.shouldCopyValues() ? new Array(len) : this._values; + var result = this._promise; + var isResolved = false; + var bitField = null; + for (var i = 0; i < len; ++i) { + var maybePromise = tryConvertToPromise(values[i], result); + + if (maybePromise instanceof Promise) { + maybePromise = maybePromise._target(); + bitField = maybePromise._bitField; + } else { + bitField = null; + } + + if (isResolved) { + if (bitField !== null) { + maybePromise.suppressUnhandledRejections(); + } + } else if (bitField !== null) { + if (BIT_FIELD_CHECK(IS_PENDING_AND_WAITING_NEG)) { + // Optimized for just passing the updates through + maybePromise._proxy(this, i); + this._values[i] = maybePromise; + } else if (BIT_FIELD_CHECK(IS_FULFILLED)) { + isResolved = this._promiseFulfilled(maybePromise._value(), i); + } else if (BIT_FIELD_CHECK(IS_REJECTED)) { + isResolved = this._promiseRejected(maybePromise._reason(), i); + } else { + isResolved = this._promiseCancelled(i); + } + } else { + isResolved = this._promiseFulfilled(maybePromise, i); + } + ASSERT(typeof isResolved === "boolean"); + } + if (!isResolved) result._setAsyncGuaranteed(); +}; + +PromiseArray.prototype._isResolved = function () { + return this._values === null; +}; + +PromiseArray.prototype._resolve = function (value) { + ASSERT(!this._isResolved()); + ASSERT(!(value instanceof Promise)); + this._values = null; + this._promise._fulfill(value); +}; + +PromiseArray.prototype._cancel = function() { + if (this._isResolved() || !this._promise._isCancellable()) return; + this._values = null; + this._promise._cancel(); +}; + +PromiseArray.prototype._reject = function (reason) { + ASSERT(!this._isResolved()); + this._values = null; + this._promise._rejectCallback(reason, false); +}; + +PromiseArray.prototype._promiseFulfilled = function (value, index) { + ASSERT(!this._isResolved()); + ASSERT(isArray(this._values)); + ASSERT(typeof index === "number"); + this._values[index] = value; + var totalResolved = ++this._totalResolved; + if (totalResolved >= this._length) { + this._resolve(this._values); + return true; + } + return false; +}; + +PromiseArray.prototype._promiseCancelled = function() { + this._cancel(); + return true; +}; + +PromiseArray.prototype._promiseRejected = function (reason) { + ASSERT(!this._isResolved()); + ASSERT(isArray(this._values)); + this._totalResolved++; + this._reject(reason); + return true; +}; + +PromiseArray.prototype._resultCancelled = function() { + if (this._isResolved()) return; + var values = this._values; + this._cancel(); + if (values instanceof Promise) { + values.cancel(); + } else { + for (var i = 0; i < values.length; ++i) { + if (values[i] instanceof Promise) { + values[i].cancel(); + } + } + } +}; + +PromiseArray.prototype.shouldCopyValues = function () { + return true; +}; + +PromiseArray.prototype.getActualLength = function (len) { + return len; +}; + +return PromiseArray; +}; diff --git a/src/promisify.js b/src/promisify.js new file mode 100644 index 0000000..a0bebf3 --- /dev/null +++ b/src/promisify.js @@ -0,0 +1,326 @@ +"use strict"; +module.exports = function(Promise, INTERNAL) { +var THIS = {}; +var util = require("./util"); +var nodebackForPromise = require("./nodeback"); +var withAppended = util.withAppended; +var maybeWrapAsError = util.maybeWrapAsError; +var canEvaluate = util.canEvaluate; +var ASSERT = require("./assert"); +var TypeError = require("./errors").TypeError; +var defaultSuffix = AFTER_PROMISIFIED_SUFFIX; +var defaultPromisified = {__isPromisified__: true}; +var noCopyProps = [ + "arity", // Firefox 4 + "length", + "name", + "arguments", + "caller", + "callee", + "prototype", + "__isPromisified__" +]; +var noCopyPropsPattern = new RegExp("^(?:" + noCopyProps.join("|") + ")$"); + +var defaultFilter = function(name) { + return util.isIdentifier(name) && + name.charAt(0) !== "_" && + name !== "constructor"; +}; + +function propsFilter(key) { + return !noCopyPropsPattern.test(key); +} + +function isPromisified(fn) { + try { + return fn.__isPromisified__ === true; + } + catch (e) { + return false; + } +} + +function hasPromisified(obj, key, suffix) { + var val = util.getDataPropertyOrDefault(obj, key + suffix, + defaultPromisified); + return val ? isPromisified(val) : false; +} +function checkValid(ret, suffix, suffixRegexp) { + // Verify that in the list of methods to promisify there is no + // method that has a name ending in "Async"-suffix while + // also having a method with the same name but no Async suffix + for (var i = 0; i < ret.length; i += 2) { + var key = ret[i]; + if (suffixRegexp.test(key)) { + var keyWithoutAsyncSuffix = key.replace(suffixRegexp, ""); + for (var j = 0; j < ret.length; j += 2) { + if (ret[j] === keyWithoutAsyncSuffix) { + throw new TypeError(PROMISIFICATION_NORMAL_METHODS_ERROR + .replace("%s", suffix)); + } + } + } + } +} + +function promisifiableMethods(obj, suffix, suffixRegexp, filter) { + var keys = util.inheritedDataKeys(obj); + var ret = []; + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + var value = obj[key]; + var passesDefaultFilter = filter === defaultFilter + ? true : defaultFilter(key, value, obj); + if (typeof value === "function" && + !isPromisified(value) && + !hasPromisified(obj, key, suffix) && + filter(key, value, obj, passesDefaultFilter)) { + ret.push(key, value); + } + } + checkValid(ret, suffix, suffixRegexp); + return ret; +} + +var escapeIdentRegex = function(str) { + return str.replace(/([$])/, "\\$"); +}; + +var makeNodePromisifiedEval; +if (!__BROWSER__) { +//Gives an optimal sequence of argument count to try given a formal parameter +//.length for a function +var switchCaseArgumentOrder = function(likelyArgumentCount) { + var ret = [likelyArgumentCount]; + var min = Math.max(0, likelyArgumentCount - 1 - PARAM_COUNTS_TO_TRY); + for(var i = likelyArgumentCount - 1; i >= min; --i) { + ret.push(i); + } + for(var i = likelyArgumentCount + 1; i <= PARAM_COUNTS_TO_TRY; ++i) { + ret.push(i); + } + return ret; +}; + +var argumentSequence = function(argumentCount) { + return util.filledRange(argumentCount, "_arg", ""); +}; + +var parameterDeclaration = function(parameterCount) { + return util.filledRange( + Math.max(parameterCount, PARAM_COUNTS_TO_TRY), "_arg", ""); +}; + +var parameterCount = function(fn) { + if (typeof fn.length === "number") { + return Math.max(Math.min(fn.length, MAX_PARAM_COUNT + 1), 0); + } + //Unsupported .length for functions + return 0; +}; + +makeNodePromisifiedEval = +function(callback, receiver, originalName, fn, _, multiArgs) { + //-1 for the callback parameter + var newParameterCount = Math.max(0, parameterCount(fn) - 1); + var argumentOrder = switchCaseArgumentOrder(newParameterCount); + var shouldProxyThis = typeof callback === "string" || receiver === THIS; + + function generateCallForArgumentCount(count) { + var args = argumentSequence(count).join(", "); + var comma = count > 0 ? ", " : ""; + var ret; + if (shouldProxyThis) { + ret = "ret = callback.call(this, {{args}}, nodeback); break;\n"; + } else { + ret = receiver === undefined + ? "ret = callback({{args}}, nodeback); break;\n" + : "ret = callback.call(receiver, {{args}}, nodeback); break;\n"; + } + return ret.replace("{{args}}", args).replace(", ", comma); + } + + function generateArgumentSwitchCase() { + var ret = ""; + for (var i = 0; i < argumentOrder.length; ++i) { + ret += "case " + argumentOrder[i] +":" + + generateCallForArgumentCount(argumentOrder[i]); + } + + ret += " \n\ + default: \n\ + var args = new Array(len + 1); \n\ + var i = 0; \n\ + for (var i = 0; i < len; ++i) { \n\ + args[i] = arguments[i]; \n\ + } \n\ + args[i] = nodeback; \n\ + [CodeForCall] \n\ + break; \n\ + ".replace("[CodeForCall]", (shouldProxyThis + ? "ret = callback.apply(this, args);\n" + : "ret = callback.apply(receiver, args);\n")); + return ret; + } + + var getFunctionCode = typeof callback === "string" + ? ("this != null ? this['"+callback+"'] : fn") + : "fn"; + var body = "'use strict'; \n\ + var ret = function (Parameters) { \n\ + 'use strict'; \n\ + var len = arguments.length; \n\ + var promise = new Promise(INTERNAL); \n\ + promise._captureStackTrace(); \n\ + var nodeback = nodebackForPromise(promise, " + multiArgs + "); \n\ + var ret; \n\ + var callback = tryCatch([GetFunctionCode]); \n\ + switch(len) { \n\ + [CodeForSwitchCase] \n\ + } \n\ + if (ret === errorObj) { \n\ + promise._rejectCallback(maybeWrapAsError(ret.e), true, true);\n\ + } \n\ + if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); \n\ + return promise; \n\ + }; \n\ + notEnumerableProp(ret, '__isPromisified__', true); \n\ + return ret; \n\ + ".replace("[CodeForSwitchCase]", generateArgumentSwitchCase()) + .replace("[GetFunctionCode]", getFunctionCode); + body = body.replace("Parameters", parameterDeclaration(newParameterCount)); + return new Function("Promise", + "fn", + "receiver", + "withAppended", + "maybeWrapAsError", + "nodebackForPromise", + "tryCatch", + "errorObj", + "notEnumerableProp", + "INTERNAL", + body)( + Promise, + fn, + receiver, + withAppended, + maybeWrapAsError, + nodebackForPromise, + util.tryCatch, + util.errorObj, + util.notEnumerableProp, + INTERNAL); +}; +} + +function makeNodePromisifiedClosure(callback, receiver, _, fn, __, multiArgs) { + var defaultThis = (function() {return this;})(); + var method = callback; + if (typeof method === "string") { + callback = fn; + } + function promisified() { + var _receiver = receiver; + if (receiver === THIS) _receiver = this; + ASSERT(typeof callback === "function"); + var promise = new Promise(INTERNAL); + promise._captureStackTrace(); + var cb = typeof method === "string" && this !== defaultThis + ? this[method] : callback; + var fn = nodebackForPromise(promise, multiArgs); + try { + cb.apply(_receiver, withAppended(arguments, fn)); + } catch(e) { + promise._rejectCallback(maybeWrapAsError(e), true, true); + } + if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); + return promise; + } + util.notEnumerableProp(promisified, "__isPromisified__", true); + return promisified; +} + +var makeNodePromisified = canEvaluate + ? makeNodePromisifiedEval + : makeNodePromisifiedClosure; + +function promisifyAll(obj, suffix, filter, promisifier, multiArgs) { + ASSERT(typeof suffix === "string"); + ASSERT(typeof filter === "function"); + var suffixRegexp = new RegExp(escapeIdentRegex(suffix) + "$"); + var methods = + promisifiableMethods(obj, suffix, suffixRegexp, filter); + + for (var i = 0, len = methods.length; i < len; i+= 2) { + var key = methods[i]; + var fn = methods[i+1]; + var promisifiedKey = key + suffix; + if (promisifier === makeNodePromisified) { + obj[promisifiedKey] = + makeNodePromisified(key, THIS, key, fn, suffix, multiArgs); + } else { + var promisified = promisifier(fn, function() { + return makeNodePromisified(key, THIS, key, + fn, suffix, multiArgs); + }); + util.notEnumerableProp(promisified, "__isPromisified__", true); + obj[promisifiedKey] = promisified; + } + } + util.toFastProperties(obj); + return obj; +} + +function promisify(callback, receiver, multiArgs) { + return makeNodePromisified(callback, receiver, undefined, + callback, null, multiArgs); +} + +Promise.promisify = function (fn, options) { + if (typeof fn !== "function") { + throw new TypeError(FUNCTION_ERROR + util.classString(fn)); + } + if (isPromisified(fn)) { + return fn; + } + options = Object(options); + var receiver = options.context === undefined ? THIS : options.context; + var multiArgs = !!options.multiArgs; + var ret = promisify(fn, receiver, multiArgs); + util.copyDescriptors(fn, ret, propsFilter); + return ret; +}; + +Promise.promisifyAll = function (target, options) { + if (typeof target !== "function" && typeof target !== "object") { + throw new TypeError(PROMISIFY_TYPE_ERROR); + } + options = Object(options); + var multiArgs = !!options.multiArgs; + var suffix = options.suffix; + if (typeof suffix !== "string") suffix = defaultSuffix; + var filter = options.filter; + if (typeof filter !== "function") filter = defaultFilter; + var promisifier = options.promisifier; + if (typeof promisifier !== "function") promisifier = makeNodePromisified; + + if (!util.isIdentifier(suffix)) { + throw new RangeError(SUFFIX_NOT_IDENTIFIER); + } + + var keys = util.inheritedDataKeys(target); + for (var i = 0; i < keys.length; ++i) { + var value = target[keys[i]]; + if (keys[i] !== "constructor" && + util.isClass(value)) { + promisifyAll(value.prototype, suffix, filter, promisifier, + multiArgs); + promisifyAll(value, suffix, filter, promisifier, multiArgs); + } + } + + return promisifyAll(target, suffix, filter, promisifier, multiArgs); +}; +}; + diff --git a/src/props.js b/src/props.js new file mode 100644 index 0000000..ccbf22e --- /dev/null +++ b/src/props.js @@ -0,0 +1,125 @@ +"use strict"; +module.exports = function( + Promise, PromiseArray, tryConvertToPromise, apiRejection) { +var ASSERT = require("./assert"); +var util = require("./util"); +var isObject = util.isObject; +var es5 = require("./es5"); +var Es6Map; +if (typeof Map === "function") Es6Map = Map; + +var mapToEntries = (function() { + var index = 0; + var size = 0; + + function extractEntry(value, key) { + this[index] = value; + this[index + size] = key; + index++; + } + + return function mapToEntries(map) { + size = map.size; + index = 0; + var ret = new Array(map.size * 2); + map.forEach(extractEntry, ret); + return ret; + }; +})(); + +var entriesToMap = function(entries) { + var ret = new Es6Map(); + var length = entries.length / 2 | 0; + for (var i = 0; i < length; ++i) { + var key = entries[length + i]; + var value = entries[i]; + ret.set(key, value); + } + return ret; +}; + +function PropertiesPromiseArray(obj) { + var isMap = false; + var entries; + if (Es6Map !== undefined && obj instanceof Es6Map) { + entries = mapToEntries(obj); + isMap = true; + } else { + var keys = es5.keys(obj); + var len = keys.length; + entries = new Array(len * 2); + for (var i = 0; i < len; ++i) { + var key = keys[i]; + entries[i] = obj[key]; + entries[i + len] = key; + } + } + this.constructor$(entries); + this._isMap = isMap; + this._init$(undefined, isMap ? RESOLVE_MAP : RESOLVE_OBJECT); +} +util.inherits(PropertiesPromiseArray, PromiseArray); + +//Override +PropertiesPromiseArray.prototype._init = function () {}; + +//Override +PropertiesPromiseArray.prototype._promiseFulfilled = function (value, index) { + ASSERT(!this._isResolved()); + ASSERT(!(value instanceof Promise)); + this._values[index] = value; + var totalResolved = ++this._totalResolved; + if (totalResolved >= this._length) { + var val; + if (this._isMap) { + val = entriesToMap(this._values); + } else { + val = {}; + var keyOffset = this.length(); + for (var i = 0, len = this.length(); i < len; ++i) { + val[this._values[i + keyOffset]] = this._values[i]; + } + } + this._resolve(val); + return true; + } + return false; +}; + +// Override +PropertiesPromiseArray.prototype.shouldCopyValues = function () { + return false; +}; + +// Override +PropertiesPromiseArray.prototype.getActualLength = function (len) { + return len >> 1; +}; + +function props(promises) { + var ret; + var castValue = tryConvertToPromise(promises); + + if (!isObject(castValue)) { + return apiRejection(PROPS_TYPE_ERROR); + } else if (castValue instanceof Promise) { + ret = castValue._then( + Promise.props, undefined, undefined, undefined, undefined); + } else { + ret = new PropertiesPromiseArray(castValue).promise(); + } + + if (castValue instanceof Promise) { + ret._propagateFrom(castValue, PROPAGATE_BIND); + } + return ret; +} + +Promise.prototype.props = function () { + return props(this); +}; + +Promise.props = function (promises) { + return props(promises); +}; +}; diff --git a/src/queue.js b/src/queue.js new file mode 100644 index 0000000..15163b6 --- /dev/null +++ b/src/queue.js @@ -0,0 +1,79 @@ +"use strict"; +var ASSERT = require("./assert"); +function arrayMove(src, srcIndex, dst, dstIndex, len) { + for (var j = 0; j < len; ++j) { + dst[j + dstIndex] = src[j + srcIndex]; + src[j + srcIndex] = void 0; + } +} + +function Queue(capacity) { + this._capacity = capacity; + this._length = 0; + this._front = 0; +} + +Queue.prototype._willBeOverCapacity = function (size) { + return this._capacity < size; +}; + +Queue.prototype._pushOne = function (arg) { + var length = this.length(); + this._checkCapacity(length + 1); + var i = (this._front + length) & (this._capacity - 1); + this[i] = arg; + this._length = length + 1; +}; + +Queue.prototype.push = function (fn, receiver, arg) { + ASSERT(arguments.length === 3); + ASSERT(typeof fn === "function"); + var length = this.length() + 3; + if (this._willBeOverCapacity(length)) { + //The fast array copies expect the + //underlying array to be filled completely + this._pushOne(fn); + this._pushOne(receiver); + this._pushOne(arg); + return; + } + var j = this._front + length - 3; + this._checkCapacity(length); + var wrapMask = this._capacity - 1; + this[(j + 0) & wrapMask] = fn; + this[(j + 1) & wrapMask] = receiver; + this[(j + 2) & wrapMask] = arg; + this._length = length; +}; + +Queue.prototype.shift = function () { + ASSERT(this.length() > 0); + var front = this._front, + ret = this[front]; + + this[front] = undefined; + this._front = (front + 1) & (this._capacity - 1); + this._length--; + return ret; +}; + +Queue.prototype.length = function () { + return this._length; +}; + +Queue.prototype._checkCapacity = function (size) { + if (this._capacity < size) { + this._resizeTo(this._capacity << 1); + } +}; + +Queue.prototype._resizeTo = function (capacity) { + var oldCapacity = this._capacity; + this._capacity = capacity; + var front = this._front; + var length = this._length; + var moveItemsCount = (front + length) & (oldCapacity - 1); + arrayMove(this, 0, this, oldCapacity, moveItemsCount); +}; + +module.exports = Queue; diff --git a/src/race.js b/src/race.js new file mode 100644 index 0000000..6f57c5b --- /dev/null +++ b/src/race.js @@ -0,0 +1,50 @@ +"use strict"; +module.exports = function( + Promise, INTERNAL, tryConvertToPromise, apiRejection) { +var util = require("./util"); + +var raceLater = function (promise) { + return promise.then(function(array) { + return race(array, promise); + }); +}; + +function race(promises, parent) { + var maybePromise = tryConvertToPromise(promises); + + if (maybePromise instanceof Promise) { + return raceLater(maybePromise); + } else { + promises = util.asArray(promises); + if (promises === null) + return apiRejection(COLLECTION_ERROR + util.classString(promises)); + } + + var ret = new Promise(INTERNAL); + if (parent !== undefined) { + ret._propagateFrom(parent, PROPAGATE_ALL); + } + var fulfill = ret._fulfill; + var reject = ret._reject; + for (var i = 0, len = promises.length; i < len; ++i) { + var val = promises[i]; + + if (val === undefined && !(i in promises)) { + continue; + } + + Promise.cast(val)._then(fulfill, reject, undefined, ret, null); + } + //Yes, if promises were empty, it will be forever pending :-) + return ret; +} + +Promise.race = function (promises) { + return race(promises, undefined); +}; + +Promise.prototype.race = function () { + return race(this, undefined); +}; + +}; diff --git a/src/reduce.js b/src/reduce.js new file mode 100644 index 0000000..51f86aa --- /dev/null +++ b/src/reduce.js @@ -0,0 +1,191 @@ +"use strict"; +module.exports = function(Promise, + PromiseArray, + apiRejection, + tryConvertToPromise, + INTERNAL, + debug) { +var util = require("./util"); +var tryCatch = util.tryCatch; + +function ReductionPromiseArray(promises, fn, initialValue, _each) { + this.constructor$(promises); + var context = Promise._getContext(); + this._fn = util.contextBind(context, fn); + if (initialValue !== undefined) { + initialValue = Promise.resolve(initialValue); + initialValue._attachCancellationCallback(this); + } + this._initialValue = initialValue; + this._currentCancellable = null; + if(_each === INTERNAL) { + this._eachValues = Array(this._length); + } else if (_each === 0) { + this._eachValues = null; + } else { + this._eachValues = undefined; + } + this._promise._captureStackTrace(); + this._init$(undefined, RESOLVE_CALL_METHOD); +} +util.inherits(ReductionPromiseArray, PromiseArray); + +ReductionPromiseArray.prototype._gotAccum = function(accum) { + if (this._eachValues !== undefined && + this._eachValues !== null && + accum !== INTERNAL) { + this._eachValues.push(accum); + } +}; + +ReductionPromiseArray.prototype._eachComplete = function(value) { + if (this._eachValues !== null) { + this._eachValues.push(value); + } + return this._eachValues; +}; + +// Override +ReductionPromiseArray.prototype._init = function() {}; + +// Override +ReductionPromiseArray.prototype._resolveEmptyArray = function() { + this._resolve(this._eachValues !== undefined ? this._eachValues + : this._initialValue); +}; + +// Override +ReductionPromiseArray.prototype.shouldCopyValues = function () { + return false; +}; + +// Override +ReductionPromiseArray.prototype._resolve = function(value) { + this._promise._resolveCallback(value); + this._values = null; +}; + +// Override +ReductionPromiseArray.prototype._resultCancelled = function(sender) { + if (sender === this._initialValue) return this._cancel(); + if (this._isResolved()) return; + this._resultCancelled$(); + if (this._currentCancellable instanceof Promise) { + this._currentCancellable.cancel(); + } + if (this._initialValue instanceof Promise) { + this._initialValue.cancel(); + } +}; + +// Override +ReductionPromiseArray.prototype._iterate = function (values) { + this._values = values; + var value; + var i; + var length = values.length; + if (this._initialValue !== undefined) { + value = this._initialValue; + i = 0; + } else { + value = Promise.resolve(values[0]); + i = 1; + } + + this._currentCancellable = value; + + for (var j = i; j < length; ++j) { + var maybePromise = values[j]; + if (maybePromise instanceof Promise) { + maybePromise.suppressUnhandledRejections(); + } + } + + if (!value.isRejected()) { + for (; i < length; ++i) { + var ctx = { + accum: null, + value: values[i], + index: i, + length: length, + array: this + }; + + value = value._then(gotAccum, undefined, undefined, ctx, undefined); + + // Too many promises chained with asyncGuaranteed will result in + // stack overflow. Break up long chains to reset stack. + if ((i & 127) === 0) { + value._setNoAsyncGuarantee(); + } + } + } + + if (this._eachValues !== undefined) { + value = value + ._then(this._eachComplete, undefined, undefined, this, undefined); + } + value._then(completed, completed, undefined, value, this); +}; + +Promise.prototype.reduce = function (fn, initialValue) { + return reduce(this, fn, initialValue, null); +}; + +Promise.reduce = function (promises, fn, initialValue, _each) { + return reduce(promises, fn, initialValue, _each); +}; + +function completed(valueOrReason, array) { + if (this.isFulfilled()) { + array._resolve(valueOrReason); + } else { + array._reject(valueOrReason); + } +} + +function reduce(promises, fn, initialValue, _each) { + if (typeof fn !== "function") { + return apiRejection(FUNCTION_ERROR + util.classString(fn)); + } + var array = new ReductionPromiseArray(promises, fn, initialValue, _each); + return array.promise(); +} + +function gotAccum(accum) { + this.accum = accum; + this.array._gotAccum(accum); + var value = tryConvertToPromise(this.value, this.array._promise); + if (value instanceof Promise) { + this.array._currentCancellable = value; + return value._then(gotValue, undefined, undefined, this, undefined); + } else { + return gotValue.call(this, value); + } +} + +function gotValue(value) { + var array = this.array; + var promise = array._promise; + var fn = tryCatch(array._fn); + promise._pushContext(); + var ret; + if (array._eachValues !== undefined) { + ret = fn.call(promise._boundValue(), value, this.index, this.length); + } else { + ret = fn.call(promise._boundValue(), + this.accum, value, this.index, this.length); + } + if (ret instanceof Promise) { + array._currentCancellable = ret; + } + var promiseCreated = promise._popContext(); + debug.checkForgottenReturns( + ret, + promiseCreated, + array._eachValues !== undefined ? "Promise.each" : "Promise.reduce", + promise + ); + return ret; +} +}; diff --git a/src/schedule.js b/src/schedule.js new file mode 100644 index 0000000..bcc5048 --- /dev/null +++ b/src/schedule.js @@ -0,0 +1,79 @@ +"use strict"; +var util = require("./util"); +var schedule; +var noAsyncScheduler = function() { + throw new Error(NO_ASYNC_SCHEDULER); +}; +var NativePromise = util.getNativePromise(); +// This file figures out which scheduler to use for Bluebird. It normalizes +// async task scheduling across target platforms. Note that not all JS target +// platforms come supported. The scheduler is overridable with `setScheduler`. + +// Our scheduler for Node.js/io.js is setImmediate for recent +// versions of node because of macrotask semantics. +// The `typeof` check is for an edge case with nw.js. +if (util.isNode && typeof MutationObserver === "undefined") { + var GlobalSetImmediate = global.setImmediate; + var ProcessNextTick = process.nextTick; + schedule = util.isRecentNode + ? function(fn) { GlobalSetImmediate.call(global, fn); } + : function(fn) { ProcessNextTick.call(process, fn); }; +} else if (typeof NativePromise === "function" && + typeof NativePromise.resolve === "function") { + var nativePromise = NativePromise.resolve(); + schedule = function(fn) { + nativePromise.then(fn); + }; +// Outside of Node, we're using MutationObservers because they provide low +// latency. The second check is to guard against iOS standalone apps which +// do not fire DOM mutation events for some reason on iOS 8.3+ and cordova +// apps which have the same bug but are not `.navigator.standalone` +} else if ((typeof MutationObserver !== "undefined") && + !(typeof window !== "undefined" && + window.navigator && + (window.navigator.standalone || window.cordova)) && + ("classList" in document.documentElement)) { + schedule = (function() { + // Using 2 mutation observers to batch multiple updates into one. + var div = document.createElement("div"); + var opts = {attributes: true}; + var toggleScheduled = false; + var div2 = document.createElement("div"); + var o2 = new MutationObserver(function() { + div.classList.toggle("foo"); + toggleScheduled = false; + }); + o2.observe(div2, opts); + + var scheduleToggle = function() { + if (toggleScheduled) return; + toggleScheduled = true; + div2.classList.toggle("foo"); + }; + + return function schedule(fn) { + var o = new MutationObserver(function() { + o.disconnect(); + fn(); + }); + o.observe(div, opts); + scheduleToggle(); + }; + })(); +// setImmediate has higher latency but is still pretty good. This is useful for +// cases where MutationObserver is not defined (older IE, for example). +} else if (typeof setImmediate !== "undefined") { + schedule = function (fn) { + setImmediate(fn); + }; +// setTimeout also works, it has the most latency but it does the trick. +} else if (typeof setTimeout !== "undefined") { + schedule = function (fn) { + setTimeout(fn, 0); + }; +} else { +// Do __Not__ default to a sync scheduler, that would break Promises/A+ +// compliancy and cause race conditions. + schedule = noAsyncScheduler; +} +module.exports = schedule; diff --git a/src/settle.js b/src/settle.js new file mode 100644 index 0000000..becf211 --- /dev/null +++ b/src/settle.js @@ -0,0 +1,55 @@ +"use strict"; +module.exports = + function(Promise, PromiseArray, debug) { +var ASSERT = require("./assert"); +var PromiseInspection = Promise.PromiseInspection; +var util = require("./util"); + +function SettledPromiseArray(values) { + this.constructor$(values); +} +util.inherits(SettledPromiseArray, PromiseArray); + +SettledPromiseArray.prototype._promiseResolved = function (index, inspection) { + ASSERT(typeof index === "number"); + this._values[index] = inspection; + var totalResolved = ++this._totalResolved; + if (totalResolved >= this._length) { + this._resolve(this._values); + return true; + } + return false; +}; + +//override +SettledPromiseArray.prototype._promiseFulfilled = function (value, index) { + ASSERT(!this._isResolved()); + ASSERT(typeof index === "number"); + var ret = new PromiseInspection(); + ret._bitField = IS_FULFILLED; + ret._settledValueField = value; + return this._promiseResolved(index, ret); +}; +//override +SettledPromiseArray.prototype._promiseRejected = function (reason, index) { + ASSERT(!this._isResolved()); + ASSERT(typeof index === "number"); + var ret = new PromiseInspection(); + ret._bitField = IS_REJECTED; + ret._settledValueField = reason; + return this._promiseResolved(index, ret); +}; + +Promise.settle = function (promises) { + debug.deprecated(".settle()", ".reflect()"); + return new SettledPromiseArray(promises).promise(); +}; + +Promise.allSettled = function (promises) { + return new SettledPromiseArray(promises).promise(); +}; + +Promise.prototype.settle = function () { + return Promise.settle(this); +}; +}; diff --git a/src/some.js b/src/some.js new file mode 100644 index 0000000..b1661ae --- /dev/null +++ b/src/some.js @@ -0,0 +1,159 @@ +"use strict"; +module.exports = +function(Promise, PromiseArray, apiRejection) { +var ASSERT = require("./assert"); +var util = require("./util"); +var RangeError = require("./errors").RangeError; +var AggregateError = require("./errors").AggregateError; +var isArray = util.isArray; +var CANCELLATION = {}; + + +function SomePromiseArray(values) { + this.constructor$(values); + this._howMany = 0; + this._unwrap = false; + this._initialized = false; +} +util.inherits(SomePromiseArray, PromiseArray); + +SomePromiseArray.prototype._init = function () { + if (!this._initialized) { + return; + } + if (this._howMany === 0) { + this._resolve([]); + return; + } + this._init$(undefined, RESOLVE_CALL_METHOD); + var isArrayResolved = isArray(this._values); + if (!this._isResolved() && + isArrayResolved && + this._howMany > this._canPossiblyFulfill()) { + this._reject(this._getRangeError(this.length())); + } +}; + +SomePromiseArray.prototype.init = function () { + this._initialized = true; + this._init(); +}; + +SomePromiseArray.prototype.setUnwrap = function () { + this._unwrap = true; +}; + +SomePromiseArray.prototype.howMany = function () { + return this._howMany; +}; + +SomePromiseArray.prototype.setHowMany = function (count) { + ASSERT(!this._isResolved()); + this._howMany = count; +}; + +//override +SomePromiseArray.prototype._promiseFulfilled = function (value) { + ASSERT(!this._isResolved()); + this._addFulfilled(value); + if (this._fulfilled() === this.howMany()) { + this._values.length = this.howMany(); + if (this.howMany() === 1 && this._unwrap) { + this._resolve(this._values[0]); + } else { + this._resolve(this._values); + } + return true; + } + return false; + +}; +//override +SomePromiseArray.prototype._promiseRejected = function (reason) { + ASSERT(!this._isResolved()); + this._addRejected(reason); + return this._checkOutcome(); +}; + +//override +SomePromiseArray.prototype._promiseCancelled = function () { + if (this._values instanceof Promise || this._values == null) { + return this._cancel(); + } + ASSERT(!this._isResolved()); + this._addRejected(CANCELLATION); + return this._checkOutcome(); +}; + +SomePromiseArray.prototype._checkOutcome = function() { + if (this.howMany() > this._canPossiblyFulfill()) { + var e = new AggregateError(); + for (var i = this.length(); i < this._values.length; ++i) { + if (this._values[i] !== CANCELLATION) { + e.push(this._values[i]); + } + } + if (e.length > 0) { + this._reject(e); + } else { + this._cancel(); + } + return true; + } + return false; +}; + +SomePromiseArray.prototype._fulfilled = function () { + return this._totalResolved; +}; + +SomePromiseArray.prototype._rejected = function () { + return this._values.length - this.length(); +}; + +//Use the same array past .length() to store rejection reasons +SomePromiseArray.prototype._addRejected = function (reason) { + this._values.push(reason); +}; + +SomePromiseArray.prototype._addFulfilled = function (value) { + this._values[this._totalResolved++] = value; +}; + +SomePromiseArray.prototype._canPossiblyFulfill = function () { + return this.length() - this._rejected(); +}; + +SomePromiseArray.prototype._getRangeError = function (count) { + var message = "Input array must contain at least " + + this._howMany + " items but contains only " + count + " items"; + return new RangeError(message); +}; + +SomePromiseArray.prototype._resolveEmptyArray = function () { + this._reject(this._getRangeError(0)); +}; + +function some(promises, howMany) { + if ((howMany | 0) !== howMany || howMany < 0) { + return apiRejection(POSITIVE_INTEGER_ERROR); + } + var ret = new SomePromiseArray(promises); + var promise = ret.promise(); + ASSERT(promise.isPending()); + ASSERT(ret instanceof SomePromiseArray); + ret.setHowMany(howMany); + ret.init(); + return promise; +} + +Promise.some = function (promises, howMany) { + return some(promises, howMany); +}; + +Promise.prototype.some = function (howMany) { + return some(this, howMany); +}; + +Promise._SomePromiseArray = SomePromiseArray; +}; diff --git a/src/synchronous_inspection.js b/src/synchronous_inspection.js new file mode 100644 index 0000000..4403797 --- /dev/null +++ b/src/synchronous_inspection.js @@ -0,0 +1,103 @@ +"use strict"; +module.exports = function(Promise) { +function PromiseInspection(promise) { + if (promise !== undefined) { + promise = promise._target(); + this._bitField = promise._bitField; + this._settledValueField = promise._isFateSealed() + ? promise._settledValue() : undefined; + } + else { + this._bitField = 0; + this._settledValueField = undefined; + } +} + +PromiseInspection.prototype._settledValue = function() { + return this._settledValueField; +}; + +var value = PromiseInspection.prototype.value = function () { + if (!this.isFulfilled()) { + throw new TypeError(INSPECTION_VALUE_ERROR); + } + return this._settledValue(); +}; + +var reason = PromiseInspection.prototype.error = +PromiseInspection.prototype.reason = function () { + if (!this.isRejected()) { + throw new TypeError(INSPECTION_REASON_ERROR); + } + return this._settledValue(); +}; + +var isFulfilled = PromiseInspection.prototype.isFulfilled = function() { + return (this._bitField & IS_FULFILLED) !== 0; +}; + +var isRejected = PromiseInspection.prototype.isRejected = function () { + return (this._bitField & IS_REJECTED) !== 0; +}; + +var isPending = PromiseInspection.prototype.isPending = function () { + return (this._bitField & IS_REJECTED_OR_FULFILLED_OR_CANCELLED) === 0; +}; + +var isResolved = PromiseInspection.prototype.isResolved = function () { + return (this._bitField & IS_REJECTED_OR_FULFILLED) !== 0; +}; + +PromiseInspection.prototype.isCancelled = function() { + return (this._bitField & IS_CANCELLED_OR_WILL_BE_CANCELLED) !== 0; +}; + +Promise.prototype.__isCancelled = function() { + return (this._bitField & IS_CANCELLED) === IS_CANCELLED; +}; + +Promise.prototype._isCancelled = function() { + return this._target().__isCancelled(); +}; + +Promise.prototype.isCancelled = function() { + return (this._target()._bitField & IS_CANCELLED_OR_WILL_BE_CANCELLED) !== 0; +}; + +Promise.prototype.isPending = function() { + return isPending.call(this._target()); +}; + +Promise.prototype.isRejected = function() { + return isRejected.call(this._target()); +}; + +Promise.prototype.isFulfilled = function() { + return isFulfilled.call(this._target()); +}; + +Promise.prototype.isResolved = function() { + return isResolved.call(this._target()); +}; + +Promise.prototype.value = function() { + return value.call(this._target()); +}; + +Promise.prototype.reason = function() { + var target = this._target(); + target._unsetRejectionIsUnhandled(); + return reason.call(target); +}; + +Promise.prototype._value = function() { + return this._settledValue(); +}; + +Promise.prototype._reason = function() { + this._unsetRejectionIsUnhandled(); + return this._settledValue(); +}; + +Promise.PromiseInspection = PromiseInspection; +}; diff --git a/src/thenables.js b/src/thenables.js new file mode 100644 index 0000000..9572e5c --- /dev/null +++ b/src/thenables.js @@ -0,0 +1,89 @@ +"use strict"; +module.exports = function(Promise, INTERNAL) { +var ASSERT = require("./assert"); +var util = require("./util"); +var errorObj = util.errorObj; +var isObject = util.isObject; + +function tryConvertToPromise(obj, context) { + if (isObject(obj)) { + if (obj instanceof Promise) return obj; + var then = getThen(obj); + if (then === errorObj) { + if (context) context._pushContext(); + var ret = Promise.reject(then.e); + if (context) context._popContext(); + return ret; + } else if (typeof then === "function") { + //Make casting from another bluebird fast + if (isAnyBluebirdPromise(obj)) { + var ret = new Promise(INTERNAL); + obj._then( + ret._fulfill, + ret._reject, + undefined, + ret, + null + ); + return ret; + } + return doThenable(obj, then, context); + } + } + return obj; +} + +function doGetThen(obj) { + return obj.then; +} + +function getThen(obj) { + try { + return doGetThen(obj); + } catch (e) { + errorObj.e = e; + return errorObj; + } +} + +var hasProp = {}.hasOwnProperty; +function isAnyBluebirdPromise(obj) { + try { + return hasProp.call(obj, "_promise0"); + } catch (e) { + return false; + } +} + +function doThenable(x, then, context) { + ASSERT(typeof then === "function"); + var promise = new Promise(INTERNAL); + var ret = promise; + if (context) context._pushContext(); + promise._captureStackTrace(); + if (context) context._popContext(); + var synchronous = true; + var result = util.tryCatch(then).call(x, resolve, reject); + synchronous = false; + + if (promise && result === errorObj) { + promise._rejectCallback(result.e, true, true); + promise = null; + } + + function resolve(value) { + if (!promise) return; + promise._resolveCallback(value); + promise = null; + } + + function reject(reason) { + if (!promise) return; + promise._rejectCallback(reason, synchronous, true); + promise = null; + } + return ret; +} + +return tryConvertToPromise; +}; diff --git a/src/timers.js b/src/timers.js new file mode 100644 index 0000000..25d6454 --- /dev/null +++ b/src/timers.js @@ -0,0 +1,93 @@ +"use strict"; +module.exports = function(Promise, INTERNAL, debug) { +var util = require("./util"); +var TimeoutError = Promise.TimeoutError; + +function HandleWrapper(handle) { + this.handle = handle; +} + +HandleWrapper.prototype._resultCancelled = function() { + clearTimeout(this.handle); +}; + +var afterValue = function(value) { return delay(+this).thenReturn(value); }; +var delay = Promise.delay = function (ms, value) { + var ret; + var handle; + if (value !== undefined) { + ret = Promise.resolve(value) + ._then(afterValue, null, null, ms, undefined); + if (debug.cancellation() && value instanceof Promise) { + ret._setOnCancel(value); + } + } else { + ret = new Promise(INTERNAL); + handle = setTimeout(function() { ret._fulfill(); }, +ms); + if (debug.cancellation()) { + ret._setOnCancel(new HandleWrapper(handle)); + } + ret._captureStackTrace(); + } + ret._setAsyncGuaranteed(); + return ret; +}; + +Promise.prototype.delay = function (ms) { + return delay(ms, this); +}; + +var afterTimeout = function (promise, message, parent) { + var err; + if (typeof message !== "string") { + if (message instanceof Error) { + err = message; + } else { + err = new TimeoutError(TIMEOUT_ERROR); + } + } else { + err = new TimeoutError(message); + } + util.markAsOriginatingFromRejection(err); + promise._attachExtraTrace(err); + promise._reject(err); + + if (parent != null) { + parent.cancel(); + } +}; + +function successClear(value) { + clearTimeout(this.handle); + return value; +} + +function failureClear(reason) { + clearTimeout(this.handle); + throw reason; +} + +Promise.prototype.timeout = function (ms, message) { + ms = +ms; + var ret, parent; + + var handleWrapper = new HandleWrapper(setTimeout(function timeoutTimeout() { + if (ret.isPending()) { + afterTimeout(ret, message, parent); + } + }, ms)); + + if (debug.cancellation()) { + parent = this.then(); + ret = parent._then(successClear, failureClear, + undefined, handleWrapper, undefined); + ret._setOnCancel(handleWrapper); + } else { + ret = this._then(successClear, failureClear, + undefined, handleWrapper, undefined); + } + + return ret; +}; + +}; diff --git a/src/using.js b/src/using.js new file mode 100644 index 0000000..9812b9f --- /dev/null +++ b/src/using.js @@ -0,0 +1,226 @@ +"use strict"; +module.exports = function (Promise, apiRejection, tryConvertToPromise, + createContext, INTERNAL, debug) { + var util = require("./util"); + var TypeError = require("./errors").TypeError; + var inherits = require("./util").inherits; + var errorObj = util.errorObj; + var tryCatch = util.tryCatch; + var NULL = {}; + + function thrower(e) { + setTimeout(function(){throw e;}, 0); + } + + function castPreservingDisposable(thenable) { + var maybePromise = tryConvertToPromise(thenable); + if (maybePromise !== thenable && + typeof thenable._isDisposable === "function" && + typeof thenable._getDisposer === "function" && + thenable._isDisposable()) { + maybePromise._setDisposable(thenable._getDisposer()); + } + return maybePromise; + } + function dispose(resources, inspection) { + var i = 0; + var len = resources.length; + var ret = new Promise(INTERNAL); + function iterator() { + if (i >= len) return ret._fulfill(); + var maybePromise = castPreservingDisposable(resources[i++]); + if (maybePromise instanceof Promise && + maybePromise._isDisposable()) { + try { + maybePromise = tryConvertToPromise( + maybePromise._getDisposer().tryDispose(inspection), + resources.promise); + } catch (e) { + return thrower(e); + } + if (maybePromise instanceof Promise) { + return maybePromise._then(iterator, thrower, + null, null, null); + } + } + iterator(); + } + iterator(); + return ret; + } + + function Disposer(data, promise, context) { + this._data = data; + this._promise = promise; + this._context = context; + } + + Disposer.prototype.data = function () { + return this._data; + }; + + Disposer.prototype.promise = function () { + return this._promise; + }; + + Disposer.prototype.resource = function () { + if (this.promise().isFulfilled()) { + return this.promise().value(); + } + return NULL; + }; + + Disposer.prototype.tryDispose = function(inspection) { + var resource = this.resource(); + var context = this._context; + if (context !== undefined) context._pushContext(); + var ret = resource !== NULL + ? this.doDispose(resource, inspection) : null; + if (context !== undefined) context._popContext(); + this._promise._unsetDisposable(); + this._data = null; + return ret; + }; + + Disposer.isDisposer = function (d) { + return (d != null && + typeof d.resource === "function" && + typeof d.tryDispose === "function"); + }; + + function FunctionDisposer(fn, promise, context) { + this.constructor$(fn, promise, context); + } + inherits(FunctionDisposer, Disposer); + + FunctionDisposer.prototype.doDispose = function (resource, inspection) { + var fn = this.data(); + return fn.call(resource, resource, inspection); + }; + + function maybeUnwrapDisposer(value) { + if (Disposer.isDisposer(value)) { + this.resources[this.index]._setDisposable(value); + return value.promise(); + } + return value; + } + + function ResourceList(length) { + this.length = length; + this.promise = null; + this[length-1] = null; + } + + ResourceList.prototype._resultCancelled = function() { + var len = this.length; + for (var i = 0; i < len; ++i) { + var item = this[i]; + if (item instanceof Promise) { + item.cancel(); + } + } + }; + + Promise.using = function () { + var len = arguments.length; + if (len < 2) return apiRejection( + "you must pass at least 2 arguments to Promise.using"); + var fn = arguments[len - 1]; + if (typeof fn !== "function") { + return apiRejection(FUNCTION_ERROR + util.classString(fn)); + } + var input; + var spreadArgs = true; + if (len === 2 && Array.isArray(arguments[0])) { + input = arguments[0]; + len = input.length; + spreadArgs = false; + } else { + input = arguments; + len--; + } + var resources = new ResourceList(len); + for (var i = 0; i < len; ++i) { + var resource = input[i]; + if (Disposer.isDisposer(resource)) { + var disposer = resource; + resource = resource.promise(); + resource._setDisposable(disposer); + } else { + var maybePromise = tryConvertToPromise(resource); + if (maybePromise instanceof Promise) { + resource = + maybePromise._then(maybeUnwrapDisposer, null, null, { + resources: resources, + index: i + }, undefined); + } + } + resources[i] = resource; + } + + var reflectedResources = new Array(resources.length); + for (var i = 0; i < reflectedResources.length; ++i) { + reflectedResources[i] = Promise.resolve(resources[i]).reflect(); + } + + var resultPromise = Promise.all(reflectedResources) + .then(function(inspections) { + for (var i = 0; i < inspections.length; ++i) { + var inspection = inspections[i]; + if (inspection.isRejected()) { + errorObj.e = inspection.error(); + return errorObj; + } else if (!inspection.isFulfilled()) { + resultPromise.cancel(); + return; + } + inspections[i] = inspection.value(); + } + promise._pushContext(); + + fn = tryCatch(fn); + var ret = spreadArgs + ? fn.apply(undefined, inspections) : fn(inspections); + var promiseCreated = promise._popContext(); + debug.checkForgottenReturns( + ret, promiseCreated, "Promise.using", promise); + return ret; + }); + + var promise = resultPromise.lastly(function() { + var inspection = new Promise.PromiseInspection(resultPromise); + return dispose(resources, inspection); + }); + resources.promise = promise; + promise._setOnCancel(resources); + return promise; + }; + + Promise.prototype._setDisposable = function (disposer) { + this._bitField = this._bitField | IS_DISPOSABLE; + this._disposer = disposer; + }; + + Promise.prototype._isDisposable = function () { + return (this._bitField & IS_DISPOSABLE) > 0; + }; + + Promise.prototype._getDisposer = function () { + return this._disposer; + }; + + Promise.prototype._unsetDisposable = function () { + this._bitField = this._bitField & (~IS_DISPOSABLE); + this._disposer = undefined; + }; + + Promise.prototype.disposer = function (fn) { + if (typeof fn === "function") { + return new FunctionDisposer(fn, this, createContext()); + } + throw new TypeError(); + }; + +}; diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..1ef4213 --- /dev/null +++ b/src/util.js @@ -0,0 +1,432 @@ +"use strict"; +var ASSERT = require("./assert"); +var es5 = require("./es5"); +// Assume CSP if browser +var canEvaluate = typeof navigator == "undefined"; + +//Try catch is not supported in optimizing +//compiler, so it is isolated +var errorObj = {e: {}}; +var tryCatchTarget; +var globalObject = typeof self !== "undefined" ? self : + typeof window !== "undefined" ? window : + typeof global !== "undefined" ? global : + this !== undefined ? this : null; + +function tryCatcher() { + try { + var target = tryCatchTarget; + tryCatchTarget = null; + return target.apply(this, arguments); + } catch (e) { + errorObj.e = e; + return errorObj; + } +} +function tryCatch(fn) { + ASSERT(typeof fn === "function"); + tryCatchTarget = fn; + return tryCatcher; +} + +//Un-magical enough that using this doesn't prevent +//extending classes from outside using any convention +var inherits = function(Child, Parent) { + var hasProp = {}.hasOwnProperty; + + function T() { + this.constructor = Child; + this.constructor$ = Parent; + for (var propertyName in Parent.prototype) { + if (hasProp.call(Parent.prototype, propertyName) && + propertyName.charAt(propertyName.length-1) !== "$" + ) { + this[propertyName + "$"] = Parent.prototype[propertyName]; + } + } + } + T.prototype = Parent.prototype; + Child.prototype = new T(); + return Child.prototype; +}; + + +function isPrimitive(val) { + return val == null || val === true || val === false || + typeof val === "string" || typeof val === "number"; + +} + +function isObject(value) { + return typeof value === "function" || + typeof value === "object" && value !== null; +} + +function maybeWrapAsError(maybeError) { + if (!isPrimitive(maybeError)) return maybeError; + + return new Error(safeToString(maybeError)); +} + +function withAppended(target, appendee) { + var len = target.length; + var ret = new Array(len + 1); + var i; + for (i = 0; i < len; ++i) { + ret[i] = target[i]; + } + ret[i] = appendee; + return ret; +} + +function getDataPropertyOrDefault(obj, key, defaultValue) { + if (es5.isES5) { + var desc = Object.getOwnPropertyDescriptor(obj, key); + + if (desc != null) { + return desc.get == null && desc.set == null + ? desc.value + : defaultValue; + } + } else { + return {}.hasOwnProperty.call(obj, key) ? obj[key] : undefined; + } +} + +function notEnumerableProp(obj, name, value) { + if (isPrimitive(obj)) return obj; + var descriptor = { + value: value, + configurable: true, + enumerable: false, + writable: true + }; + es5.defineProperty(obj, name, descriptor); + return obj; +} + +function thrower(r) { + throw r; +} + +var inheritedDataKeys = (function() { + var excludedPrototypes = [ + Array.prototype, + Object.prototype, + Function.prototype + ]; + + var isExcludedProto = function(val) { + for (var i = 0; i < excludedPrototypes.length; ++i) { + if (excludedPrototypes[i] === val) { + return true; + } + } + return false; + }; + + if (es5.isES5) { + var getKeys = Object.getOwnPropertyNames; + return function(obj) { + var ret = []; + var visitedKeys = Object.create(null); + while (obj != null && !isExcludedProto(obj)) { + var keys; + try { + keys = getKeys(obj); + } catch (e) { + return ret; + } + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + if (visitedKeys[key]) continue; + visitedKeys[key] = true; + var desc = Object.getOwnPropertyDescriptor(obj, key); + if (desc != null && desc.get == null && desc.set == null) { + ret.push(key); + } + } + obj = es5.getPrototypeOf(obj); + } + return ret; + }; + } else { + var hasProp = {}.hasOwnProperty; + return function(obj) { + if (isExcludedProto(obj)) return []; + var ret = []; + + /*jshint forin:false */ + enumeration: for (var key in obj) { + if (hasProp.call(obj, key)) { + ret.push(key); + } else { + for (var i = 0; i < excludedPrototypes.length; ++i) { + if (hasProp.call(excludedPrototypes[i], key)) { + continue enumeration; + } + } + ret.push(key); + } + } + return ret; + }; + } + +})(); + +var thisAssignmentPattern = /this\s*\.\s*\S+\s*=/; +function isClass(fn) { + try { + if (typeof fn === "function") { + var keys = es5.names(fn.prototype); + + var hasMethods = es5.isES5 && keys.length > 1; + var hasMethodsOtherThanConstructor = keys.length > 0 && + !(keys.length === 1 && keys[0] === "constructor"); + var hasThisAssignmentAndStaticMethods = + thisAssignmentPattern.test(fn + "") && es5.names(fn).length > 0; + + if (hasMethods || hasMethodsOtherThanConstructor || + hasThisAssignmentAndStaticMethods) { + return true; + } + } + return false; + } catch (e) { + return false; + } +} + +function toFastProperties(obj) { + /*jshint -W027,-W055,-W031*/ + function FakeConstructor() {} + FakeConstructor.prototype = obj; + var receiver = new FakeConstructor(); + function ic() { + return typeof receiver.foo; + } + ic(); + ic(); + ASSERT("%HasFastProperties", true, obj); + return obj; + // Prevent the function from being optimized through dead code elimination + // or further optimizations. This code is never reached but even using eval + // in unreachable code causes v8 to not optimize functions. + eval(obj); +} + +var rident = /^[a-z$_][a-z$_0-9]*$/i; +function isIdentifier(str) { + return rident.test(str); +} + +function filledRange(count, prefix, suffix) { + var ret = new Array(count); + for(var i = 0; i < count; ++i) { + ret[i] = prefix + i + suffix; + } + return ret; +} + +function safeToString(obj) { + try { + return obj + ""; + } catch (e) { + return "[no string representation]"; + } +} + +function isError(obj) { + return obj instanceof Error || + (obj !== null && + typeof obj === "object" && + typeof obj.message === "string" && + typeof obj.name === "string"); +} + +function markAsOriginatingFromRejection(e) { + try { + notEnumerableProp(e, OPERATIONAL_ERROR_KEY, true); + } + catch(ignore) {} +} + +function originatesFromRejection(e) { + if (e == null) return false; + return ((e instanceof Error[BLUEBIRD_ERRORS].OperationalError) || + e[OPERATIONAL_ERROR_KEY] === true); +} + +function canAttachTrace(obj) { + return isError(obj) && es5.propertyIsWritable(obj, "stack"); +} + +var ensureErrorObject = (function() { + if (!("stack" in new Error())) { + return function(value) { + if (canAttachTrace(value)) return value; + try {throw new Error(safeToString(value));} + catch(err) {return err;} + }; + } else { + return function(value) { + if (canAttachTrace(value)) return value; + return new Error(safeToString(value)); + }; + } +})(); + +function classString(obj) { + return {}.toString.call(obj); +} + +function copyDescriptors(from, to, filter) { + var keys = es5.names(from); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + if (filter(key)) { + try { + es5.defineProperty(to, key, es5.getDescriptor(from, key)); + } catch (ignore) {} + } + } +} + +var asArray = function(v) { + if (es5.isArray(v)) { + return v; + } + return null; +}; + +if (typeof Symbol !== "undefined" && Symbol.iterator) { + var ArrayFrom = typeof Array.from === "function" ? function(v) { + return Array.from(v); + } : function(v) { + var ret = []; + var it = v[Symbol.iterator](); + var itResult; + while (!((itResult = it.next()).done)) { + ret.push(itResult.value); + } + return ret; + }; + + asArray = function(v) { + if (es5.isArray(v)) { + return v; + } else if (v != null && typeof v[Symbol.iterator] === "function") { + return ArrayFrom(v); + } + return null; + }; +} + +var isNode = typeof process !== "undefined" && + classString(process).toLowerCase() === "[object process]"; + +var hasEnvVariables = typeof process !== "undefined" && + typeof process.env !== "undefined"; + +function env(key) { + return hasEnvVariables ? process.env[key] : undefined; +} + +function getNativePromise() { + if (typeof Promise === "function") { + try { + var promise = new Promise(function(){}); + if (classString(promise) === "[object Promise]") { + return Promise; + } + } catch (e) {} + } +} + +var reflectHandler; +function contextBind(ctx, cb) { + if (ctx === null || + typeof cb !== "function" || + cb === reflectHandler) { + return cb; + } + + if (ctx.domain !== null) { + cb = ctx.domain.bind(cb); + } + + var async = ctx.async; + if (async !== null) { + var old = cb; + cb = function() { + INLINE_SLICE_LEFT_PADDED(2, args, arguments); + args[0] = old; + args[1] = this; + return async.runInAsyncScope.apply(async, args); + }; + } + return cb; +} + +var ret = { + setReflectHandler: function(fn) { + reflectHandler = fn; + }, + isClass: isClass, + isIdentifier: isIdentifier, + inheritedDataKeys: inheritedDataKeys, + getDataPropertyOrDefault: getDataPropertyOrDefault, + thrower: thrower, + isArray: es5.isArray, + asArray: asArray, + notEnumerableProp: notEnumerableProp, + isPrimitive: isPrimitive, + isObject: isObject, + isError: isError, + canEvaluate: canEvaluate, + errorObj: errorObj, + tryCatch: tryCatch, + inherits: inherits, + withAppended: withAppended, + maybeWrapAsError: maybeWrapAsError, + toFastProperties: toFastProperties, + filledRange: filledRange, + toString: safeToString, + canAttachTrace: canAttachTrace, + ensureErrorObject: ensureErrorObject, + originatesFromRejection: originatesFromRejection, + markAsOriginatingFromRejection: markAsOriginatingFromRejection, + classString: classString, + copyDescriptors: copyDescriptors, + isNode: isNode, + hasEnvVariables: hasEnvVariables, + env: env, + global: globalObject, + getNativePromise: getNativePromise, + contextBind: contextBind +}; +ret.isRecentNode = ret.isNode && (function() { + var version; + if (process.versions && process.versions.node) { + version = process.versions.node.split(".").map(Number); + } else if (process.version) { + version = process.version.split(".").map(Number); + } + return (version[0] === 0 && version[1] > 10) || (version[0] > 0); +})(); +ret.nodeSupportsAsyncResource = ret.isNode && (function() { + var supportsAsync = false; + try { + var res = require("async_hooks").AsyncResource; + supportsAsync = typeof res.prototype.runInAsyncScope === "function"; + } catch (e) { + supportsAsync = false; + } + return supportsAsync; +})(); + +if (ret.isNode) ret.toFastProperties(process); + +try {throw new Error(); } catch (e) {ret.lastLineError = e;} +module.exports = ret; diff --git a/test/mocha/2.1.2.js b/test/mocha/2.1.2.js new file mode 100644 index 0000000..eeaa32a --- /dev/null +++ b/test/mocha/2.1.2.js @@ -0,0 +1,76 @@ +"use strict"; + +var assert = require("assert"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; + +var adapter = global.adapter; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it + +describe("2.1.2.1: When fulfilled, a promise: must not transition to any other state.", function () { + testFulfilled(dummy, function (promise, done) { + var onFulfilledCalled = false; + + promise.then(function onFulfilled() { + onFulfilledCalled = true; + }, function onRejected() { + assert.strictEqual(onFulfilledCalled, false); + done(); + }); + + setTimeout(function(){done();}, 100); + }); + + specify("trying to fulfill then immediately reject", function (done) { + var tuple = pending(); + var onFulfilledCalled = false; + + tuple.promise.then(function onFulfilled() { + onFulfilledCalled = true; + }, function onRejected() { + assert.strictEqual(onFulfilledCalled, false); + done(); + }); + + tuple.fulfill(dummy); + tuple.reject(dummy); + setTimeout(function(){done();}, 100); + }); + + specify("trying to fulfill then reject, delayed", function (done) { + var tuple = pending(); + var onFulfilledCalled = false; + + tuple.promise.then(function onFulfilled() { + onFulfilledCalled = true; + }, function onRejected() { + assert.strictEqual(onFulfilledCalled, false); + done(); + }); + + setTimeout(function () { + tuple.fulfill(dummy); + tuple.reject(dummy); + }, 50); + setTimeout(function(){done();}, 100); + }); + + specify("trying to fulfill immediately then reject delayed", function (done) { + var tuple = pending(); + var onFulfilledCalled = false; + + tuple.promise.then(function onFulfilled() { + onFulfilledCalled = true; + }, function onRejected() { + assert.strictEqual(onFulfilledCalled, false); + done(); + }); + + tuple.fulfill(dummy); + setTimeout(function () { + tuple.reject(dummy); + }, 50); + setTimeout(function(){done();}, 100); + }); +}); diff --git a/test/mocha/2.1.3.js b/test/mocha/2.1.3.js new file mode 100644 index 0000000..0013481 --- /dev/null +++ b/test/mocha/2.1.3.js @@ -0,0 +1,76 @@ +"use strict"; + +var assert = require("assert"); +var testRejected = require("./helpers/testThreeCases").testRejected; + +var adapter = global.adapter; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it + +describe("2.1.3.1: When rejected, a promise: must not transition to any other state.", function () { + testRejected(dummy, function (promise, done) { + var onRejectedCalled = false; + + promise.then(function onFulfilled() { + assert.strictEqual(onRejectedCalled, false); + done(); + }, function onRejected() { + onRejectedCalled = true; + }); + + setTimeout(function(){done();}, 100); + }); + + specify("trying to reject then immediately fulfill", function (done) { + var tuple = pending(); + var onRejectedCalled = false; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(onRejectedCalled, false); + done(); + }, function onRejected() { + onRejectedCalled = true; + }); + + tuple.reject(dummy); + tuple.fulfill(dummy); + setTimeout(function(){done();}, 100); + }); + + specify("trying to reject then fulfill, delayed", function (done) { + var tuple = pending(); + var onRejectedCalled = false; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(onRejectedCalled, false); + done(); + }, function onRejected() { + onRejectedCalled = true; + }); + + setTimeout(function () { + tuple.reject(dummy); + tuple.fulfill(dummy); + }, 50); + setTimeout(function(){done();}, 100); + }); + + specify("trying to reject immediately then fulfill delayed", function (done) { + var tuple = pending(); + var onRejectedCalled = false; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(onRejectedCalled, false); + done(); + }, function onRejected() { + onRejectedCalled = true; + }); + + tuple.reject(dummy); + setTimeout(function () { + tuple.fulfill(dummy); + }, 50); + setTimeout(function(){done();}, 100); + }); +}); diff --git a/test/mocha/2.2.1.js b/test/mocha/2.2.1.js new file mode 100644 index 0000000..cfe1012 --- /dev/null +++ b/test/mocha/2.2.1.js @@ -0,0 +1,41 @@ +"use strict"; + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it + +describe("2.2.1: Both `onFulfilled` and `onRejected` are optional arguments.", function () { + describe("2.2.1.1: If `onFulfilled` is not a function, it must be ignored.", function () { + function testNonFunction(nonFunction, stringRepresentation) { + specify("`onFulfilled` is " + stringRepresentation, function (done) { + rejected(dummy).then(nonFunction, function () { + done(); + }); + }); + } + + testNonFunction(undefined, "`undefined`"); + testNonFunction(null, "`null`"); + testNonFunction(false, "`false`"); + testNonFunction(5, "`5`"); + testNonFunction({}, "an object"); + }); + + describe("2.2.1.2: If `onRejected` is not a function, it must be ignored.", function () { + function testNonFunction(nonFunction, stringRepresentation) { + specify("`onRejected` is " + stringRepresentation, function (done) { + fulfilled(dummy).then(function () { + done(); + }, nonFunction); + }); + } + + testNonFunction(undefined, "`undefined`"); + testNonFunction(null, "`null`"); + testNonFunction(false, "`false`"); + testNonFunction(5, "`5`"); + testNonFunction({}, "an object"); + }); +}); diff --git a/test/mocha/2.2.2.js b/test/mocha/2.2.2.js new file mode 100644 index 0000000..29683e5 --- /dev/null +++ b/test/mocha/2.2.2.js @@ -0,0 +1,151 @@ +"use strict"; + +var assert = require("assert"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality + +describe("2.2.2: If `onFulfilled` is a function,", function () { + describe("2.2.2.1: it must be called after `promise` is fulfilled, with `promise`’s fulfillment value as its " + + "first argument.", function () { + testFulfilled(sentinel, function (promise, done) { + promise.then(function onFulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("2.2.2.2: it must not be called before `promise` is fulfilled", function () { + specify("fulfilled after a delay", function (done) { + var tuple = pending(); + var isFulfilled = false; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(isFulfilled, true); + done(); + }); + + setTimeout(function () { + tuple.fulfill(dummy); + isFulfilled = true; + }, 50); + }); + + specify("never fulfilled", function (done) { + var tuple = pending(); + var onFulfilledCalled = false; + + tuple.promise.then(function onFulfilled() { + onFulfilledCalled = true; + done(); + }); + + setTimeout(function () { + assert.strictEqual(onFulfilledCalled, false); + done(); + }, 150); + }); + }); + + describe("2.2.2.3: it must not be called more than once.", function () { + specify("already-fulfilled", function (done) { + var timesCalled = 0; + + fulfilled(dummy).then(function onFulfilled() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + }); + + specify("trying to fulfill a pending promise more than once, immediately", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + tuple.fulfill(dummy); + tuple.fulfill(dummy); + }); + + specify("trying to fulfill a pending promise more than once, delayed", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + setTimeout(function () { + tuple.fulfill(dummy); + tuple.fulfill(dummy); + }, 50); + }); + + specify("trying to fulfill a pending promise more than once, immediately then delayed", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + tuple.fulfill(dummy); + setTimeout(function () { + tuple.fulfill(dummy); + }, 50); + }); + + specify("when multiple `then` calls are made, spaced apart in time", function (done) { + var tuple = pending(); + var timesCalled = [0, 0, 0]; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[0], 1); + }); + + setTimeout(function () { + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[1], 1); + }); + }, 50); + + setTimeout(function () { + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[2], 1); + done(); + }); + }, 100); + + setTimeout(function () { + tuple.fulfill(dummy); + }, 150); + }); + + specify("when `then` is interleaved with fulfillment", function (done) { + var tuple = pending(); + var timesCalled = [0, 0]; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[0], 1); + }); + + tuple.fulfill(dummy); + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[1], 1); + done(); + }); + }); + }); +}); diff --git a/test/mocha/2.2.3.js b/test/mocha/2.2.3.js new file mode 100644 index 0000000..d2a7109 --- /dev/null +++ b/test/mocha/2.2.3.js @@ -0,0 +1,151 @@ +"use strict"; + +var assert = require("assert"); +var testRejected = require("./helpers/testThreeCases").testRejected; + +var adapter = global.adapter; +var rejected = adapter.rejected; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality + +describe("2.2.3: If `onRejected` is a function,", function () { + describe("2.2.3.1: it must be called after `promise` is rejected, with `promise`’s rejection reason as its " + + "first argument.", function () { + testRejected(sentinel, function (promise, done) { + promise.then(null, function onRejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("2.2.3.2: it must not be called before `promise` is rejected", function () { + specify("rejected after a delay", function (done) { + var tuple = pending(); + var isRejected = false; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(isRejected, true); + done(); + }); + + setTimeout(function () { + tuple.reject(dummy); + isRejected = true; + }, 50); + }); + + specify("never rejected", function (done) { + var tuple = pending(); + var onRejectedCalled = false; + + tuple.promise.then(null, function onRejected() { + onRejectedCalled = true; + done(); + }); + + setTimeout(function () { + assert.strictEqual(onRejectedCalled, false); + done(); + }, 150); + }); + }); + + describe("2.2.3.3: it must not be called more than once.", function () { + specify("already-rejected", function (done) { + var timesCalled = 0; + + rejected(dummy).then(null, function onRejected() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + }); + + specify("trying to reject a pending promise more than once, immediately", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + tuple.reject(dummy); + tuple.reject(dummy); + }); + + specify("trying to reject a pending promise more than once, delayed", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + setTimeout(function () { + tuple.reject(dummy); + tuple.reject(dummy); + }, 50); + }); + + specify("trying to reject a pending promise more than once, immediately then delayed", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + tuple.reject(dummy); + setTimeout(function () { + tuple.reject(dummy); + }, 50); + }); + + specify("when multiple `then` calls are made, spaced apart in time", function (done) { + var tuple = pending(); + var timesCalled = [0, 0, 0]; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[0], 1); + }); + + setTimeout(function () { + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[1], 1); + }); + }, 50); + + setTimeout(function () { + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[2], 1); + done(); + }); + }, 100); + + setTimeout(function () { + tuple.reject(dummy); + }, 150); + }); + + specify("when `then` is interleaved with rejection", function (done) { + var tuple = pending(); + var timesCalled = [0, 0]; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[0], 1); + }); + + tuple.reject(dummy); + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[1], 1); + done(); + }); + }); + }); +}); diff --git a/test/mocha/2.2.4.js b/test/mocha/2.2.4.js new file mode 100644 index 0000000..5f953f2 --- /dev/null +++ b/test/mocha/2.2.4.js @@ -0,0 +1,182 @@ +"use strict"; + +var assert = require("assert"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it + +describe("2.2.4: `onFulfilled` or `onRejected` must not be called until the execution context stack contains only " + + "platform code.", function () { + describe("`then` returns before the promise becomes fulfilled or rejected", function () { + testFulfilled(dummy, function (promise, done) { + var thenHasReturned = false; + + promise.then(function onFulfilled() { + assert.strictEqual(thenHasReturned, true); + done(); + }); + + thenHasReturned = true; + }); + testRejected(dummy, function (promise, done) { + var thenHasReturned = false; + + promise.then(null, function onRejected() { + assert.strictEqual(thenHasReturned, true); + done(); + }); + + thenHasReturned = true; + }); + }); + + describe("Clean-stack execution ordering tests (fulfillment case)", function () { + specify("when `onFulfilled` is added immediately before the promise is fulfilled", + function () { + var tuple = pending(); + var onFulfilledCalled = false; + + tuple.promise.then(function onFulfilled() { + onFulfilledCalled = true; + }); + + tuple.fulfill(dummy); + + assert.strictEqual(onFulfilledCalled, false); + }); + + specify("when `onFulfilled` is added immediately after the promise is fulfilled", + function () { + var tuple = pending(); + var onFulfilledCalled = false; + + tuple.fulfill(dummy); + + tuple.promise.then(function onFulfilled() { + onFulfilledCalled = true; + }); + + assert.strictEqual(onFulfilledCalled, false); + }); + + specify("when one `onFulfilled` is added inside another `onFulfilled`", function (done) { + var promise = fulfilled(); + var firstOnFulfilledFinished = false; + + promise.then(function () { + promise.then(function () { + assert.strictEqual(firstOnFulfilledFinished, true); + done(); + }); + firstOnFulfilledFinished = true; + }); + }); + + specify("when `onFulfilled` is added inside an `onRejected`", function (done) { + var promise = rejected(); + var promise2 = fulfilled(); + var firstOnRejectedFinished = false; + + promise.then(null, function () { + promise2.then(function () { + assert.strictEqual(firstOnRejectedFinished, true); + done(); + }); + firstOnRejectedFinished = true; + }); + }); + + specify("when the promise is fulfilled asynchronously", function (done) { + var tuple = pending(); + var firstStackFinished = false; + + setTimeout(function () { + tuple.fulfill(dummy); + firstStackFinished = true; + }, 0); + + tuple.promise.then(function () { + assert.strictEqual(firstStackFinished, true); + done(); + }); + }); + }); + + describe("Clean-stack execution ordering tests (rejection case)", function () { + specify("when `onRejected` is added immediately before the promise is rejected", + function () { + var tuple = pending(); + var onRejectedCalled = false; + + tuple.promise.then(null, function onRejected() { + onRejectedCalled = true; + }); + + tuple.reject(dummy); + + assert.strictEqual(onRejectedCalled, false); + }); + + specify("when `onRejected` is added immediately after the promise is rejected", + function () { + var tuple = pending(); + var onRejectedCalled = false; + + tuple.reject(dummy); + + tuple.promise.then(null, function onRejected() { + onRejectedCalled = true; + }); + + assert.strictEqual(onRejectedCalled, false); + }); + + specify("when `onRejected` is added inside an `onFulfilled`", function (done) { + var promise = fulfilled(); + var promise2 = rejected(); + var firstOnFulfilledFinished = false; + + promise.then(function () { + promise2.then(null, function () { + assert.strictEqual(firstOnFulfilledFinished, true); + done(); + }); + firstOnFulfilledFinished = true; + }); + }); + + specify("when one `onRejected` is added inside another `onRejected`", function (done) { + var promise = rejected(); + var firstOnRejectedFinished = false; + + promise.then(null, function () { + promise.then(null, function () { + assert.strictEqual(firstOnRejectedFinished, true); + done(); + }); + firstOnRejectedFinished = true; + }); + }); + + specify("when the promise is rejected asynchronously", function (done) { + var tuple = pending(); + var firstStackFinished = false; + + setTimeout(function () { + tuple.reject(dummy); + firstStackFinished = true; + }, 0); + + tuple.promise.then(null, function () { + assert.strictEqual(firstStackFinished, true); + done(); + }); + }); + }); +}); diff --git a/test/mocha/2.2.5.js b/test/mocha/2.2.5.js new file mode 100644 index 0000000..d40aa8a --- /dev/null +++ b/test/mocha/2.2.5.js @@ -0,0 +1,58 @@ +/*jshint strict: false */ + +var assert = require("assert"); + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it + +var undefinedThisStrict = (function() { + "use strict"; + return this; +})(); + +var undefinedThisSloppy = (function() { + return this; +})(); + +describe("2.2.5 `onFulfilled` and `onRejected` must be called as functions (i.e. with no `this` value).", function () { + describe("strict mode", function () { + specify("fulfilled", function (done) { + fulfilled(dummy).then(function onFulfilled() { + "use strict"; + + assert(this === undefinedThisStrict || + this === undefinedThisSloppy); + done(); + }); + }); + + specify("rejected", function (done) { + rejected(dummy).then(null, function onRejected() { + "use strict"; + + assert(this === undefinedThisStrict || + this === undefinedThisSloppy); + done(); + }); + }); + }); + + describe("sloppy mode", function () { + specify("fulfilled", function (done) { + fulfilled(dummy).then(function onFulfilled() { + assert.strictEqual(this, undefinedThisSloppy); + done(); + }); + }); + + specify("rejected", function (done) { + rejected(dummy).then(null, function onRejected() { + assert.strictEqual(this, undefinedThisSloppy); + done(); + }); + }); + }); +}); diff --git a/test/mocha/2.2.6.js b/test/mocha/2.2.6.js new file mode 100644 index 0000000..74b558c --- /dev/null +++ b/test/mocha/2.2.6.js @@ -0,0 +1,257 @@ +"use strict"; + +var assert = require("assert"); +var sinon = require("sinon"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var other = { other: "other" }; // a value we don't want to be strict equal to +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality +var sentinel2 = { sentinel2: "sentinel2" }; +var sentinel3 = { sentinel3: "sentinel3" }; + +function callbackAggregator(times, ultimateCallback) { + var soFar = 0; + return function () { + if (++soFar === times) { + ultimateCallback(); + } + }; +} + +describe("2.2.6: `then` may be called multiple times on the same promise.", function () { + describe("2.2.6.1: If/when `promise` is fulfilled, all respective `onFulfilled` callbacks must execute in the " + + "order of their originating calls to `then`.", function () { + describe("multiple boring fulfillment handlers", function () { + testFulfilled(sentinel, function (promise, done) { + var handler1 = sinon.stub().returns(other); + var handler2 = sinon.stub().returns(other); + var handler3 = sinon.stub().returns(other); + + var spy = sinon.spy(); + promise.then(handler1, spy); + promise.then(handler2, spy); + promise.then(handler3, spy); + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + + sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); + sinon.assert.notCalled(spy); + + done(); + }); + }); + }); + + describe("multiple fulfillment handlers, one of which throws", function () { + testFulfilled(sentinel, function (promise, done) { + var handler1 = sinon.stub().returns(other); + var handler2 = sinon.stub().throws(other); + var handler3 = sinon.stub().returns(other); + + var spy = sinon.spy(); + promise.then(handler1, spy); + promise.then(handler2, spy).caught(function(){}); + promise.then(handler3, spy); + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + + sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); + sinon.assert.notCalled(spy); + + done(); + }); + }); + }); + + describe("results in multiple branching chains with their own fulfillment values", function () { + testFulfilled(dummy, function (promise, done) { + var semiDone = callbackAggregator(3, done); + + promise.then(function () { + return sentinel; + }).then(function (value) { + assert.strictEqual(value, sentinel); + semiDone(); + }); + + promise.then(function () { + throw sentinel2; + }).then(null, function (reason) { + assert.strictEqual(reason, sentinel2); + semiDone(); + }); + + promise.then(function () { + return sentinel3; + }).then(function (value) { + assert.strictEqual(value, sentinel3); + semiDone(); + }); + }); + }); + + describe("`onFulfilled` handlers are called in the original order", function () { + testFulfilled(dummy, function (promise, done) { + var handler1 = sinon.spy(function handler1() {}); + var handler2 = sinon.spy(function handler2() {}); + var handler3 = sinon.spy(function handler3() {}); + + promise.then(handler1); + promise.then(handler2); + promise.then(handler3); + + promise.then(function () { + sinon.assert.callOrder(handler1, handler2, handler3); + done(); + }); + }); + + describe("even when one handler is added inside another handler", function () { + testFulfilled(dummy, function (promise, done) { + var handler1 = sinon.spy(function handler1() {}); + var handler2 = sinon.spy(function handler2() {}); + var handler3 = sinon.spy(function handler3() {}); + + promise.then(function () { + handler1(); + promise.then(handler3); + }); + promise.then(handler2); + + promise.then(function () { + // Give implementations a bit of extra time to flush their internal queue, if necessary. + setTimeout(function () { + sinon.assert.callOrder(handler1, handler2, handler3); + done(); + }, 15); + }); + }); + }); + }); + }); + + describe("2.2.6.2: If/when `promise` is rejected, all respective `onRejected` callbacks must execute in the " + + "order of their originating calls to `then`.", function () { + describe("multiple boring rejection handlers", function () { + testRejected(sentinel, function (promise, done) { + var handler1 = sinon.stub().returns(other); + var handler2 = sinon.stub().returns(other); + var handler3 = sinon.stub().returns(other); + + var spy = sinon.spy(); + promise.then(spy, handler1); + promise.then(spy, handler2); + promise.then(spy, handler3); + + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + + sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); + sinon.assert.notCalled(spy); + + done(); + }); + }); + }); + + describe("multiple rejection handlers, one of which throws", function () { + testRejected(sentinel, function (promise, done) { + var handler1 = sinon.stub().returns(other); + var handler2 = sinon.stub().throws(other); + var handler3 = sinon.stub().returns(other); + + var spy = sinon.spy(); + promise.then(spy, handler1); + promise.then(spy, handler2).caught(function(){}); + promise.then(spy, handler3); + + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + + sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); + sinon.assert.notCalled(spy); + + done(); + }); + }); + }); + + describe("results in multiple branching chains with their own fulfillment values", function () { + testRejected(sentinel, function (promise, done) { + var semiDone = callbackAggregator(3, done); + + promise.then(null, function () { + return sentinel; + }).then(function (value) { + assert.strictEqual(value, sentinel); + semiDone(); + }); + + promise.then(null, function () { + throw sentinel2; + }).then(null, function (reason) { + assert.strictEqual(reason, sentinel2); + semiDone(); + }); + + promise.then(null, function () { + return sentinel3; + }).then(function (value) { + assert.strictEqual(value, sentinel3); + semiDone(); + }); + }); + }); + + describe("`onRejected` handlers are called in the original order", function () { + testRejected(dummy, function (promise, done) { + var handler1 = sinon.spy(function handler1() {}); + var handler2 = sinon.spy(function handler2() {}); + var handler3 = sinon.spy(function handler3() {}); + + promise.then(null, handler1); + promise.then(null, handler2); + promise.then(null, handler3); + + promise.then(null, function () { + sinon.assert.callOrder(handler1, handler2, handler3); + done(); + }); + }); + + describe("even when one handler is added inside another handler", function () { + testRejected(dummy, function (promise, done) { + var handler1 = sinon.spy(function handler1() {}); + var handler2 = sinon.spy(function handler2() {}); + var handler3 = sinon.spy(function handler3() {}); + + promise.then(null, function () { + handler1(); + promise.then(null, handler3); + }); + promise.then(null, handler2); + + promise.then(null, function () { + // Give implementations a bit of extra time to flush their internal queue, if necessary. + setTimeout(function () { + sinon.assert.callOrder(handler1, handler2, handler3); + done(); + }, 15); + }); + }); + }); + }); + }); +}); diff --git a/test/mocha/2.2.7.js b/test/mocha/2.2.7.js new file mode 100644 index 0000000..a9ef1f6 --- /dev/null +++ b/test/mocha/2.2.7.js @@ -0,0 +1,109 @@ +"use strict"; + +var assert = require("assert"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; +var reasons = require("./helpers/reasons"); + +var adapter = global.adapter; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality +var other = { other: "other" }; // a value we don't want to be strict equal to + +describe("2.2.7: `then` must return a promise: `promise2 = promise1.then(onFulfilled, onRejected)`", function () { + specify("is a promise", function () { + var promise1 = pending().promise; + var promise2 = promise1.then(); + + assert(typeof promise2 === "object" || typeof promise2 === "function"); + assert.notStrictEqual(promise2, null); + assert.strictEqual(typeof promise2.then, "function"); + }); + + describe("2.2.7.1: If either `onFulfilled` or `onRejected` returns a value `x`, run the Promise Resolution " + + "Procedure `[[Resolve]](promise2, x)`", function () { + specify("see separate 3.3 tests", function () { }); + }); + + describe("2.2.7.2: If either `onFulfilled` or `onRejected` throws an exception `e`, `promise2` must be rejected " + + "with `e` as the reason.", function () { + function testReason(expectedReason, stringRepresentation) { + describe("The reason is " + stringRepresentation, function () { + testFulfilled(dummy, function (promise1, done) { + var promise2 = promise1.then(function onFulfilled() { + throw expectedReason; + }); + + promise2.then(null, function onPromise2Rejected(actualReason) { + assert.strictEqual(actualReason, expectedReason); + done(); + }); + }); + testRejected(dummy, function (promise1, done) { + var promise2 = promise1.then(null, function onRejected() { + throw expectedReason; + }); + + promise2.then(null, function onPromise2Rejected(actualReason) { + assert.strictEqual(actualReason, expectedReason); + done(); + }); + }); + }); + } + + Object.keys(reasons).forEach(function (stringRepresentation) { + testReason(reasons[stringRepresentation], stringRepresentation); + }); + }); + + describe("2.2.7.3: If `onFulfilled` is not a function and `promise1` is fulfilled, `promise2` must be fulfilled " + + "with the same value.", function () { + + function testNonFunction(nonFunction, stringRepresentation) { + describe("`onFulfilled` is " + stringRepresentation, function () { + testFulfilled(sentinel, function (promise1, done) { + var promise2 = promise1.then(nonFunction); + + promise2.then(function onPromise2Fulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + } + + testNonFunction(undefined, "`undefined`"); + testNonFunction(null, "`null`"); + testNonFunction(false, "`false`"); + testNonFunction(5, "`5`"); + testNonFunction({}, "an object"); + testNonFunction([function () { return other; }], "an array containing a function"); + }); + + describe("2.2.7.4: If `onRejected` is not a function and `promise1` is rejected, `promise2` must be rejected " + + "with the same reason.", function () { + + function testNonFunction(nonFunction, stringRepresentation) { + describe("`onRejected` is " + stringRepresentation, function () { + testRejected(sentinel, function (promise1, done) { + var promise2 = promise1.then(null, nonFunction); + + promise2.then(null, function onPromise2Rejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + } + + testNonFunction(undefined, "`undefined`"); + testNonFunction(null, "`null`"); + testNonFunction(false, "`false`"); + testNonFunction(5, "`5`"); + testNonFunction({}, "an object"); + testNonFunction([function () { return other; }], "an array containing a function"); + }); +}); diff --git a/test/mocha/2.3.1.js b/test/mocha/2.3.1.js new file mode 100644 index 0000000..6a1b8ff --- /dev/null +++ b/test/mocha/2.3.1.js @@ -0,0 +1,34 @@ +"use strict"; + +var assert = require("assert"); + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it + +describe("2.3.1: If `promise` and `x` refer to the same object, reject `promise` with a `TypeError' as the reason.", + function () { + specify("via return from a fulfilled promise", function (done) { + var promise = fulfilled(dummy).then(function () { + return promise; + }); + + promise.then(null, function (reason) { + assert(reason instanceof adapter.TypeError); + done(); + }); + }); + + specify("via return from a rejected promise", function (done) { + var promise = rejected(dummy).then(null, function () { + return promise; + }); + + promise.then(null, function (reason) { + assert(reason instanceof adapter.TypeError); + done(); + }); + }); +}); diff --git a/test/mocha/2.3.2.js b/test/mocha/2.3.2.js new file mode 100644 index 0000000..913af92 --- /dev/null +++ b/test/mocha/2.3.2.js @@ -0,0 +1,126 @@ +"use strict"; + +var assert = require("assert"); + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality + +function testPromiseResolution(xFactory, test) { + specify("via return from a fulfilled promise", function (done) { + var promise = fulfilled(dummy).then(function onBasePromiseFulfilled() { + return xFactory(); + }); + + test(promise, done); + }); + + specify("via return from a rejected promise", function (done) { + var promise = rejected(dummy).then(null, function onBasePromiseRejected() { + return xFactory(); + }); + + test(promise, done); + }); +} + +describe("2.3.2: If `x` is a promise, adopt its state", function () { + describe("2.3.2.1: If `x` is pending, `promise` must remain pending until `x` is fulfilled or rejected.", + function () { + function xFactory() { + return pending().promise; + } + + testPromiseResolution(xFactory, function (promise, done) { + var wasFulfilled = false; + var wasRejected = false; + + promise.then( + function onPromiseFulfilled() { + wasFulfilled = true; + }, + function onPromiseRejected() { + wasRejected = true; + } + ); + + setTimeout(function () { + assert.strictEqual(wasFulfilled, false); + assert.strictEqual(wasRejected, false); + done(); + }, 100); + }); + }); + + describe("2.3.2.2: If/when `x` is fulfilled, fulfill `promise` with the same value.", function () { + describe("`x` is already-fulfilled", function () { + function xFactory() { + return fulfilled(sentinel); + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(function onPromiseFulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("`x` is eventually-fulfilled", function () { + var tuple = null; + + function xFactory() { + tuple = pending(); + setTimeout(function () { + tuple.fulfill(sentinel); + }, 50); + return tuple.promise; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(function onPromiseFulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + }); + + describe("2.3.2.3: If/when `x` is rejected, reject `promise` with the same reason.", function () { + describe("`x` is already-rejected", function () { + function xFactory() { + return rejected(sentinel); + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function onPromiseRejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("`x` is eventually-rejected", function () { + var tuple = null; + + function xFactory() { + tuple = pending(); + setTimeout(function () { + tuple.reject(sentinel); + }, 50); + return tuple.promise; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function onPromiseRejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + }); +}); diff --git a/test/mocha/2.3.3.js b/test/mocha/2.3.3.js new file mode 100644 index 0000000..97c13a2 --- /dev/null +++ b/test/mocha/2.3.3.js @@ -0,0 +1,993 @@ +"use strict"; + +var assert = require("assert"); +var thenables = require("./helpers/thenables"); +var reasons = require("./helpers/reasons"); + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality +var other = { other: "other" }; // a value we don't want to be strict equal to +var sentinelArray = [sentinel]; // a sentinel fulfillment value to test when we need an array + +function testPromiseResolution(xFactory, test) { + specify("via return from a fulfilled promise", function (done) { + var promise = fulfilled(dummy).then(function onBasePromiseFulfilled() { + return xFactory(); + }); + + test(promise, done); + }); + + specify("via return from a rejected promise", function (done) { + var promise = rejected(dummy).then(null, function onBasePromiseRejected() { + return xFactory(); + }); + + test(promise, done); + }); +} + +function testCallingResolvePromise(yFactory, stringRepresentation, test) { + describe("`y` is " + stringRepresentation, function () { + describe("`then` calls `resolvePromise` synchronously", function () { + function xFactory() { + return { + then: function (resolvePromise) { + resolvePromise(yFactory()); + } + }; + } + + testPromiseResolution(xFactory, test); + }); + + describe("`then` calls `resolvePromise` asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise) { + setTimeout(function () { + resolvePromise(yFactory()); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, test); + }); + }); +} + +function testCallingRejectPromise(r, stringRepresentation, test) { + describe("`r` is " + stringRepresentation, function () { + describe("`then` calls `rejectPromise` synchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + rejectPromise(r); + } + }; + } + + testPromiseResolution(xFactory, test); + }); + + describe("`then` calls `rejectPromise` asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + setTimeout(function () { + rejectPromise(r); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, test); + }); + }); +} + +function testCallingResolvePromiseFulfillsWith(yFactory, stringRepresentation, fulfillmentValue) { + testCallingResolvePromise(yFactory, stringRepresentation, function (promise, done) { + + promise.then(function onPromiseFulfilled(value) { + assert.strictEqual(value, fulfillmentValue); + done(); + }); + }); +} + +function testCallingResolvePromiseRejectsWith(yFactory, stringRepresentation, rejectionReason) { + testCallingResolvePromise(yFactory, stringRepresentation, function (promise, done) { + + promise.then(null, function onPromiseRejected(reason) { + assert.strictEqual(reason, rejectionReason); + done(); + }); + }); +} + +function testCallingRejectPromiseRejectsWith(reason, stringRepresentation) { + testCallingRejectPromise(reason, stringRepresentation, function (promise, done) { + + promise.then(null, function onPromiseRejected(rejectionReason) { + assert.strictEqual(rejectionReason, reason); + done(); + }); + }); +} + +describe("2.3.3: Otherwise, if `x` is an object or function,", function () { + describe("2.3.3.1: Let `then` be `x.then`", function () { + describe("`x` is an object with null prototype", function () { + var numberOfTimesThenWasRetrieved = null; + + beforeEach(function () { + numberOfTimesThenWasRetrieved = 0; + }); + + function xFactory() { + return Object.create(null, { + then: { + get: function () { + ++numberOfTimesThenWasRetrieved; + return function thenMethodForX(onFulfilled) { + onFulfilled(); + }; + } + } + }); + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function () { + assert.strictEqual(numberOfTimesThenWasRetrieved, 1); + done(); + }); + }); + }); + + describe("`x` is an object with normal Object.prototype", function () { + var numberOfTimesThenWasRetrieved = null; + + beforeEach(function () { + numberOfTimesThenWasRetrieved = 0; + }); + + function xFactory() { + return Object.create(Object.prototype, { + then: { + get: function () { + ++numberOfTimesThenWasRetrieved; + return function thenMethodForX(onFulfilled) { + onFulfilled(); + }; + } + } + }); + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function () { + assert.strictEqual(numberOfTimesThenWasRetrieved, 1); + done(); + }); + }); + }); + + describe("`x` is a function", function () { + var numberOfTimesThenWasRetrieved = null; + + beforeEach(function () { + numberOfTimesThenWasRetrieved = 0; + }); + + function xFactory() { + function x() { } + + Object.defineProperty(x, "then", { + get: function () { + ++numberOfTimesThenWasRetrieved; + return function thenMethodForX(onFulfilled) { + onFulfilled(); + }; + } + }); + + return x; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function () { + assert.strictEqual(numberOfTimesThenWasRetrieved, 1); + done(); + }); + }); + }); + }); + + describe("2.3.3.2: If retrieving the property `x.then` results in a thrown exception `e`, reject `promise` with " + + "`e` as the reason.", function () { + function testRejectionViaThrowingGetter(e, stringRepresentation) { + function xFactory() { + return Object.create(Object.prototype, { + then: { + get: function () { + throw e; + } + } + }); + } + + describe("`e` is " + stringRepresentation, function () { + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(null, function (reason) { + assert.strictEqual(reason, e); + done(); + }); + }); + }); + } + + Object.keys(reasons).forEach(function (stringRepresentation) { + testRejectionViaThrowingGetter(reasons[stringRepresentation], stringRepresentation); + }); + }); + + describe("2.3.3.3: If `then` is a function, call it with `x` as `this`, first argument `resolvePromise`, and " + + "second argument `rejectPromise`", function () { + describe("Calls with `x` as `this` and two function arguments", function () { + function xFactory() { + var x = { + then: function (onFulfilled, onRejected) { + assert.strictEqual(this, x); + assert.strictEqual(typeof onFulfilled, "function"); + assert.strictEqual(typeof onRejected, "function"); + onFulfilled(); + } + }; + return x; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function () { + done(); + }); + }); + }); + + describe("Uses the original value of `then`", function () { + var numberOfTimesThenWasRetrieved = null; + + beforeEach(function () { + numberOfTimesThenWasRetrieved = 0; + }); + + function xFactory() { + return Object.create(Object.prototype, { + then: { + get: function () { + if (numberOfTimesThenWasRetrieved === 0) { + return function (onFulfilled) { + onFulfilled(); + }; + } + return null; + } + } + }); + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function () { + done(); + }); + }); + }); + + describe("2.3.3.3.1: If/when `resolvePromise` is called with value `y`, run `[[Resolve]](promise, y)`", + function () { + describe("`y` is not a thenable", function () { + testCallingResolvePromiseFulfillsWith(function () { return undefined; }, "`undefined`", undefined); + testCallingResolvePromiseFulfillsWith(function () { return null; }, "`null`", null); + testCallingResolvePromiseFulfillsWith(function () { return false; }, "`false`", false); + testCallingResolvePromiseFulfillsWith(function () { return 5; }, "`5`", 5); + testCallingResolvePromiseFulfillsWith(function () { return sentinel; }, "an object", sentinel); + testCallingResolvePromiseFulfillsWith(function () { return sentinelArray; }, "an array", sentinelArray); + }); + + describe("`y` is a thenable", function () { + Object.keys(thenables.fulfilled).forEach(function (stringRepresentation) { + function yFactory() { + return thenables.fulfilled[stringRepresentation](sentinel); + } + + testCallingResolvePromiseFulfillsWith(yFactory, stringRepresentation, sentinel); + }); + + Object.keys(thenables.rejected).forEach(function (stringRepresentation) { + function yFactory() { + return thenables.rejected[stringRepresentation](sentinel); + } + + testCallingResolvePromiseRejectsWith(yFactory, stringRepresentation, sentinel); + }); + }); + + describe("`y` is a thenable for a thenable", function () { + Object.keys(thenables.fulfilled).forEach(function (outerStringRepresentation) { + var outerThenableFactory = thenables.fulfilled[outerStringRepresentation]; + + Object.keys(thenables.fulfilled).forEach(function (innerStringRepresentation) { + var innerThenableFactory = thenables.fulfilled[innerStringRepresentation]; + + var stringRepresentation = outerStringRepresentation + " for " + innerStringRepresentation; + + function yFactory() { + return outerThenableFactory(innerThenableFactory(sentinel)); + } + + testCallingResolvePromiseFulfillsWith(yFactory, stringRepresentation, sentinel); + }); + + Object.keys(thenables.rejected).forEach(function (innerStringRepresentation) { + var innerThenableFactory = thenables.rejected[innerStringRepresentation]; + + var stringRepresentation = outerStringRepresentation + " for " + innerStringRepresentation; + + function yFactory() { + return outerThenableFactory(innerThenableFactory(sentinel)); + } + + testCallingResolvePromiseRejectsWith(yFactory, stringRepresentation, sentinel); + }); + }); + }); + }); + + describe("2.3.3.3.2: If/when `rejectPromise` is called with reason `r`, reject `promise` with `r`", + function () { + Object.keys(reasons).forEach(function (stringRepresentation) { + testCallingRejectPromiseRejectsWith(reasons[stringRepresentation], stringRepresentation); + }); + }); + + describe("2.3.3.3.3: If both `resolvePromise` and `rejectPromise` are called, or multiple calls to the same " + + "argument are made, the first call takes precedence, and any further calls are ignored.", + function () { + describe("calling `resolvePromise` then `rejectPromise`, both synchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + resolvePromise(sentinel); + rejectPromise(other); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("calling `resolvePromise` synchronously then `rejectPromise` asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + resolvePromise(sentinel); + + setTimeout(function () { + rejectPromise(other); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("calling `resolvePromise` then `rejectPromise`, both asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + setTimeout(function () { + resolvePromise(sentinel); + }, 0); + + setTimeout(function () { + rejectPromise(other); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("calling `resolvePromise` with an asynchronously-fulfilled promise, then calling " + + "`rejectPromise`, both synchronously", function () { + function xFactory() { + var tuple = pending(); + setTimeout(function () { + tuple.fulfill(sentinel); + }, 50); + + return { + then: function (resolvePromise, rejectPromise) { + resolvePromise(tuple.promise); + rejectPromise(other); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("calling `resolvePromise` with an asynchronously-rejected promise, then calling " + + "`rejectPromise`, both synchronously", function () { + function xFactory() { + var tuple = pending(); + setTimeout(function () { + tuple.reject(sentinel); + }, 50); + + return { + then: function (resolvePromise, rejectPromise) { + resolvePromise(tuple.promise); + rejectPromise(other); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("calling `rejectPromise` then `resolvePromise`, both synchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + rejectPromise(sentinel); + resolvePromise(other); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("calling `rejectPromise` synchronously then `resolvePromise` asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + rejectPromise(sentinel); + + setTimeout(function () { + resolvePromise(other); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("calling `rejectPromise` then `resolvePromise`, both asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + setTimeout(function () { + rejectPromise(sentinel); + }, 0); + + setTimeout(function () { + resolvePromise(other); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("calling `resolvePromise` twice synchronously", function () { + function xFactory() { + return { + then: function (resolvePromise) { + resolvePromise(sentinel); + resolvePromise(other); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("calling `resolvePromise` twice, first synchronously then asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise) { + resolvePromise(sentinel); + + setTimeout(function () { + resolvePromise(other); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("calling `resolvePromise` twice, both times asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise) { + setTimeout(function () { + resolvePromise(sentinel); + }, 0); + + setTimeout(function () { + resolvePromise(other); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("calling `resolvePromise` with an asynchronously-fulfilled promise, then calling it again, both " + + "times synchronously", function () { + function xFactory() { + var tuple = pending(); + setTimeout(function () { + tuple.fulfill(sentinel); + }, 50); + + return { + then: function (resolvePromise) { + resolvePromise(tuple.promise); + resolvePromise(other); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("calling `resolvePromise` with an asynchronously-rejected promise, then calling it again, both " + + "times synchronously", function () { + function xFactory() { + var tuple = pending(); + setTimeout(function () { + tuple.reject(sentinel); + }, 50); + + return { + then: function (resolvePromise) { + resolvePromise(tuple.promise); + resolvePromise(other); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("calling `rejectPromise` twice synchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + rejectPromise(sentinel); + rejectPromise(other); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("calling `rejectPromise` twice, first synchronously then asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + rejectPromise(sentinel); + + setTimeout(function () { + resolvePromise(other); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("calling `rejectPromise` twice, both times asynchronously", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + setTimeout(function () { + rejectPromise(sentinel); + }, 0); + + setTimeout(function () { + resolvePromise(other); + }, 0); + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("saving and abusing `resolvePromise` and `rejectPromise`", function () { + var savedResolvePromise, savedRejectPromise; + + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + savedResolvePromise = resolvePromise; + savedRejectPromise = rejectPromise; + } + }; + } + + beforeEach(function () { + savedResolvePromise = null; + savedRejectPromise = null; + }); + + testPromiseResolution(xFactory, function (promise, done) { + var timesFulfilled = 0; + var timesRejected = 0; + + promise.then( + function () { + ++timesFulfilled; + }, + function () { + ++timesRejected; + } + ); + + if (savedResolvePromise && savedRejectPromise) { + savedResolvePromise(dummy); + savedResolvePromise(dummy); + savedRejectPromise(dummy); + savedRejectPromise(dummy); + } + + setTimeout(function () { + savedResolvePromise(dummy); + savedResolvePromise(dummy); + savedRejectPromise(dummy); + savedRejectPromise(dummy); + }, 4); + + setTimeout(function () { + assert.strictEqual(timesFulfilled, 1); + assert.strictEqual(timesRejected, 0); + done(); + }, 60); + }); + }); + }); + + describe("2.3.3.3.4: If calling `then` throws an exception `e`,", function () { + describe("2.3.3.3.4.1: If `resolvePromise` or `rejectPromise` have been called, ignore it.", function () { + describe("`resolvePromise` was called with a non-thenable", function () { + function xFactory() { + return { + then: function (resolvePromise) { + resolvePromise(sentinel); + throw other; + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("`resolvePromise` was called with an asynchronously-fulfilled promise", function () { + function xFactory() { + var tuple = pending(); + setTimeout(function () { + tuple.fulfill(sentinel); + }, 50); + + return { + then: function (resolvePromise) { + resolvePromise(tuple.promise); + throw other; + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("`resolvePromise` was called with an asynchronously-rejected promise", function () { + function xFactory() { + var tuple = pending(); + setTimeout(function () { + tuple.reject(sentinel); + }, 50); + + return { + then: function (resolvePromise) { + resolvePromise(tuple.promise); + throw other; + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("`rejectPromise` was called", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + rejectPromise(sentinel); + throw other; + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("`resolvePromise` then `rejectPromise` were called", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + resolvePromise(sentinel); + rejectPromise(other); + throw other; + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(function (value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("`rejectPromise` then `resolvePromise` were called", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + rejectPromise(sentinel); + resolvePromise(other); + throw other; + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + }); + + describe("2.3.3.3.4.2: Otherwise, reject `promise` with `e` as the reason.", function () { + describe("straightforward case", function () { + function xFactory() { + return { + then: function () { + throw sentinel; + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("`resolvePromise` is called asynchronously before the `throw`", function () { + function xFactory() { + return { + then: function (resolvePromise) { + setTimeout(function () { + resolvePromise(other); + }, 0); + throw sentinel; + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("`rejectPromise` is called asynchronously before the `throw`", function () { + function xFactory() { + return { + then: function (resolvePromise, rejectPromise) { + setTimeout(function () { + rejectPromise(other); + }, 0); + throw sentinel; + } + }; + } + + testPromiseResolution(xFactory, function (promise, done) { + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + }); + }); + }); + + describe("2.3.3.4: If `then` is not a function, fulfill promise with `x`", function () { + function testFulfillViaNonFunction(then, stringRepresentation) { + var x = null; + + beforeEach(function () { + x = { then: then }; + }); + + function xFactory() { + return x; + } + + describe("`then` is " + stringRepresentation, function () { + testPromiseResolution(xFactory, function (promise, done) { + promise.then(function (value) { + assert.strictEqual(value, x); + done(); + }); + }); + }); + } + + testFulfillViaNonFunction(5, "`5`"); + testFulfillViaNonFunction({}, "an object"); + testFulfillViaNonFunction([function () { }], "an array containing a function"); + testFulfillViaNonFunction(/a-b/i, "a regular expression"); + testFulfillViaNonFunction(Object.create(Function.prototype), "an object inheriting from `Function.prototype`"); + + + + }); +}); diff --git a/test/mocha/2.3.4.js b/test/mocha/2.3.4.js new file mode 100644 index 0000000..e421eea --- /dev/null +++ b/test/mocha/2.3.4.js @@ -0,0 +1,69 @@ +"use strict"; + +var assert = require("assert"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it + +describe("2.3.4: If `x` is not an object or function, fulfill `promise` with `x`", function () { + function testValue(expectedValue, stringRepresentation, beforeEachHook, afterEachHook) { + describe("The value is " + stringRepresentation, function () { + if (typeof beforeEachHook === "function") { + beforeEach(beforeEachHook); + } + if (typeof afterEachHook === "function") { + afterEach(afterEachHook); + } + + testFulfilled(dummy, function (promise1, done) { + var promise2 = promise1.then(function onFulfilled() { + return expectedValue; + }); + + promise2.then(function onPromise2Fulfilled(actualValue) { + assert.strictEqual(actualValue, expectedValue); + done(); + }); + }); + testRejected(dummy, function (promise1, done) { + var promise2 = promise1.then(null, function onRejected() { + return expectedValue; + }); + + promise2.then(function onPromise2Fulfilled(actualValue) { + assert.strictEqual(actualValue, expectedValue); + done(); + }); + }); + }); + } + + testValue(undefined, "`undefined`"); + testValue(null, "`null`"); + testValue(false, "`false`"); + testValue(true, "`true`"); + testValue(0, "`0`"); + + testValue( + true, + "`true` with `Boolean.prototype` modified to have a `then` method", + function () { + Boolean.prototype.then = function () {}; + }, + function () { + delete Boolean.prototype.then; + } + ); + + testValue( + 1, + "`1` with `Number.prototype` modified to have a `then` method", + function () { + Number.prototype.then = function () {}; + }, + function () { + delete Number.prototype.then; + } + ); +}); diff --git a/test/mocha/3.2.1.js b/test/mocha/3.2.1.js new file mode 100644 index 0000000..1478a0f --- /dev/null +++ b/test/mocha/3.2.1.js @@ -0,0 +1,41 @@ +"use strict"; + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it + +describe("3.2.1: Both `onFulfilled` and `onRejected` are optional arguments.", function () { + describe("3.2.1.1: If `onFulfilled` is not a function, it must be ignored.", function () { + function testNonFunction(nonFunction, stringRepresentation) { + specify("`onFulfilled` is " + stringRepresentation, function (done) { + rejected(dummy).then(nonFunction, function () { + done(); + }); + }); + } + + testNonFunction(undefined, "`undefined`"); + testNonFunction(null, "`null`"); + testNonFunction(false, "`false`"); + testNonFunction(5, "`5`"); + testNonFunction({}, "an object"); + }); + + describe("3.2.1.2: If `onRejected` is not a function, it must be ignored.", function () { + function testNonFunction(nonFunction, stringRepresentation) { + specify("`onRejected` is " + stringRepresentation, function (done) { + fulfilled(dummy).then(function () { + done(); + }, nonFunction); + }); + } + + testNonFunction(undefined, "`undefined`"); + testNonFunction(null, "`null`"); + testNonFunction(false, "`false`"); + testNonFunction(5, "`5`"); + testNonFunction({}, "an object"); + }); +}); diff --git a/test/mocha/3.2.2.js b/test/mocha/3.2.2.js new file mode 100644 index 0000000..0e90048 --- /dev/null +++ b/test/mocha/3.2.2.js @@ -0,0 +1,187 @@ +"use strict"; + +var assert = require("assert"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality + +describe("3.2.2: If `onFulfilled` is a function,", function () { + describe("3.2.2.1: it must be called after `promise` is fulfilled, with `promise`’s fulfillment value as its " + + "first argument.", function () { + testFulfilled(sentinel, function (promise, done) { + promise.then(function onFulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + + describe("3.2.2.2: it must not be called more than once.", function () { + specify("already-fulfilled", function (done) { + var timesCalled = 0; + + fulfilled(dummy).then(function onFulfilled() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + }); + + specify("trying to fulfill a pending promise more than once, immediately", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + tuple.fulfill(dummy); + tuple.fulfill(dummy); + }); + + specify("trying to fulfill a pending promise more than once, delayed", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + setTimeout(function () { + tuple.fulfill(dummy); + tuple.fulfill(dummy); + }, 50); + }); + + specify("trying to fulfill a pending promise more than once, immediately then delayed", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + tuple.fulfill(dummy); + setTimeout(function () { + tuple.fulfill(dummy); + }, 50); + }); + + specify("when multiple `then` calls are made, spaced apart in time", function (done) { + var tuple = pending(); + var timesCalled = [0, 0, 0]; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[0], 1); + }); + + setTimeout(function () { + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[1], 1); + }); + }, 50); + + setTimeout(function () { + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[2], 1); + done(); + }); + }, 100); + + setTimeout(function () { + tuple.fulfill(dummy); + }, 150); + }); + + specify("when `then` is interleaved with fulfillment", function (done) { + var tuple = pending(); + var timesCalled = [0, 0]; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[0], 1); + }); + + tuple.fulfill(dummy); + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(++timesCalled[1], 1); + done(); + }); + }); + }); + + describe("3.2.2.3: it must not be called if `onRejected` has been called.", function () { + testRejected(dummy, function (promise, done) { + var onRejectedCalled = false; + + promise.then(function onFulfilled() { + assert.strictEqual(onRejectedCalled, false); + done(); + }, function onRejected() { + onRejectedCalled = true; + }); + + setTimeout(function(){done();}, 100); + }); + + specify("trying to reject then immediately fulfill", function (done) { + var tuple = pending(); + var onRejectedCalled = false; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(onRejectedCalled, false); + done(); + }, function onRejected() { + onRejectedCalled = true; + }); + + tuple.reject(dummy); + tuple.fulfill(dummy); + setTimeout(function(){done();}, 100); + }); + + specify("trying to reject then fulfill, delayed", function (done) { + var tuple = pending(); + var onRejectedCalled = false; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(onRejectedCalled, false); + done(); + }, function onRejected() { + onRejectedCalled = true; + }); + + setTimeout(function () { + tuple.reject(dummy); + tuple.fulfill(dummy); + }, 50); + setTimeout(function(){done();}, 100); + }); + + specify("trying to reject immediately then fulfill delayed", function (done) { + var tuple = pending(); + var onRejectedCalled = false; + + tuple.promise.then(function onFulfilled() { + assert.strictEqual(onRejectedCalled, false); + done(); + }, function onRejected() { + onRejectedCalled = true; + }); + + tuple.reject(dummy); + setTimeout(function () { + tuple.fulfill(dummy); + }, 50); + setTimeout(function(){done();}, 100); + }); + }); +}); diff --git a/test/mocha/3.2.3.js b/test/mocha/3.2.3.js new file mode 100644 index 0000000..3960d87 --- /dev/null +++ b/test/mocha/3.2.3.js @@ -0,0 +1,187 @@ +"use strict"; + +var assert = require("assert"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; + +var adapter = global.adapter; +var rejected = adapter.rejected; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality + +describe("3.2.3: If `onRejected` is a function,", function () { + describe("3.2.3.1: it must be called after `promise` is rejected, with `promise`’s rejection reason as its " + + "first argument.", function () { + testRejected(sentinel, function (promise, done) { + promise.then(null, function onRejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + + describe("3.2.3.2: it must not be called more than once.", function () { + specify("already-rejected", function (done) { + var timesCalled = 0; + + rejected(dummy).then(null, function onRejected() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + }); + + specify("trying to reject a pending promise more than once, immediately", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + tuple.reject(dummy); + tuple.reject(dummy); + }); + + specify("trying to reject a pending promise more than once, delayed", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + setTimeout(function () { + tuple.reject(dummy); + tuple.reject(dummy); + }, 50); + }); + + specify("trying to reject a pending promise more than once, immediately then delayed", function (done) { + var tuple = pending(); + var timesCalled = 0; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled, 1); + done(); + }); + + tuple.reject(dummy); + setTimeout(function () { + tuple.reject(dummy); + }, 50); + }); + + specify("when multiple `then` calls are made, spaced apart in time", function (done) { + var tuple = pending(); + var timesCalled = [0, 0, 0]; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[0], 1); + }); + + setTimeout(function () { + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[1], 1); + }); + }, 50); + + setTimeout(function () { + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[2], 1); + done(); + }); + }, 100); + + setTimeout(function () { + tuple.reject(dummy); + }, 150); + }); + + specify("when `then` is interleaved with rejection", function (done) { + var tuple = pending(); + var timesCalled = [0, 0]; + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[0], 1); + }); + + tuple.reject(dummy); + + tuple.promise.then(null, function onRejected() { + assert.strictEqual(++timesCalled[1], 1); + done(); + }); + }); + }); + + describe("3.2.3.3: it must not be called if `onFulfilled` has been called.", function () { + testFulfilled(dummy, function (promise, done) { + var onFulfilledCalled = false; + + promise.then(function onFulfilled() { + onFulfilledCalled = true; + }, function onRejected() { + assert.strictEqual(onFulfilledCalled, false); + done(); + }); + + setTimeout(function(){done();}, 100); + }); + + specify("trying to fulfill then immediately reject", function (done) { + var tuple = pending(); + var onFulfilledCalled = false; + + tuple.promise.then(function onFulfilled() { + onFulfilledCalled = true; + }, function onRejected() { + assert.strictEqual(onFulfilledCalled, false); + done(); + }); + + tuple.fulfill(dummy); + tuple.reject(dummy); + setTimeout(function(){done();}, 100); + }); + + specify("trying to fulfill then reject, delayed", function (done) { + var tuple = pending(); + var onFulfilledCalled = false; + + tuple.promise.then(function onFulfilled() { + onFulfilledCalled = true; + }, function onRejected() { + assert.strictEqual(onFulfilledCalled, false); + done(); + }); + + setTimeout(function () { + tuple.fulfill(dummy); + tuple.reject(dummy); + }, 50); + setTimeout(function(){done();}, 100); + }); + + specify("trying to fulfill immediately then reject delayed", function (done) { + var tuple = pending(); + var onFulfilledCalled = false; + + tuple.promise.then(function onFulfilled() { + onFulfilledCalled = true; + }, function onRejected() { + assert.strictEqual(onFulfilledCalled, false); + done(); + }); + + tuple.fulfill(dummy); + setTimeout(function () { + tuple.reject(dummy); + }, 50); + setTimeout(function(){done();}, 100); + }); + }); +}); diff --git a/test/mocha/3.2.4.js b/test/mocha/3.2.4.js new file mode 100644 index 0000000..5076d64 --- /dev/null +++ b/test/mocha/3.2.4.js @@ -0,0 +1,31 @@ +"use strict"; + +var assert = require("assert"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it + +describe("3.2.4: `then` must return before `onFulfilled` or `onRejected` is called", function () { + testFulfilled(dummy, function (promise, done) { + var thenHasReturned = false; + + promise.then(function onFulfilled() { + assert(thenHasReturned); + done(); + }); + + thenHasReturned = true; + }); + + testRejected(dummy, function (promise, done) { + var thenHasReturned = false; + + promise.then(null, function onRejected() { + assert(thenHasReturned); + done(); + }); + + thenHasReturned = true; + }); +}); diff --git a/test/mocha/3.2.5.js b/test/mocha/3.2.5.js new file mode 100644 index 0000000..ad83821 --- /dev/null +++ b/test/mocha/3.2.5.js @@ -0,0 +1,257 @@ +"use strict"; + +var assert = require("assert"); +var sinon = require("sinon"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var other = { other: "other" }; // a value we don't want to be strict equal to +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality +var sentinel2 = { sentinel2: "sentinel2" }; +var sentinel3 = { sentinel3: "sentinel3" }; + +function callbackAggregator(times, ultimateCallback) { + var soFar = 0; + return function () { + if (++soFar === times) { + ultimateCallback(); + } + }; +} + +describe("3.2.5: `then` may be called multiple times on the same promise.", function () { + describe("3.2.5.1: If/when `promise` is fulfilled, respective `onFulfilled` callbacks must execute in the order " + + "of their originating calls to `then`.", function () { + describe("multiple boring fulfillment handlers", function () { + testFulfilled(sentinel, function (promise, done) { + var handler1 = sinon.stub().returns(other); + var handler2 = sinon.stub().returns(other); + var handler3 = sinon.stub().returns(other); + + var spy = sinon.spy(); + promise.then(handler1, spy); + promise.then(handler2, spy); + promise.then(handler3, spy); + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + + sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); + sinon.assert.notCalled(spy); + + done(); + }); + }); + }); + + describe("multiple fulfillment handlers, one of which throws", function () { + testFulfilled(sentinel, function (promise, done) { + var handler1 = sinon.stub().returns(other); + var handler2 = sinon.stub().throws(other); + var handler3 = sinon.stub().returns(other); + + var spy = sinon.spy(); + promise.then(handler1, spy); + promise.then(handler2, spy).caught(function(){}); + promise.then(handler3, spy); + + promise.then(function (value) { + assert.strictEqual(value, sentinel); + + sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); + sinon.assert.notCalled(spy); + + done(); + }); + }); + }); + + describe("results in multiple branching chains with their own fulfillment values", function () { + testFulfilled(dummy, function (promise, done) { + var semiDone = callbackAggregator(3, done); + + promise.then(function () { + return sentinel; + }).then(function (value) { + assert.strictEqual(value, sentinel); + semiDone(); + }); + + promise.then(function () { + throw sentinel2; + }).then(null, function (reason) { + assert.strictEqual(reason, sentinel2); + semiDone(); + }); + + promise.then(function () { + return sentinel3; + }).then(function (value) { + assert.strictEqual(value, sentinel3); + semiDone(); + }); + }); + }); + + describe("`onFulfilled` handlers are called in the original order", function () { + testFulfilled(dummy, function (promise, done) { + var handler1 = sinon.spy(function handler1() {}); + var handler2 = sinon.spy(function handler2() {}); + var handler3 = sinon.spy(function handler3() {}); + + promise.then(handler1); + promise.then(handler2); + promise.then(handler3); + + promise.then(function () { + sinon.assert.callOrder(handler1, handler2, handler3); + done(); + }); + }); + + describe("even when one handler is added inside another handler", function () { + testFulfilled(dummy, function (promise, done) { + var handler1 = sinon.spy(function handler1() {}); + var handler2 = sinon.spy(function handler2() {}); + var handler3 = sinon.spy(function handler3() {}); + + promise.then(function () { + handler1(); + promise.then(handler3); + }); + promise.then(handler2); + + promise.then(function () { + // Give implementations a bit of extra time to flush their internal queue, if necessary. + setTimeout(function () { + sinon.assert.callOrder(handler1, handler2, handler3); + done(); + }, 15); + }); + }); + }); + }); + }); + + describe("3.2.5.2: If/when `promise` is rejected, respective `onRejected` callbacks must execute in the order " + + "of their originating calls to `then`.", function () { + describe("multiple boring rejection handlers", function () { + testRejected(sentinel, function (promise, done) { + var handler1 = sinon.stub().returns(other); + var handler2 = sinon.stub().returns(other); + var handler3 = sinon.stub().returns(other); + + var spy = sinon.spy(); + promise.then(spy, handler1); + promise.then(spy, handler2); + promise.then(spy, handler3); + + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + + sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); + sinon.assert.notCalled(spy); + + done(); + }); + }); + }); + + describe("multiple rejection handlers, one of which throws", function () { + testRejected(sentinel, function (promise, done) { + var handler1 = sinon.stub().returns(other); + var handler2 = sinon.stub().throws(other); + var handler3 = sinon.stub().returns(other); + + var spy = sinon.spy(); + promise.then(spy, handler1); + promise.then(spy, handler2).caught(function(){}); + promise.then(spy, handler3); + + promise.then(null, function (reason) { + assert.strictEqual(reason, sentinel); + + sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); + sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); + sinon.assert.notCalled(spy); + + done(); + }); + }); + }); + + describe("results in multiple branching chains with their own fulfillment values", function () { + testRejected(sentinel, function (promise, done) { + var semiDone = callbackAggregator(3, done); + + promise.then(null, function () { + return sentinel; + }).then(function (value) { + assert.strictEqual(value, sentinel); + semiDone(); + }); + + promise.then(null, function () { + throw sentinel2; + }).then(null, function (reason) { + assert.strictEqual(reason, sentinel2); + semiDone(); + }); + + promise.then(null, function () { + return sentinel3; + }).then(function (value) { + assert.strictEqual(value, sentinel3); + semiDone(); + }); + }); + }); + + describe("`onRejected` handlers are called in the original order", function () { + testRejected(dummy, function (promise, done) { + var handler1 = sinon.spy(function handler1() {}); + var handler2 = sinon.spy(function handler2() {}); + var handler3 = sinon.spy(function handler3() {}); + + promise.then(null, handler1); + promise.then(null, handler2); + promise.then(null, handler3); + + promise.then(null, function () { + sinon.assert.callOrder(handler1, handler2, handler3); + done(); + }); + }); + + describe("even when one handler is added inside another handler", function () { + testRejected(dummy, function (promise, done) { + var handler1 = sinon.spy(function handler1() {}); + var handler2 = sinon.spy(function handler2() {}); + var handler3 = sinon.spy(function handler3() {}); + + promise.then(null, function () { + handler1(); + promise.then(null, handler3); + }); + promise.then(null, handler2); + + promise.then(null, function () { + // Give implementations a bit of extra time to flush their internal queue, if necessary. + setTimeout(function () { + sinon.assert.callOrder(handler1, handler2, handler3); + done(); + }, 15); + }); + }); + }); + }); + }); +}); diff --git a/test/mocha/3.2.6.js b/test/mocha/3.2.6.js new file mode 100644 index 0000000..bf9b324 --- /dev/null +++ b/test/mocha/3.2.6.js @@ -0,0 +1,322 @@ +"use strict"; + +var assert = require("assert"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; +var pending = adapter.pending; + +var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it +var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality +var other = { other: "other" }; // a value we don't want to be strict equal to + +describe("3.2.6: `then` must return a promise: `promise2 = promise1.then(onFulfilled, onRejected)`", function () { + specify("is a promise", function () { + var promise1 = pending().promise; + var promise2 = promise1.then(); + + assert(typeof promise2 === "object" || typeof promise2 === "function"); + assert.notStrictEqual(promise2, null); + assert.strictEqual(typeof promise2.then, "function"); + }); + + describe("3.2.6.1: If either `onFulfilled` or `onRejected` returns a value that is not a promise, `promise2` " + + "must be fulfilled with that value.", function () { + function testValue(expectedValue, stringRepresentation) { + describe("The value is " + stringRepresentation, function () { + testFulfilled(dummy, function (promise1, done) { + var promise2 = promise1.then(function onFulfilled() { + return expectedValue; + }); + + promise2.then(function onPromise2Fulfilled(actualValue) { + assert.strictEqual(actualValue, expectedValue); + done(); + }); + }); + testRejected(dummy, function (promise1, done) { + var promise2 = promise1.then(null, function onRejected() { + return expectedValue; + }); + + promise2.then(function onPromise2Fulfilled(actualValue) { + assert.strictEqual(actualValue, expectedValue); + done(); + }); + }); + }); + } + + testValue(undefined, "`undefined`"); + testValue(null, "`null`"); + testValue(false, "`false`"); + testValue(0, "`0`"); + testValue(new Error(), "an error"); + testValue(new Date(), "a date"); + testValue({}, "an object"); + testValue({ then: 5 }, "an object with a non-function `then` property"); + }); + + describe("3.2.6.2: If either `onFulfilled` or `onRejected` throws an exception, `promise2` " + + "must be rejected with the thrown exception as the reason.", function () { + function testReason(expectedReason, stringRepresentation) { + describe("The reason is " + stringRepresentation, function () { + testFulfilled(dummy, function (promise1, done) { + var promise2 = promise1.then(function onFulfilled() { + throw expectedReason; + }); + + promise2.then(null, function onPromise2Rejected(actualReason) { + assert.strictEqual(actualReason, expectedReason); + done(); + }); + }); + testRejected(dummy, function (promise1, done) { + var promise2 = promise1.then(null, function onRejected() { + throw expectedReason; + }); + + promise2.then(null, function onPromise2Rejected(actualReason) { + assert.strictEqual(actualReason, expectedReason); + done(); + }); + }); + }); + } + + testReason(undefined, "`undefined`"); + testReason(null, "`null`"); + testReason(false, "`false`"); + testReason(0, "`0`"); + testReason(new Error(), "an error"); + testReason(new Date(), "a date"); + testReason({}, "an object"); + testReason({ then: function () { } }, "a promise-alike"); + testReason(fulfilled(dummy), "a fulfilled promise"); + var promise = rejected(dummy); promise.caught(function(){}); + testReason(promise, "a rejected promise"); + }); + + describe("3.2.6.3: If either `onFulfilled` or `onRejected` returns a promise (call it `returnedPromise`), " + + "`promise2` must assume the state of `returnedPromise`", function () { + describe("3.2.6.3.1: If `returnedPromise` is pending, `promise2` must remain pending until `returnedPromise` " + + "is fulfilled or rejected.", function () { + testFulfilled(dummy, function (promise1, done) { + var wasFulfilled = false; + var wasRejected = false; + + var promise2 = promise1.then(function onFulfilled() { + var returnedPromise = pending().promise; + return returnedPromise; + }); + + promise2.then( + function onPromise2Fulfilled() { + wasFulfilled = true; + }, + function onPromise2Rejected() { + wasRejected = true; + } + ); + + setTimeout(function () { + assert.strictEqual(wasFulfilled, false); + assert.strictEqual(wasRejected, false); + done(); + }, 100); + }); + + testRejected(dummy, function (promise1, done) { + var wasFulfilled = false; + var wasRejected = false; + + var promise2 = promise1.then(null, function onRejected() { + var returnedPromise = pending().promise; + return returnedPromise; + }); + + promise2.then( + function onPromise2Fulfilled() { + wasFulfilled = true; + }, + function onPromise2Rejected() { + wasRejected = true; + } + ); + + setTimeout(function () { + assert.strictEqual(wasFulfilled, false); + assert.strictEqual(wasRejected, false); + done(); + }, 100); + }); + }); + + describe("3.2.6.3.2: If/when `returnedPromise` is fulfilled, `promise2` must be fulfilled with the same value.", + function () { + describe("`promise1` is fulfilled, and `returnedPromise` is:", function () { + testFulfilled(sentinel, function (returnedPromise, done) { + var promise1 = fulfilled(dummy); + var promise2 = promise1.then(function onFulfilled() { + return returnedPromise; + }); + + promise2.then(function onPromise2Fulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + + specify("a pseudo-promise", function (done) { + var promise1 = fulfilled(dummy); + var promise2 = promise1.then(function onFulfilled() { + return { + then: function (f) { f(sentinel); } + }; + }); + + promise2.then(function onPromise2Fulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + describe("`promise1` is rejected, and `returnedPromise` is:", function () { + testFulfilled(sentinel, function (returnedPromise, done) { + var promise1 = rejected(dummy); + var promise2 = promise1.then(null, function onRejected() { + return returnedPromise; + }); + + promise2.then(function onPromise2Fulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + + specify("a pseudo-promise", function (done) { + var promise1 = rejected(dummy); + var promise2 = promise1.then(null, function onRejected() { + return { + then: function (f) { f(sentinel); } + }; + }); + + promise2.then(function onPromise2Fulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + }); + + describe("3.2.6.3.3: If/when `returnedPromise` is rejected, `promise2` must be rejected with the same reason.", + function () { + describe("`promise1` is fulfilled, and `returnedPromise` is:", function () { + testRejected(sentinel, function (returnedPromise, done) { + var promise1 = fulfilled(dummy); + var promise2 = promise1.then(function onFulfilled() { + return returnedPromise; + }); + + promise2.then(null, function onPromise2Rejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + + specify("a pseudo-promise", function (done) { + var promise1 = fulfilled(dummy); + var promise2 = promise1.then(function onFulfilled() { + return { + then: function (f, r) { r(sentinel); } + }; + }); + + promise2.then(null, function onPromise2Rejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + describe("`promise1` is rejected, and `returnedPromise` is:", function () { + testRejected(sentinel, function (returnedPromise, done) { + var promise1 = rejected(dummy); + var promise2 = promise1.then(null, function onRejected() { + return returnedPromise; + }); + + promise2.then(null, function onPromise2Rejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + + specify("a pseudo-promise", function (done) { + var promise1 = rejected(dummy); + var promise2 = promise1.then(null, function onRejected() { + return { + then: function (f, r) { r(sentinel); } + }; + }); + + promise2.then(null, function onPromise2Rejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + }); + }); + + describe("3.2.6.4: If `onFulfilled` is not a function and `promise1` is fulfilled, `promise2` must be fulfilled " + + "with the same value.", function () { + + function testNonFunction(nonFunction, stringRepresentation) { + describe("`onFulfilled` is " + stringRepresentation, function () { + testFulfilled(sentinel, function (promise1, done) { + var promise2 = promise1.then(nonFunction); + + promise2.then(function onPromise2Fulfilled(value) { + assert.strictEqual(value, sentinel); + done(); + }); + }); + }); + } + + testNonFunction(undefined, "`undefined`"); + testNonFunction(null, "`null`"); + testNonFunction(false, "`false`"); + testNonFunction(5, "`5`"); + testNonFunction({}, "an object"); + testNonFunction([function () { return other; }], "an array containing a function"); + }); + + describe("3.2.6.5: If `onRejected` is not a function and `promise1` is rejected, `promise2` must be rejected " + + "with the same reason.", function () { + + function testNonFunction(nonFunction, stringRepresentation) { + describe("`onRejected` is " + stringRepresentation, function () { + testRejected(sentinel, function (promise1, done) { + var promise2 = promise1.then(null, nonFunction); + + promise2.then(null, function onPromise2Rejected(reason) { + assert.strictEqual(reason, sentinel); + done(); + }); + }); + }); + } + + testNonFunction(undefined, "`undefined`"); + testNonFunction(null, "`null`"); + testNonFunction(false, "`false`"); + testNonFunction(5, "`5`"); + testNonFunction({}, "an object"); + testNonFunction([function () { return other; }], "an array containing a function"); + }); +}); diff --git a/test/mocha/any.js b/test/mocha/any.js new file mode 100644 index 0000000..7509d80 --- /dev/null +++ b/test/mocha/any.js @@ -0,0 +1,112 @@ +"use strict"; +/* +Based on When.js tests + +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var sentinel = {}; +var other = {}; +var RangeError = Promise.RangeError; + +describe("Promise.any-test", function () { + + specify("should reject on empty input array", function() { + var a = []; + return Promise.any(a) + .caught(RangeError, testUtils.returnToken) + .then(testUtils.assertToken); + }); + + specify("should resolve with an input value", function() { + var input = [1, 2, 3]; + return Promise.any(input).then( + function(result) { + assert(testUtils.contains(input, result)); + }, assert.fail + ); + }); + + specify("should resolve with a promised input value", function() { + var input = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + return Promise.any(input).then( + function(result) { + assert(testUtils.contains([1, 2, 3], result)); + }, assert.fail + ); + }); + + specify("should reject with all rejected input values if all inputs are rejected", function() { + var input = [Promise.reject(1), Promise.reject(2), Promise.reject(3)]; + var promise = Promise.any(input); + + return promise.then( + assert.fail, + function(result) { + //Cannot use deep equality in IE8 because non-enumerable properties are not + //supported + assert(result[0] === 1); + assert(result[1] === 2); + assert(result[2] === 3); + } + ); + }); + + specify("should accept a promise for an array", function() { + var expected, input; + + expected = [1, 2, 3]; + input = Promise.resolve(expected); + + return Promise.any(input).then( + function(result) { + assert.notDeepEqual(expected.indexOf(result), -1); + }, assert.fail + ); + }); + + specify("should allow zero handlers", function() { + var input = [1, 2, 3]; + return Promise.any(input).then( + function(result) { + assert(testUtils.contains(input, result)); + }, assert.fail + ); + }); + + specify("should resolve to empty array when input promise does not resolve to array", function() { + return Promise.any(Promise.resolve(1)) + .caught(TypeError, testUtils.returnToken) + .then(testUtils.assertToken); + }); + + specify("should reject when given immediately rejected promise", function() { + var err = new Error(); + return Promise.any(Promise.reject(err)).then(assert.fail, function(e) { + assert.strictEqual(err, e); + }); + }); +}); diff --git a/test/mocha/api_exceptions.js b/test/mocha/api_exceptions.js new file mode 100644 index 0000000..aacab03 --- /dev/null +++ b/test/mocha/api_exceptions.js @@ -0,0 +1,231 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +function assertErrorHasLongTraces(e) { + assert(e.stack.indexOf("From previous event:") > -1); +} + +function testCollection(name, a1, a2, a3) { + + function getPromise(obj, val) { + return obj === void 0 + ? Promise.resolve(val)[name](a1, a2, a3) + : Promise[name](val, a1, a2, a3); + } + + function thenable(obj) { + var o = { + then: function(f) { + setTimeout(function(){ + f(3); + }, 1); + } + } + specify("thenable for non-collection value", function() { + return getPromise(obj, o) + .then(assert.fail) + .caught(Promise.TypeError, testUtils.returnToken) + .then(testUtils.assertToken) + }); + }; + + function immediate(obj) { + specify("immediate for non-collection value", function(){ + return getPromise(obj, 3) + .then(assert.fail) + .caught(Promise.TypeError, testUtils.returnToken) + .then(testUtils.assertToken) + }); + } + + function promise(obj) { + var d = Promise.defer(); + setTimeout(function(){ + d.resolve(3); + }, 1); + specify("promise for non-collection value", function() { + return getPromise(obj, d.promise) + .then(assert.fail) + .caught(Promise.TypeError, testUtils.returnToken) + .then(testUtils.assertToken) + }); + } + + describe("When passing non-collection argument to Promise."+name + "() it should reject", function() { + immediate(Promise); + thenable(Promise); + promise(Promise); + }); + + describe("When calling ."+name + "() on a promise that resolves to a non-collection it should reject", function() { + immediate(); + thenable(); + promise(); + }); +} + +if (Promise.hasLongStackTraces()) { + + + describe("runtime API misuse should result in rejections", function(){ + specify("returning promises circularly", function() { + var d = Promise.defer(); + var p = d.promise; + + var c = p.then(function(){ + return c; + }); + d.fulfill(3); + return c.then(assert.fail, function(e){ + assert(e instanceof Promise.TypeError); + }); + }); + + specify("using illegal catchfilter", function() { + + var d = Promise.defer(); + var p = d.promise; + d.fulfill(3); + return p.caught(null, function(){ + + }).then(assert.fail, function(e){ + assert(e instanceof Promise.TypeError); + }); + }); + + specify("non-function to map", function() { + + return Promise.map([], []).then(assert.fail, function(e){ + assert(e instanceof Promise.TypeError); + }); + }); + + + specify("non-function to map inside then", function() { + + return Promise.resolve().then(function(){ + return Promise.map([], []); + }).then(assert.fail, function(e){ + assert(e instanceof Promise.TypeError); + assertErrorHasLongTraces(e); + }); + }); + + + specify("non-function to reduce", function() { + + return Promise.reduce([], []).then(assert.fail, function(e){ + assert(e instanceof Promise.TypeError); + }); + }); + + + specify("non-function to reduce inside then", function() { + + return Promise.resolve().then(function(){ + return Promise.reduce([], []); + }).then(assert.fail, function(e){ + assert(e instanceof Promise.TypeError); + assertErrorHasLongTraces(e); + }); + }); + + + specify("non-integer to some", function() { + + return Promise.some([], "asd").then(assert.fail, function(e){ + assert(e instanceof Promise.TypeError); + }); + }); + + + specify("non-integer to some inside then", function() { + + return Promise.resolve().then(function(){ + return Promise.some([], "asd") + }).then(assert.fail, function(e){ + assert(e instanceof Promise.TypeError); + assertErrorHasLongTraces(e); + }); + }); + + specify("non-array to all", function() { + + Promise.all(3, 3).then(assert.fail, function(e){ + assert(e instanceof Promise.TypeError); + }); + }); + + + specify("non-array to all inside then", function() { + + return Promise.resolve().then(function(){ + return Promise.all(3, 3); + }).then(assert.fail, function(e) { + assert(e instanceof Promise.TypeError); + assertErrorHasLongTraces(e); + }); + }); + + }); + + + describe("static API misuse should just throw right away", function(){ + + specify("non-function to promise constructor", function() { + try { + new Promise(); + assert.fail(); + } + catch (e) { + assert(e instanceof Promise.TypeError); + } + }); + + specify("non-function to coroutine", function() { + try { + Promise.coroutine(); + assert.fail(); + } + catch (e) { + assert(e instanceof Promise.TypeError); + } + }); + + + specify("non-object to promisifyAll", function() { + try { + Promise.promisifyAll(); + assert.fail(); + } + catch (e) { + assert(e instanceof Promise.TypeError); + } + }); + + + specify("non-function to promisify", function() { + try { + Promise.promisify(); + assert.fail(); + } + catch (e) { + assert(e instanceof Promise.TypeError); + } + }); + + }); + + testCollection("race"); + testCollection("all"); + testCollection("settle"); + testCollection("any"); + testCollection("some", 1); + testCollection("map", function(){}); + testCollection("reduce", function(){}); + testCollection("filter", function(){}); + testCollection("props", function(){}); +} diff --git a/test/mocha/async.js b/test/mocha/async.js new file mode 100644 index 0000000..c487ddd --- /dev/null +++ b/test/mocha/async.js @@ -0,0 +1,245 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +describe("Async requirement", function() { + + var arr = []; + + function a() { + arr.push(1); + } + + function b() { + arr.push(2); + } + + function c() { + arr.push(3); + } + + + function assertArr() { + assert.deepEqual(arr, [1,2,3]); + arr.length = 0; + } + + beforeEach(function() { + arr = []; + }); + + specify("Basic", function() { + var p = new Promise(function(resolve) { + resolve(); + }); + a(); + p.then(c); + b(); + return p.then(assertArr); + }); + + specify("Resolve-Before-Then", function() { + var resolveP; + var p = new Promise(function(resolve) { + resolveP = resolve; + }); + + a(); + resolveP(); + p.then(c); + b(); + return p.then(assertArr); + }); + + specify("Resolve-After-Then", function() { + var resolveP; + var p = new Promise(function(resolve) { + resolveP = resolve; + }); + + a(); + p.then(c); + resolveP(); + b(); + return p.then(assertArr); + }); + + specify("Then-Inside-Then", function() { + var fulfilledP = Promise.resolve(); + return fulfilledP.then(function() { + a(); + var ret = fulfilledP.then(c).then(assertArr); + b(); + return ret; + }); + }); + + if (typeof Error.captureStackTrace === "function") { + describe("Should not grow the stack and cause eventually stack overflow.", function(){ + var lim; + beforeEach(function() { + lim = Error.stackTraceLimit; + Error.stackTraceLimit = 10000; + }); + + afterEach(function() { + Error.stackTraceLimit = lim; + }); + + function assertStackIsNotGrowing(stack) { + assert(stack.split("\n").length > 5); + assert(stack.split("\n").length < 15); + } + + specify("Already fulfilled.", function() { + function test(i){ + if (i <= 0){ + return Promise.resolve(new Error().stack); + } else { + return Promise.resolve(i-1).then(test) + } + } + return test(100).then(function(stack) { + assertStackIsNotGrowing(stack); + }); + }); + + specify("Already rejected", function() { + function test(i){ + if (i <= 0){ + return Promise.reject(new Error().stack); + } else { + return Promise.reject(i-1).then(assert.fail, test) + } + } + return test(100).then(assert.fail, function(stack) { + assertStackIsNotGrowing(stack); + }); + }); + + specify("Immediately fulfilled", function() { + function test(i){ + var deferred = Promise.defer(); + if (i <= 0){ + deferred.fulfill(new Error().stack); + return deferred.promise; + } else { + deferred.fulfill(i-1); + return deferred.promise.then(test) + } + } + return test(100).then(function(stack) { + assertStackIsNotGrowing(stack); + }); + }); + + specify("Immediately rejected", function() { + function test(i){ + var deferred = Promise.defer(); + if (i <= 0){ + deferred.reject(new Error().stack); + return deferred.promise; + } else { + deferred.reject(i-1); + return deferred.promise.then(assert.fail, test) + } + } + return test(10).then(assert.fail, function(stack) { + assertStackIsNotGrowing(stack); + }); + }); + }); + } + + if (testUtils.isRecentNode) { + describe("Frees memory of old values in promise chains", function () { + function getHeapUsed() { + global.gc(); + return process.memoryUsage().heapUsed; + } + + var initialHeapUsed; + + before(function () { + if (typeof global.gc !== "function") { + throw new Error("These tests require the --expose-gc flag"); + } + initialHeapUsed = getHeapUsed(); + }); + + specify(".then", function () { + return Promise.resolve() + .then(function () { + assert.ok( + getHeapUsed() < initialHeapUsed * 1.1, + "Promise.resolve uses minimal memory" + ); + var rows = []; + for (var i = 0; i < 1e6; i++) { + rows.push(["Example " + i, i, i * 2]); + } + return rows; + }) + .then(function (rows) { + assert.ok( + getHeapUsed() > initialHeapUsed * 12, + "large array uses a large amount of memory" + ); + return { len: rows.length }; + }) + .then(function (x) { + // work around cancellation retaining previous result + return x; + }) + .then(function (summaryResult) { + assert.ok( + getHeapUsed() < initialHeapUsed * 1.1, + "memory used by large array is freed" + ); + assert.strictEqual(summaryResult.len, 1e6, "result"); + }); + }); + + specify(".catch", function () { + return Promise.reject(new Error("error 1")) + .catch(function () { + assert.ok( + getHeapUsed() < initialHeapUsed * 1.1, + "Promise.reject uses minimal memory" + ); + var rows = []; + for (var i = 0; i < 1e6; i++) { + rows.push(["Example " + i, i, i * 2]); + } + var error = new Error("error 2"); + error.result = rows; + throw error; + }) + .catch(function (err) { + assert.ok( + getHeapUsed() > initialHeapUsed * 12, + "large array uses a large amount of memory" + ); + var rows = err.result; + var error = new Error("error 3"); + error.result = { len: rows.length }; + throw error; + }) + .catch(function (err) { + // work around cancellation retaining previous result + throw err; + }) + .catch(function (err) { + assert.ok( + getHeapUsed() < initialHeapUsed * 1.1, + "memory used by large array is freed" + ); + var summaryResult = err.result; + assert.strictEqual(summaryResult.len, 1e6, "result"); + }); + }); + }); + } +}); diff --git a/test/mocha/async_hooks.js b/test/mocha/async_hooks.js new file mode 100644 index 0000000..15d0eed --- /dev/null +++ b/test/mocha/async_hooks.js @@ -0,0 +1,145 @@ +"use strict"; + +var assert = require("assert"); + +var getContextFn = Promise._getContext; +Promise.config({ asyncHooks: true }); +var supportsAsync = Promise._getContext !== getContextFn; +Promise.config({ asyncHooks: false }); +if (supportsAsync) { + runTests(); +} + +function runTests() { + var async_hooks = require('async_hooks'); + + var tree = new Set(); + var hook = async_hooks.createHook({ + init: function(asyncId, type, triggerId) { + if (tree.has(triggerId)) { + tree.add(asyncId); + } + } + }); + + var currentId = async_hooks.executionAsyncId; + + function getAsyncPromise() { + return new Promise(function(resolve, reject) { + setTimeout(function() { + setTimeout(resolve, 1); + }, 1); + }); + } + + describe("async_hooks", function() { + beforeEach(function() { + Promise.config({ asyncHooks: true }); + }) + afterEach(function() { + tree.clear(); + hook.disable(); + Promise.config({ asyncHooks: false }); + }); + + it('should preserve async context when using fromNode', function() { + hook.enable() + tree.add(currentId()); + + return new Promise(function(resolve) { + var globalResolve; + setImmediate(function() { + hook.enable() + tree.add(currentId()); + resolve( + new Promise(function(resolve) { globalResolve = resolve; }) + .then(function() { + assert.ok(tree.has(currentId())); + }) + ); + }) + + setTimeout(function() { + globalResolve(); + }, 10); + }) + }); + + it('should preserve async context when using .map', function() { + hook.enable() + tree.add(currentId()); + var d1 = getAsyncPromise(); + + return new Promise(function(resolve, reject) { + resolve(Promise.map([d1, null, Promise.resolve(1), Promise.delay(1)], function() { + return currentId(); + }).then(function(asyncIds) { + for (var i = 0; i < asyncIds.length; ++i) { + assert.ok(tree.has(asyncIds[i])); + } + })); + }); + }); + + it('should preserve async context when using .filter', function() { + hook.enable() + tree.add(currentId()); + var d1 = getAsyncPromise(); + + return new Promise(function(resolve, reject) { + resolve(Promise.filter([d1, null, Promise.resolve(1), Promise.delay(1)], function() { + assert.ok(tree.has(currentId())); + })); + }); + }); + + it('should preserve async context when using .reduce', function() { + hook.enable() + tree.add(currentId()); + var d1 = getAsyncPromise(); + + return new Promise(function(resolve, reject) { + resolve(Promise.reduce([d1, null, Promise.resolve(1), Promise.delay(1)], function() { + assert.ok(tree.has(currentId())); + })); + }); + }); + + it('should preserve async context when using .join', function() { + hook.enable() + tree.add(currentId()); + var d1 = getAsyncPromise(); + + return new Promise(function(resolve, reject) { + resolve(Promise.join(d1, Promise.delay(1), function() { + assert.ok(tree.has(currentId())); + })); + }); + }); + + it('should preserve async context when using .each', function() { + hook.enable() + tree.add(currentId()); + var d1 = getAsyncPromise(); + + return new Promise(function(resolve, reject) { + resolve(Promise.each([d1, null, Promise.resolve(1), Promise.delay(1)], function() { + assert.ok(tree.has(currentId())); + })); + }); + }); + + it('should be able to disable AsyncResource usage', function() { + Promise.config({ asyncHooks: false }); + hook.enable() + tree.add(currentId()); + var d1 = getAsyncPromise(); + + return new Promise(function(resolve, reject) { + resolve(d1.then(function() { + assert.ok(!tree.has(currentId())); + })); + }); + }); + }); +} diff --git a/test/mocha/bind.js b/test/mocha/bind.js new file mode 100644 index 0000000..cdb88e4 --- /dev/null +++ b/test/mocha/bind.js @@ -0,0 +1,1225 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var defaultThis = function() {return this}(); + +function timedThenableOf(value) { + return { + then: function(onFulfilled) { + setTimeout(function() { + onFulfilled(value); + }, 1); + } + }; +} + +function timedPromiseOf(value) { + return Promise.delay(1, value); +} + +function immediatePromiseOf(value) { + return Promise.resolve(value); +} + +function immediateThenableOf(value) { + return { + then: function(onFulfilled) { + onFulfilled(value); + } + }; +} + +function timedRejectedThenableOf(value) { + return { + then: function(onFulfilled, onRejected) { + setTimeout(function() { + onRejected(value); + }, 1); + } + }; +} + +function timedRejectedPromiseOf(value) { + return Promise.delay(1).then(function() { + throw value; + }); +} + +function immediateRejectedPromiseOf(value) { + return Promise.reject(value); +} + +function immediateRejectedThenableOf(value) { + return { + then: function(onFulfilled, onRejected) { + onRejected(value); + } + }; +} + +function toValue(valueOrPromise) { + if (valueOrPromise && typeof valueOrPromise.value === "function") { + return valueOrPromise.value(); + } + return valueOrPromise +} + +var THIS = {name: "this"}; + +function CustomError1() {} +CustomError1.prototype = Object.create(Error.prototype); +function CustomError2() {} +CustomError2.prototype = Object.create(Error.prototype); + + +describe("when using .bind", function() { + describe("with finally", function() { + describe("this should refer to the bound object", function() { + specify("in straight-forward handler", function() { + return Promise.resolve().bind(THIS).lastly(function(){ + assert(this === THIS); + }); + }); + + specify("after promise returned from finally resolves", function() { + var d = Promise.defer(); + var promise = d.promise; + var waited = false; + + setTimeout(function(){ + waited = true; + d.fulfill(); + }, 1); + + return Promise.resolve().bind(THIS).lastly(function(){ + return promise; + }).lastly(function(){ + assert(waited); + assert(this === THIS); + }); + }); + }) + + }); + + describe("with tap", function() { + describe("this should refer to the bound object", function() { + specify("in straight-forward handler", function() { + return Promise.resolve().bind(THIS).tap(function(){ + assert(this === THIS); + }); + }); + + specify("after promise returned from tap resolves", function() { + var d = Promise.defer(); + var promise = d.promise; + var waited = false; + setTimeout(function(){ + waited = true; + d.fulfill(); + }, 1); + + return Promise.resolve().bind(THIS).tap(function(){ + return promise; + }).tap(function(){ + assert(waited); + assert(this === THIS); + }); + }); + }) + + }); + + describe("with timeout", function() { + describe("this should refer to the bound object", function() { + specify("in straight-forward handler", function() { + return Promise.resolve(3).bind(THIS).timeout(500).then(function(v) { + assert(v === 3); + assert(this === THIS); + }); + }); + specify("in rejected handler", function() { + return Promise.reject(3).bind(THIS).timeout(500).then(assert.fail, function(v){ + assert(v === 3); + assert(this === THIS); + }); + }); + + specify("in rejected handler after timeout", function() { + return new Promise(function(){}) + .bind(THIS).timeout(10).caught(Promise.TimeoutError, function(err){ + assert(this === THIS); + }); + }); + }) + + }); + + describe("With catch filters", function() { + describe("this should refer to the bound object", function() { + specify("in an immediately trapped catch handler", function() { + return Promise.resolve().bind(THIS).then(function(){ + assert(THIS === this); + var a; + a.b(); + }).caught(Error, function(e){ + assert(THIS === this); + }); + }); + specify("in a later trapped catch handler", function() { + return Promise.resolve().bind(THIS).then(function(){ + throw new CustomError1(); + }).caught(CustomError2, assert.fail) + .caught(CustomError1, function(e){ + assert(THIS === this); + }); + }); + }); + }); + + describe("With .get promises", function(){ + specify("this should refer to the bound object", function() { + return Promise.resolve({key: "value"}).bind(THIS).get("key").then(function(val){ + assert(val === "value"); + assert(this === THIS); + }); + }); + }); + + describe("With .call promises", function(){ + specify("this should refer to the bound object", function() { + return Promise.resolve({key: function(){return "value";}}).bind(THIS).call("key").then(function(val){ + assert(val === "value"); + assert(this === THIS); + }); + }); + }); + + + describe("With .done promises", function(){ + + describe("this should refer to the bound object", function() { + specify("when rejected", function() { + return Promise.reject().bind(THIS).done(assert.fail, function(){ + assert(this === THIS); + }); + }); + specify("when fulfilled", function() { + return Promise.resolve().bind(THIS).done(function(){ + assert(this === THIS); + }); + }); + }); + }); + + describe("With .spread promises", function(){ + + describe("this should refer to the bound object", function() { + specify("when spreading immediate array", function() { + return Promise.resolve([1,2,3]).bind(THIS).spread(function(a, b, c){ + assert(c === 3); + assert(this === THIS); + }); + }); + specify("when spreading eventual array", function() { + var d = Promise.defer(); + var promise = d.promise; + + setTimeout(function(){ + d.fulfill([1,2,3]); + }, 1); + + return promise.bind(THIS).spread(function(a, b, c){ + assert(c === 3); + assert(this === THIS); + }); + }); + + specify("when spreading eventual array of eventual values", function() { + var d = Promise.defer(); + var promise = d.promise; + setTimeout(function(){ + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + d.fulfill([p1, p2, p3]); + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 3); + }, 1); + return promise.bind(THIS).all().spread(function(a, b, c){ + assert(c === 3); + assert(this === THIS); + }); + + }); + }); + }); + + describe("With nodeify", function() { + describe("this should refer to the bound object", function() { + specify("when the callback succeeeds", function() { + var spy = testUtils.getSpy(); + Promise.resolve(3).bind(THIS).nodeify(spy(function(err, success){ + assert(success === 3); + assert(this === THIS); + })); + return spy.promise; + }); + specify("when the callback errs", function() { + var spy = testUtils.getSpy(); + Promise.reject(3).bind(THIS).nodeify(spy(function(err, success){ + assert(err === 3); + assert(this === THIS); + })); + return spy.promise; + }); + }); + }); + + + describe("With map", function() { + describe("this should refer to the bound object", function() { + specify("inside the mapper with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).map(function(v, i){ + if (i === 2) { + assert(this === THIS); + } + }); + }); + specify("inside the mapper with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).map(function(v, i){ + if (i === 2) { + assert(this === THIS); + } + }); + }); + + specify("after the mapper with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).map(function(){ + return 1; + }).then(function(){ + assert(this === THIS); + }); + }); + + specify("after the mapper with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).map(function(){ + return 1; + }).then(function(){ + assert(this === THIS); + }); + + + }); + + specify("after the mapper with immediate values when the map returns promises", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).map(function(){ + return p1; + }).then(function(){ + assert(this === THIS); + }); + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).map(function(){ + return p1.then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + + }); + }); + }); + + describe("With reduce", function() { + describe("this should refer to the bound object", function() { + specify("inside the reducer with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).reduce(function(prev, v, i){ + if (i === 2) { + assert(this === THIS); + } + }); + }); + specify("inside the reducer with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).reduce(function(prev, v, i){ + if (i === 2) { + assert(this === THIS); + } + }); + }); + + specify("after the reducer with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).reduce(function(){ + return 1; + }).then(function(){ + assert(this === THIS); + }); + }); + + specify("after the reducer with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + return Promise.resolve([p1, p2, p3]).bind(THIS).reduce(function(){ + return 1; + }).then(function(){ + assert(this === THIS); + }); + + }); + + specify("after the reducer with immediate values when the reducer returns promise", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).reduce(function(){ + return p1; + }).then(function(){ + assert(this === THIS); + }); + + + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).reduce(function(){ + return p1.then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + + + }); + }); + }); + + + describe("With filter", function() { + describe("this should refer to the bound object", function() { + specify("inside the filterer with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).filter(function(v, i){ + if (i === 2) { + assert(this === THIS); + } + }); + }); + specify("inside the filterer with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).filter(function(v, i){ + if (i === 2) { + assert(this === THIS); + } + }); + }); + + specify("after the filterer with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).filter(function(){ + return 1; + }).then(function(){ + assert(this === THIS); + }); + }); + + specify("after the filterer with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).filter(function(){ + return 1; + }).then(function(){ + assert(this === THIS); + }); + }); + + specify("after the filterer with immediate values when the filterer returns promises", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).filter(function(){ + return p1; + }).then(function(){ + assert(this === THIS); + }); + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).filter(function(){ + return p1.then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + }); + }); + }); + + describe("With all", function() { + describe("this should refer to the bound object", function() { + specify("after all with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).all().then(function(v){ + assert(v.length === 3); + assert(this === THIS); + }); + }); + specify("after all with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).all().then(function(v){ + assert(v.length === 3); + assert(this === THIS); + }); + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).filter(function(){ + return Promise.all([p1]).then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + }); + }); + }); + + describe("With any", function() { + describe("this should refer to the bound object", function() { + specify("after any with immediate values", function() { + Promise.resolve([1,2,3]).bind(THIS).any().then(function(v){ + assert(v === 1); + assert(this === THIS); + }); + }); + specify("after any with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).any().then(function(v){ + assert(v === 1); + assert(this === THIS); + }); + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).filter(function(){ + return Promise.any([p1]).then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + + }); + }); + }); + + + describe("With race", function() { + describe("this should refer to the bound object", function() { + specify("after race with immediate values", function() { + Promise.resolve([1,2,3]).bind(THIS).race().then(function(v){ + assert(v === 1); + assert(this === THIS); + }); + }); + specify("after race with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).race().then(function(v){ + assert(v === 1); + assert(this === THIS); + }); + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).filter(function(){ + return Promise.race([p1]).then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + }); + }); + }); + + describe("With delay", function() { + describe("this should refer to the bound object", function() { + specify("after race with immediate values", function() { + Promise.resolve([1,2,3]).bind(THIS).delay(1).then(function(v){ + assert(v[0] === 1); + assert(this === THIS); + }); + }); + specify("after race with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).delay(1).all().then(function(v){ + assert(v[0] === 1); + assert(this === THIS); + }); + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).delay(1).bind(THIS).delay(1).filter(function(){ + assert(this === THIS); + return Promise.delay(1).then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + }); + }); + }); + + describe("With settle", function() { + describe("this should refer to the bound object", function() { + specify("after settle with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).settle().then(function(v){ + assert(v.length === 3); + assert(this === THIS); + }); + }); + specify("after settle with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).settle().then(function(v){ + assert(v.length === 3); + assert(this === THIS); + }); + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).filter(function(){ + return Promise.settle([p1]).then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + }); + }); + }); + + describe("With some", function() { + describe("this should refer to the bound object", function() { + specify("after some with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).some(2).then(function(v){ + assert.deepEqual(v, [1,2]); + assert(this === THIS); + }); + }); + specify("after some with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).some(2).then(function(v){ + assert.deepEqual(v, [1,2]); + assert(this === THIS); + }); + }); + + specify("after some with eventual array for eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + var dArray = Promise.defer(); + var arrayPromise = dArray.promise; + + setTimeout(function(){ + dArray.fulfill([p1, p2, p3]); + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + }, 1); + + return arrayPromise.bind(THIS).some(2).then(function(v){ + assert.deepEqual(v, [1,2]); + assert(this === THIS); + }); + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + setTimeout(function(){ + d1.fulfill(1); + }, 1); + + return Promise.resolve([1,2,3]).bind(THIS).filter(function(){ + return Promise.some([p1], 1).then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + }); + }); + }); + + + + describe("With props", function() { + describe("this should refer to the bound object", function() { + specify("after props with immediate values", function() { + return Promise.resolve([1,2,3]).bind(THIS).props().then(function(v){ + assert(v[2] === 3); + assert(this === THIS); + }); + }); + specify("after props with eventual values", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.resolve([p1, p2, p3]).bind(THIS).props().then(function(v){ + assert(v[2] === 3); + assert(this === THIS); + }); + }); + }); + + describe("this should not refer to the bound object", function() { + specify("in the promises created within the handler", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + setTimeout(function(){ + d1.fulfill(1); + }, 1); + return Promise.resolve([1,2,3]).bind(THIS).props(function(){ + return Promise.settle([p1]).then(function(){ + assert(this !== THIS); + return 1; + }) + }).then(function(){ + assert(this === THIS); + }); + + + }); + }); + }); + +}); + +describe("When using .bind to gratuitously rebind", function() { + var a = {value: 1}; + var b = {value: 2}; + var c = {value: 3}; + + function makeTest(a, b, c) { + return function() { + return Promise.bind(a).then(function(){ + assert(this.value === 1); + }).bind(b).then(function(){ + assert(this.value === 2); + }).bind(c).then(function(){ + assert(this.value === 3); + }); + } + } + + specify("should not get confused immediately", makeTest(a, b, c)); + specify("should not get confused immediate thenable", + makeTest(immediateThenableOf(a), immediateThenableOf(b), immediateThenableOf(c))); + specify("should not get confused immediate promise", + makeTest(immediatePromiseOf(a), immediatePromiseOf(b), immediatePromiseOf(c))); + specify("should not get confused timed thenable", + makeTest(timedThenableOf(a), timedThenableOf(b), timedThenableOf(c))); + specify("should not get confused timed promise", + makeTest(timedPromiseOf(a), timedPromiseOf(b), timedPromiseOf(c))); +}); + +describe("Promised thisArg", function() { + + var e = {value: 1}; + + specify("basic case, this first", function(done) { + var thisPromise = Promise.delay(1, 1); + var promise = thisPromise.delay(1).thenReturn(2); + promise.bind(thisPromise).then(function(val) { + assert(+this === 1); + assert(+val === 2); + done(); + }); + }); + + specify("bound value is not changed by returned promise", function() { + return Promise.resolve().then(function() { + return new Promise(function(resolve) { + resolve(); + }).bind(THIS).then(function() {}); + }).then(function() { + assert.strictEqual(this, defaultThis); + }); + }); + + specify("basic case, main promise first", function() { + var promise = Promise.delay(1, 2); + var thisPromise = promise.thenReturn(1); + return promise.bind(thisPromise).then(function(val) { + assert.strictEqual(+this, 1); + assert.strictEqual(+val, 2); + }); + }); + + specify("both reject, this rejects first", function(done) { + var e1 = new Error(); + var e2 = new Error(); + var thisPromise = Promise.delay(1, 0).thenThrow(e1); + var promise = Promise.delay(2, 56).thenThrow(e2); + promise.bind(thisPromise).then(null, function(reason) { + assert(this === defaultThis); + assert(reason === e1); + done(); + }); + }); + + specify("both reject, main promise rejects first", function(done) { + var e1 = new Error("first"); + var e2 = new Error("second"); + var thisPromise = Promise.delay(56, 1).thenThrow(e1); + var promise = Promise.delay(2, 0).thenThrow(e2); + promise.bind(thisPromise).then(null, function(reason) { + assert(this === defaultThis); + assert(reason === e2); + done(); + }); + }); + + specify("Immediate value waits for deferred this", function() { + var t = Promise.delay(1, THIS); + var t2 = {}; + return Promise.resolve(t2).bind(t).then(function(value) { + assert.strictEqual(this, THIS); + assert.strictEqual(t2, value); + }); + }); + + + specify("Immediate error waits for deferred this", function() { + var t = Promise.delay(1, THIS); + var err = new Error(); + return Promise.reject(err).bind(t).then(assert.fail, function(e) { + assert.strictEqual(this, THIS); + assert.strictEqual(err, e); + }); + }); + + function makeThisArgRejectedTest(reason) { + return function() { + return Promise.bind(reason()).then(assert.fail, function(e) { + assert(this === defaultThis); + assert(e.value === 1); + }) + }; + } + + specify("if thisArg is rejected timed promise, returned promise is rejected", + makeThisArgRejectedTest(function() { return timedRejectedPromiseOf(e); })); + specify("if thisArg is rejected immediate promise, returned promise is rejected", + makeThisArgRejectedTest(function() { return immediateRejectedPromiseOf(e); })); + specify("if thisArg is rejected timed thenable, returned promise is rejected", + makeThisArgRejectedTest(function() { return timedRejectedThenableOf(e); })); + specify("if thisArg is rejected immediate thenable, returned promise is rejected", + makeThisArgRejectedTest(function() { return immediateRejectedThenableOf(e); })); + + function makeThisArgRejectedTestMethod(reason) { + return function() { + + return Promise.resolve().bind(reason()).then(assert.fail, function(e) { + assert(this === defaultThis); + assert(e.value === 1); + }) + }; + } + + specify("if thisArg is rejected timed promise, returned promise is rejected", + makeThisArgRejectedTestMethod(function() { return timedRejectedPromiseOf(e); })); + specify("if thisArg is rejected immediate promise, returned promise is rejected", + makeThisArgRejectedTestMethod(function() { return immediateRejectedPromiseOf(e); })); + specify("if thisArg is rejected timed thenable, returned promise is rejected", + makeThisArgRejectedTestMethod(function() { return timedRejectedThenableOf(e); })); + specify("if thisArg is rejected immediate thenable, returned promise is rejected", + makeThisArgRejectedTestMethod(function() { return immediateRejectedThenableOf(e); })); +}); + +describe("github issue", function() { + specify("gh-426", function() { + return Promise.all([Promise.delay(10)]).bind(THIS).spread(function() { + assert.equal(this, THIS); + }); + }); + + specify("gh-702-1", function() { + return Promise.bind(Promise.delay(1, THIS)).then(function() { + assert.equal(this, THIS); + }).then(function() { + assert.equal(this, THIS); + }); + }); + + specify("gh-702-2", function() { + return Promise.resolve().bind(Promise.delay(1, THIS)).then(function() { + assert.equal(this, THIS); + }).then(function() { + assert.equal(this, THIS); + }); + }); +}); + + +describe("promised bind", function() { + specify("works after following", function() { + return Promise.bind(Promise.delay(1, THIS)).then(function() { + assert.equal(this, THIS); + return Promise.delay(1); + }).then(function() { + assert.equal(this, THIS); + return Promise.delay(1); + }).then(function() { + assert.equal(this, THIS); + }); + }); + + specify("works with spread", function() { + return Promise.bind(Promise.delay(1, THIS), [1,2,3]).spread(function() { + assert.equal(this, THIS); + assert.deepEqual([1,2,3], [].slice.call(arguments)); + return Promise.delay(1, [].slice.call(arguments)); + }).spread(function() { + assert.deepEqual([1,2,3], [].slice.call(arguments)); + assert.equal(this, THIS); + return Promise.delay(1, [].slice.call(arguments)); + }).spread(function() { + assert.deepEqual([1,2,3], [].slice.call(arguments)); + assert.equal(this, THIS); + }); + }); + + specify("works with immediate finally", function() { + return Promise.bind(Promise.delay(1, THIS), [1,2,3]).lastly(function() { + assert.equal(this, THIS); + }).then(function() { + assert.equal(this, THIS); + }); + }); + + specify("works with delayed finally", function() { + return Promise.bind(Promise.delay(1, THIS), [1,2,3]).lastly(function() { + assert.equal(this, THIS); + return Promise.delay(1); + }).then(function() { + assert.equal(this, THIS); + }); + }); + + specify("works with immediate tap", function() { + return Promise.bind(Promise.delay(1, THIS), [1,2,3]).tap(function() { + assert.equal(this, THIS); + }).then(function() { + assert.equal(this, THIS); + }); + }); + + specify("works with delayed tap", function() { + return Promise.bind(Promise.delay(1, THIS), [1,2,3]).tap(function() { + assert.equal(this, THIS); + return Promise.delay(1); + }).then(function() { + assert.equal(this, THIS); + }); + }); +}); + + diff --git a/test/mocha/bluebird-multiple-instances.js b/test/mocha/bluebird-multiple-instances.js new file mode 100644 index 0000000..50337bf --- /dev/null +++ b/test/mocha/bluebird-multiple-instances.js @@ -0,0 +1,107 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var isNodeJS = testUtils.isNodeJS; +var OldPromise = require("./helpers/bluebird0_7_0.js"); + +if (isNodeJS) { + var Promise1 = testUtils.addDeferred(require("../../js/debug/promise.js")()); + var Promise2 = testUtils.addDeferred(require("../../js/debug/promise.js")()); + + var err1 = new Error(); + var err2 = new Error(); + + describe("Separate instances of bluebird", function() { + + specify("Should have identical Error types", function() { + assert(Promise1.CancellationError === Promise2.CancellationError); + assert(Promise1.RejectionError === Promise2.RejectionError); + assert(Promise1.TimeoutError === Promise2.TimeoutError); + }); + + specify("Should not be identical", function() { + assert(Promise1.onPossiblyUnhandledRejection !== + Promise2.onPossiblyUnhandledRejection); + assert(Promise1 !== Promise2); + }); + + specify("Should have different unhandled rejection handlers", function() { + var spy1 = testUtils.getSpy(); + var spy2 = testUtils.getSpy(); + + Promise1.onPossiblyUnhandledRejection(spy1(function(e, promise) { + assert(promise instanceof Promise1); + assert(!(promise instanceof Promise2)); + assert(e === err1); + })); + + Promise2.onPossiblyUnhandledRejection(spy2(function(e, promise) { + assert(promise instanceof Promise2); + assert(!(promise instanceof Promise1)); + assert(e === err2); + })); + assert(Promise1.onPossiblyUnhandledRejection !== + Promise2.onPossiblyUnhandledRejection); + + var d1 = Promise1.defer(); + var d2 = Promise2.defer(); + + d1.promise.then(function(){ + throw err1; + }); + + d2.promise.then(function(){ + throw err2; + }); + + setTimeout(function(){ + d1.fulfill(); + d2.fulfill(); + setTimeout(function() { + Promise1._unhandledRejectionCheck(); + Promise2._unhandledRejectionCheck(); + }, 100); + }, 1); + return Promise.all([spy1.promise, spy2.promise]); + }); + + specify("Should use fast cast", function() { + var a = Promise1.defer(); + var b = Promise2.cast(a.promise); + assert(a.promise._receiver0 === b); + }); + + specify("Should use fast cast with very old version", function() { + var a = OldPromise.pending(); + var b = Promise1.cast(a.promise); + assert(a.promise._receiver0 === b); + }); + + specify("Should return 2 from very old promise", function() { + return Promise1.resolve().then( + function(){ return OldPromise.cast(0).then(function(){return 2}); + }).then(function(two){ + assert.equal(two, 2); + }); + }); + + specify("Should reject primitive from fast cast", function() { + var a = OldPromise.pending(); + var b = Promise.resolve(a.promise); + a.reject(1); + return b.then(assert.fail, function(e) { + assert.strictEqual(e, 1); + }); + }); + specify("Should reject object from fast cast", function() { + var err = new Error(); + var a = OldPromise.pending(); + var b = Promise.resolve(a.promise); + a.reject(err); + return b.then(assert.fail, function(e) { + assert.strictEqual(e, err); + }); + }); + }); + +} diff --git a/test/mocha/call.js b/test/mocha/call.js new file mode 100644 index 0000000..1186247 --- /dev/null +++ b/test/mocha/call.js @@ -0,0 +1,57 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +var c = { + val: 3, + method: function() { + return [].slice.call(arguments).concat(this.val); + } +}; + +describe("call", function() { + specify("0 args", function() { + return Promise.resolve(c).call("method").then(function(res) { + assert.deepEqual([3], res); + }); + }); + specify("1 args", function() { + return Promise.resolve(c).call("method", 1).then(function(res) { + assert.deepEqual([1, 3], res); + }); + }); + specify("2 args", function() { + return Promise.resolve(c).call("method", 1, 2).then(function(res) { + assert.deepEqual([1, 2, 3], res); + }); + }); + specify("3 args", function() { + return Promise.resolve(c).call("method", 1, 2, 3).then(function(res) { + assert.deepEqual([1, 2, 3, 3], res); + }); + }); + specify("10 args", function() { + return Promise.resolve(c).call("method", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10).then(function(res) { + assert.deepEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3], res); + }); + }); + specify("method not found", function() { + var promises = [ + Promise.resolve([]).call("abc").then(assert.fail, testUtils.noop), + Promise.resolve([]).call("abc", 1, 2, 3, 4, 5, 6, 7).then(assert.fail, testUtils.noop), + Promise.resolve([]).call("abc ").then(assert.fail, testUtils.noop), + Promise.resolve(null).call("abc", 1, 2, 3, 4, 5, 6, 7).then(assert.fail, testUtils.noop), + Promise.resolve(null).call("abc").then(assert.fail, testUtils.noop), + Promise.resolve(null).call("abc ").then(assert.fail, testUtils.noop) + ]; + + return Promise.all(promises).then(function(errors) { + for (var i = 0; i < errors.length; ++i) { + var message = errors[i].message || errors[i].toString(); + assert(message.indexOf("has no method") >= 0); + } + }); + }); +}); diff --git a/test/mocha/cancel.js b/test/mocha/cancel.js new file mode 100644 index 0000000..03d172e --- /dev/null +++ b/test/mocha/cancel.js @@ -0,0 +1,3184 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var awaitLateQueue = testUtils.awaitLateQueue; + +describe("Cancellation", function() { + specify("requires a function", function() { + return new Promise(function(_, __, onCancel) { + onCancel(); + }).then(assert.fail, function(e) { + assert(e instanceof Promise.TypeError); + }); + }); + + specify("can register multiple on same promise", function() { + var cancelled = 0; + var p = new Promise(function(_, __, onCancel) { + onCancel(function() {cancelled++}); + onCancel(function() {cancelled++}); + onCancel(function() {cancelled++}); + onCancel(function() {cancelled++}); + }); + + p.cancel(); + return awaitLateQueue(function() { + assert.equal(4, cancelled); + }); + }); + + specify("follower promises' handlers are not called, registered before", function() { + var cancelled = 0; + var p = new Promise(function(_, __, onCancel) { + onCancel(function() {cancelled++}); + }); + + new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() {cancelled++}); + }); + new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() {cancelled++}); + }); + new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() {cancelled++}); + }); + p.cancel(); + return awaitLateQueue(function() { + assert.equal(1, cancelled); + }); + }); + + specify("follower promises' handlers are not called, registered after", function() { + var cancelled = 0; + var p = new Promise(function(_, __, onCancel) { + onCancel(function() {cancelled++}); + }); + + p.cancel(); + new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() {cancelled++}); + }).suppressUnhandledRejections(); + new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() {cancelled++}); + }).suppressUnhandledRejections(); + new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() {cancelled++}); + }).suppressUnhandledRejections(); + return awaitLateQueue(function() { + assert.equal(1, cancelled); + }); + }); + + specify("downstream follower promises' handlers are not called, registered before", function() { + var cancelled = 0; + var p = new Promise(function(_, __, onCancel) { + onCancel(function() {cancelled++}); + }); + + new Promise(function(resolve, _, onCancel) { + resolve(p.then()); + onCancel(function() {cancelled++}); + }); + new Promise(function(resolve, _, onCancel) { + resolve(p.then()); + onCancel(function() {cancelled++}); + }); + new Promise(function(resolve, _, onCancel) { + resolve(p.then()); + onCancel(function() {cancelled++}); + }); + + p.cancel(); + return awaitLateQueue(function() { + assert.equal(1, cancelled); + }); + }); + + specify("downstream follower promises' handlers are called, registered after", function() { + var cancelled = 0; + var p = new Promise(function(_, __, onCancel) { + onCancel(function() {cancelled++}); + }); + + p.cancel(); + new Promise(function(resolve, _, onCancel) { + resolve(p.then()); + onCancel(function() {cancelled++}); + }).suppressUnhandledRejections(); + new Promise(function(resolve, _, onCancel) { + resolve(p.then()); + onCancel(function() {cancelled++}); + }).suppressUnhandledRejections(); + new Promise(function(resolve, _, onCancel) { + resolve(p.then()); + onCancel(function() {cancelled++}); + }).suppressUnhandledRejections(); + return awaitLateQueue(function() { + assert.equal(1, cancelled); + }); + }); + + specify("immediately rejected promise immediately cancelled with then in-between", function() { + var error = new Error(); + var resolve; + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + var p = Promise.reject(error).then().lastly(resolve); + p.cancel(); + p.caught(function() {}); + return result; + }); + + specify("callback is called asynchronously but fate is sealed synchronously", function() { + var called = false; + var promiseResolve; + var promise = new Promise(function(resolve, reject, onCancel) { + promiseResolve = resolve; + onCancel(function() { + called = true; + }); + }); + return awaitLateQueue(function() { + promise.cancel(); + promiseResolve(); + return Promise.resolve().then(function() { + assert(called); + assert(!promise.isFulfilled()); + }); + }); + }); + + if (testUtils.isNodeJS) { + specify("throws in process if callback throws", function() { + var e = new Error(); + var promise = new Promise(function(resolve, reject, onCancel) { + onCancel(function onCancel() { + throw e; + }); + }); + promise.cancel(); + return testUtils.awaitGlobalException(function(err) { + assert.equal(e, err); + }); + }); + } + + specify("cancels the promise chain", function() { + var called = false; + var thens = 0; + var resolveChain; + var root = new Promise(function(resolve, reject, onCancel) { + resolveChain = resolve; + onCancel(function() { + called = true; + }); + }).then(function() { + thens++; + }).then(function() { + thens++; + }).then(function() { + thens++; + }); + + root.cancel(); + resolveChain(); + return awaitLateQueue(function() { + assert.equal(0, thens); + assert(called); + }); + }); + + specify("calls finally handlers", function() { + var called = false; + var thens = 0; + var resolveChain; + var root = new Promise(function(resolve, reject, onCancel) { + resolveChain = resolve; + onCancel(function() { + called = true; + }); + }); + var chain = root.lastly(function() { + thens++; + }).lastly(function() { + thens++; + }).lastly(function() { + thens++; + }); + + chain.cancel(); + resolveChain(); + return awaitLateQueue(function() { + assert.equal(3, thens); + assert(called); + }); + }); + + specify("cancels the followee", function() { + var called = false; + var finalled = false; + var promise = new Promise(function(_, __, onCancel) { + onCancel(function() { + called = true; + }); + }); + var promise2 = new Promise(function(resolve, reject, onCancel) { + resolve(promise); + }); + var promise3 = new Promise(function(resolve, reject, onCancel) { + resolve(promise2); + }).lastly(function() { + finalled = true; + }); + + promise3.cancel(); + return awaitLateQueue(function() { + assert(called); + assert(finalled); + }); + }); + + specify("cancels the followee, calling all callbacks and finally handlers", function() { + var called = 0; + var finalled = 0; + + var promise = new Promise(function(_, __, onCancel) { + onCancel(function() { + called++; + }); + }).lastly(function() { + finalled++; + }); + + var promise2 = new Promise(function(resolve, reject, onCancel) { + resolve(promise); + onCancel(function() { + called++; + }); + }).lastly(function() { + finalled++; + }); + + var promise3 = new Promise(function(resolve, reject, onCancel) { + resolve(promise2); + onCancel(function() { + called++; + }); + }).lastly(function() { + finalled++; + }); + + promise3.cancel(); + return awaitLateQueue(function() { + assert.equal(3, called); + assert.equal(3, finalled); + }); + }); + + specify("cancels the followee, calling all onCancel callbacks", function() { + var called = 0; + + var promise = new Promise(function(_, __, onCancel) { + onCancel(function() { + called++; + }); + }) + + var promise2 = new Promise(function(resolve, reject, onCancel) { + resolve(promise); + onCancel(function() { + called++; + }); + }); + + var promise3 = new Promise(function(resolve, reject, onCancel) { + resolve(promise2); + onCancel(function() { + called++; + }); + }); + + promise3.cancel(); + return awaitLateQueue(function() { + assert.equal(3, called); + }); + }); + + specify("can be used for breaking chains early", function() { + var called = false; + var p = Promise.resolve(1) + .then(function(data) { + p["break"](); + }) + .then(function() { + called = true; + }); + return awaitLateQueue(function() { + assert(!called); + }); + }); + + specify("multiple cancel calls have no effect", function() { + var called = 0; + var finalled = 0; + var req1 = new Promise(function(resolve, _, onCancel) { + resolve(); + onCancel(function() { + called++; + }); + }); + + var ret = req1.then(function() { + return new Promise(function(_, __, onCancel) { + onCancel(function() { + called++; + }); + }); + }).then(function() { + return new Promise(function(_, __, onCancel) { + onCancel(function() { + called++; + }); + }); + }).lastly(function() { + finalled++; + }); + + req1.then(function() { + ret.cancel(); + ret.cancel(); + ret.cancel(); + }); + + return awaitLateQueue(function() { + assert.equal(1, called); + assert.equal(1, finalled); + }); + }); + + specify("throwing in finally turns into a rejection", function() { + var e = new Error(""); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + throw e; + }) + .caught(function(err) { + assert.equal(err, e); + }); + promise.cancel(); + return promise; + }); + + specify("returning an immediately rejected promise in finally turns into a rejection", function() { + var e = new Error(""); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + return Promise.reject(e); + }) + .caught(function(err) { + assert.equal(err, e); + }); + promise.cancel(); + return promise; + }); + specify("returning an eventually rejected promise in finally turns into a rejection", function() { + var e = new Error(""); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + return new Promise(function(resolve, reject, onCancel) { + Promise.delay(1).then(function() { + reject(e); + }); + }); + }) + .caught(function(err) { + assert.equal(err, e); + }); + promise.cancel(); + return promise; + }); + + specify("finally handler returned promises are awaited for", function() { + var awaited = 0; + var resolve; + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + return Promise.delay(1).then(function() { + awaited++; + }); + }) + .lastly(function() { + return Promise.delay(1).then(function() { + awaited++; + }); + }) + .lastly(function() { + return Promise.delay(1).then(function() { + awaited++; + }); + }) + .lastly(function() { + resolve(); + }) + promise.cancel(); + return result.then(function() { + assert.equal(3, awaited); + }); + }); + + specify("finally handler returned promises are skipped if they are cancelled", function() { + var cancelled = 0; + var resolve; + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + var ret = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + ret.cancel(); + return ret; + }) + .lastly(function() { + var ret = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + ret.cancel(); + return ret; + }) + .lastly(function() { + var ret = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + ret.cancel(); + return ret; + }) + .lastly(function() { + resolve(); + }) + promise.suppressUnhandledRejections(); + promise.cancel(); + return result.then(function() { + assert.equal(3, cancelled); + }); + }); + + specify("finally handler returned promises are skipped if they are eventually cancelled", function() { + var cancelled = 0; + var resolve; + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + var ret = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return ret; + }) + .lastly(function() { + var ret = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return ret; + }) + .lastly(function() { + var ret = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return ret; + }) + .lastly(function() { + resolve(); + }) + promise.cancel(); + return result.then(function() { + assert.equal(3, cancelled); + }); + }); + + specify("finally handler returned promises are skipped if theiy are eventually cancelled while following", function() { + var cancelled = 0; + var resolve; + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return ret; + }) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return ret; + }) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return ret; + }) + .lastly(function() { + resolve(); + }) + promise.cancel(); + return result.then(function() { + assert.equal(6, cancelled); + }); + }); + + specify("finally handler returned promises are skipped if theiy are immediately cancelled while following", function() { + var cancelled = 0; + var resolve; + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + ret.cancel(); + return ret; + }) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + ret.cancel(); + return ret; + }) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + ret.cancel(); + return ret; + }) + .lastly(function() { + resolve(); + }); + promise.suppressUnhandledRejections(); + promise.cancel(); + return result.then(function() { + assert.equal(6, cancelled); + }); + }); + + specify("finally handler returned promises target are skipped if their follower is eventually cancelled", function() { + var cancelled = 0; + var resolve; + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return p; + }) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return p; + }) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return p; + }) + .lastly(function() { + resolve(); + }) + promise.cancel(); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(6, cancelled); + }); + }); + }); + + specify("finally handler returned promises target are skipped if their follower is immediately cancelled", function() { + var cancelled = 0; + var resolve; + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + var promise = new Promise(function(_, __, onCancel) {}) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + ret.cancel(); + return p; + }) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + ret.cancel(); + return p; + }) + .lastly(function() { + var p = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }); + var ret = new Promise(function(resolve, _, onCancel) { + resolve(p); + onCancel(function() { + cancelled++; + }); + }); + ret.cancel(); + return p; + }) + .lastly(function() { + resolve(); + }) + promise.cancel(); + promise.suppressUnhandledRejections(); + return result.then(function() { + assert.equal(6, cancelled); + }); + }); + + specify("attaching handler on already cancelled promise", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + return awaitLateQueue(function() { + p.lastly(resolve); + return result; + }); + }); + + specify("if onCancel callback causes synchronous rejection, it is ignored and cancellation wins", function() { + var promisifiedXhr = function() { + var xhrReject; + var xhr = { + then: function(resolve, reject) { + xhrReject = reject; + }, + abort: function() { + xhrReject(new Error("")); + } + }; + return new Promise(function(resolve, _, onCancel) { + resolve(xhr); + onCancel(function() { + xhr.abort(); + }); + }); + }; + + var req = promisifiedXhr().lastly(function() { + resolve(); + }); + req.cancel(); + var resolve; + return new Promise(function(_, __, onCancel) {resolve = arguments[0]}); + }); + + specify("isCancelled() synchronously returns true after calling cancel() on pending promise", function() { + var promise = new Promise(function () {}); + promise.cancel(); + assert(promise.isCancelled()); + }); + + specify("isCancelled() synchronously returns true after calling cancel() on promise created from .then()", function() { + var promise = new Promise(function () {}); + var thenPromise = promise.then(); + thenPromise.cancel(); + assert(thenPromise.isCancelled()); + }); + + specify("gh-166", function() { + var f1 = false, f2 = false, f3 = false, f4 = false; + var a = Promise.resolve(); + a = a.then(function() { + f1 = true; + return Promise.delay(1); + }); + + a = a.then(function() { + f2 = true; + return Promise.delay(1); + }); + + a = a.then(function() { + f3 = true; + return Promise.delay(1); + }).then(function() { + assert(a.isCancellable()); + a.cancel(); + }).delay(100); + + + a = a.then(function() { + f4 = true; + }); + + var waitingForLongDelay = a; + + a = a.lastly(function() { + assert(f1); assert(f2); assert(f3); + assert(!f4); + assert(waitingForLongDelay.isCancelled()); + resolve(); + }); + + assert(a.isCancellable()); + var resolve; + var p = new Promise(function(_, __, onCancel) {resolve = arguments[0]}); + return p; + }); + + specify("gh-1187", function() { + var a = Promise.delay(300).lastly(function() {}); + a.cancel(); + assert(a.isCancelled()); + assert(!a.isCancellable()); + }) +}); + +describe("Cancellation with .all", function() { + specify("immediately cancelled input", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + Promise.all(p).lastly(resolve); + return result; + }); + + specify("eventually cancelled input", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + Promise.all(p).lastly(resolve); + return awaitLateQueue(function() { + p.cancel(); + return result; + }); + }); + + specify("immediately cancelled input inside array", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + Promise.all([1,2,p]).lastly(resolve); + return result; + }); + + specify("eventually cancelled input inside array", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + Promise.all([1,2,p]).lastly(resolve); + return awaitLateQueue(function() { + p.cancel(); + return result; + }); + }); + + specify("immediately cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.all(inputs) + .lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); + + specify("eventually cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.all(inputs) + .lastly(function() {finalled++; resolve(); }); + + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); + + specify("immediately cancelled output while waiting on promise-for-input", function() { + var cancelled = 0; + var finalled = 0; + var input = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }).lastly(function() { + finalled++; + }); + + var all = Promise.all(input) + .lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 1); + assert.equal(finalled, 2); + }); + }); + }); + + specify("eventually cancelled output while waiting on promise-for-input", function() { + var cancelled = 0; + var finalled = 0; + var input = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }).lastly(function() { + finalled++; + }); + + var all = Promise.all(input).lastly(function() {finalled++; resolve(); }); + + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 1); + assert.equal(finalled, 2); + }); + }); + }); +}); + +describe("Cancellation with .props", function() { + specify("immediately cancelled input", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + Promise.props(p).lastly(resolve).suppressUnhandledRejections(); + return result; + }); + + specify("eventually cancelled input", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + Promise.props(p).lastly(resolve); + return awaitLateQueue(function() { + p.cancel(); + return result; + }); + }); + + specify("immediately cancelled input inside array", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + Promise.props([1,2,p]).lastly(resolve); + return result; + }); + + specify("eventually cancelled input inside array", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + Promise.props([1,2,p]).lastly(resolve); + return awaitLateQueue(function() { + p.cancel(); + return result; + }); + }); + + specify("immediately cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.props(inputs).lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); + + specify("eventually cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.props(inputs).lastly(function() {finalled++; resolve(); }); + + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); + + specify("immediately cancelled output while waiting on promise-for-input", function() { + var cancelled = 0; + var finalled = 0; + var input = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }).lastly(function() { + finalled++; + }); + + var all = Promise.props(input).lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 1); + assert.equal(finalled, 2); + }); + }); + }); + + specify("eventually cancelled output while waiting on promise-for-input", function() { + var cancelled = 0; + var finalled = 0; + var input = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }).lastly(function() { + finalled++; + }); + + var all = Promise.props(input) + .lastly(function() {finalled++; resolve(); }); + + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 1); + assert.equal(finalled, 2); + }); + }); + }); +}); + +describe("Cancellation with .some", function() { + specify("immediately cancelled input", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + Promise.some(p, 1).lastly(resolve); + return result; + }); + + specify("eventually cancelled input", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + Promise.some(p, 1).lastly(resolve); + return awaitLateQueue(function() { + p.cancel(); + return result; + }); + }); + + specify("immediately cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.some(inputs, 1) + .lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); + + specify("eventually cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.some(inputs, 1) + .lastly(function() {finalled++; resolve(); }); + + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); + + + specify("immediately cancelled output while waiting on promise-for-input", function() { + var cancelled = 0; + var finalled = 0; + var input = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }).lastly(function() { + finalled++; + }); + + var all = Promise.some(input, 1) + .lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 1); + assert.equal(finalled, 2); + }); + }); + }); + + specify("eventually cancelled output while waiting on promise-for-input", function() { + var cancelled = 0; + var finalled = 0; + var input = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }).lastly(function() { + finalled++; + }); + + var all = Promise.some(input, 1) + .lastly(function() {finalled++; resolve(); }); + + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 1); + assert.equal(finalled, 2); + }); + }); + }); + + specify("some promises are cancelled immediately", function() { + var resolve; + var p1 = new Promise(function(_, __, onCancel) {}); + var p2 = new Promise(function(_, __, onCancel) {}); + var p3 = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + + p1.cancel(); + p2.cancel(); + resolve(1); + return Promise.some([p1, p2, p3], 1).then(function(result) { + assert.deepEqual([1], result); + }); + }); + + specify("some promises are cancelled eventually", function() { + var resolve; + var p1 = new Promise(function(_, __, onCancel) {}); + var p2 = new Promise(function(_, __, onCancel) {}); + var p3 = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + + Promise.delay(1).then(function() { + p1.cancel(); + p2.cancel(); + return Promise.delay(1).then(function() { + resolve(1); + }); + }); + return Promise.some([p1, p2, p3], 1).then(function(result) { + assert.deepEqual([1], result); + }); + }); + + specify("promise for some promises that are cancelled immediately", function() { + var resolve; + var p1 = new Promise(function(_, __, onCancel) {}); + var p2 = new Promise(function(_, __, onCancel) {}); + var p3 = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + + p1.cancel(); + p2.cancel(); + resolve(1); + var promise = Promise.delay(1, [p1, p2, p3]); + return Promise.some(promise, 1).then(function(result) { + assert.deepEqual([1], result); + }); + }); + + specify("promise for some promises that are cancelled eventually", function() { + var resolve; + var p1 = new Promise(function(_, __, onCancel) {}); + var p2 = new Promise(function(_, __, onCancel) {}); + var p3 = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + + Promise.delay(1).then(function() { + p1.cancel(); + p2.cancel(); + return Promise.delay(1).then(function() { + resolve(1); + }); + }); + var promise = Promise.delay(1, [p1, p2, p3]); + return Promise.some(promise, 1).then(function(result) { + assert.deepEqual([1], result); + }); + }); + + + specify("all promises cancel, not enough for fulfillment - immediately", function() { + var resolve; + var p1 = new Promise(function(_, __, onCancel) {}); + var p2 = new Promise(function(_, __, onCancel) {}); + var p3 = new Promise(function(_, __, onCancel) {}); + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + + p1.cancel(); + p2.cancel(); + p3.cancel(); + Promise.some([p1, p2, p3], 1).then(assert.fail, function(e) { + assert(e instanceof Promise.CancellationError); + resolve(); + }); + return result; + }); + + specify("all promises cancel, not enough for fulfillment - eventually", function() { + var resolve; + var p1 = new Promise(function(_, __, onCancel) {}); + var p2 = new Promise(function(_, __, onCancel) {}); + var p3 = new Promise(function(_, __, onCancel) {}); + var result = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + + Promise.delay(1).then(function() { + p1.cancel(); + p2.cancel(); + return Promise.delay(1).then(function() { + p3.cancel(); + }); + }); + Promise.some([p1, p2, p3], 1).then(assert.fail, assert.fail).lastly(resolve); + return result; + }); + + specify("some promises cancel, some reject, not enough for fulfillment - immediately", function() { + var error = new Error(); + var reject; + var p1 = new Promise(function(_, __, onCancel) {}); + var p2 = new Promise(function(_, __, onCancel) {}); + var p3 = new Promise(function(_, __, onCancel) {reject = arguments[1];}); + + p1.cancel(); + p2.cancel(); + reject(error); + return Promise.some([p1, p2, p3], 1).then(assert.fail, function(result) { + assert(result instanceof Promise.AggregateError); + assert.equal(1, result.length); + assert.equal(error, result[0]); + }); + }); + + specify("some promises cancel, some reject, not enough for fulfillment - eventually", function() { + var error = new Error(); + var reject; + var p1 = new Promise(function(_, __, onCancel) {}); + var p2 = new Promise(function(_, __, onCancel) {}); + var p3 = new Promise(function(_, __, onCancel) {reject = arguments[1];}); + + Promise.delay(1).then(function() { + p1.cancel(); + p2.cancel(); + return Promise.delay(1).then(function() { + reject(error); + }); + }); + return Promise.some([p1, p2, p3], 1).then(assert.fail, function(result) { + assert(result instanceof Promise.AggregateError); + assert.equal(1, result.length); + assert.equal(error, result[0]); + }); + }); +}); + +describe("Cancellation with .reduce", function() { + specify("initialValue immediately cancelled immediate input", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + initialValue.cancel(); + Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + + specify("initialValue eventually cancelled immediate input", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + initialValue.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + }); + }); + + specify("initialValue eventually cancelled eventual input", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = new Promise(function(_, __, onCancel) {}); + Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + initialValue.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + }); + }); + + specify("initialValue immediately cancelled eventual input", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = new Promise(function(_, __, onCancel) {}); + initialValue.cancel(); + Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + + specify("returned promise cancels immediately", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [1, 2, + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + Promise.reduce(inputs, function(a, b) { + finalled++; + var ret = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}); + ret.cancel(); + return ret; + }).lastly(function() { + finalled++; + }); + + return awaitLateQueue(function() { + assert.equal(3, finalled); + assert.equal(1, cancelled); + }); + }); + + specify("returned promise cancels eventually", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [1, 2, + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + Promise.reduce(inputs, function(a, b) { + finalled++; + var ret = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}); + awaitLateQueue(function() { + ret.cancel(); + }); + return ret; + }).lastly(function() { + finalled++; + }); + + return awaitLateQueue(function() { + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(3, finalled); + assert.equal(1, cancelled); + }); + }); + }); + }); + + specify("input immediately cancelled while waiting initialValue", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = new Promise(function(_, __, onCancel) {}); + inputs.cancel(); + Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + assert.equal(1, finalled); + assert.equal(0, cancelled); + }); + }); + + specify("input eventually cancelled while waiting initialValue", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = new Promise(function(_, __, onCancel) {}); + Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + inputs.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(1, finalled); + assert.equal(0, cancelled); + }); + }); + }); + }); + + specify("output immediately cancelled while waiting inputs", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}); + + var all = Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + all.cancel(); + return awaitLateQueue(function() { + assert.equal(3, finalled); + assert.equal(2, cancelled); + }); + }); + + specify("output immediately cancelled while waiting initialValue", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + var all = Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + all.cancel(); + return awaitLateQueue(function() { + assert.equal(5, finalled); + assert.equal(4, cancelled); + }); + }); + + specify("output immediately cancelled while waiting firstValue", function() { + var initialValue = 1; + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + var all = Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + all.cancel(); + return awaitLateQueue(function() { + assert.equal(4, finalled); + assert.equal(3, cancelled); + }); + }); + + specify("output immediately cancelled while waiting firstValue and secondValue", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + var all = Promise.reduce(inputs, function(){ + finalled++; + }).lastly(function() { + finalled++; + }); + all.cancel(); + return awaitLateQueue(function() { + assert.equal(4, finalled); + assert.equal(3, cancelled); + }); + }); + + specify("output immediately cancelled while waiting for a result", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [1, 2, + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + var all = Promise.reduce(inputs, function(a, b) { + finalled++; + return new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}); + }).lastly(function() { + finalled++; + }); + all.cancel(); + return awaitLateQueue(function() { + assert.equal(4, finalled); + assert.equal(2, cancelled); + }); + }); + + specify("output eventually cancelled while waiting inputs", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}); + + var all = Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + all.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(3, finalled); + assert.equal(2, cancelled); + }); + }); + }); + }); + + specify("output eventually cancelled while waiting initialValue", function() { + var initialValue = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + var all = Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + all.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(5, finalled); + assert.equal(4, cancelled); + }); + }); + }); + }); + + specify("output eventually cancelled while waiting firstValue", function() { + var initialValue = 1; + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + var all = Promise.reduce(inputs, function(){ + finalled++; + }, initialValue).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + all.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(4, finalled); + assert.equal(3, cancelled); + }); + }); + }); + }); + + specify("output eventually cancelled while waiting firstValue and secondValue", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + var all = Promise.reduce(inputs, function(){ + finalled++; + }).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + all.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(4, finalled); + assert.equal(3, cancelled); + }); + }); + }); + }); + + specify("output eventually cancelled while waiting for a result", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [1, 2, + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + var all = Promise.reduce(inputs, function(a, b) { + finalled++; + return new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}); + }).lastly(function() { + finalled++; + }); + + return awaitLateQueue(function() { + all.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(4, finalled); + assert.equal(2, cancelled); + }); + }); + }); + }); +}); + +describe("Cancellation with .map", function() { + specify("immediately cancelled input", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + Promise.map(p, function(){}).lastly(resolve); + return result; + }); + + specify("eventually cancelled input", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + Promise.map(p, function(){}).lastly(resolve); + return awaitLateQueue(function() { + p.cancel(); + return result; + }); + }); + + specify("immediately cancelled input inside array", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + Promise.map([1,2,p], function(){}).lastly(resolve); + return result; + }); + + specify("eventually cancelled input inside array", function() { + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + var p = new Promise(function(_, __, onCancel) {}); + Promise.map([1,2,p], function(){}).lastly(resolve); + return awaitLateQueue(function() { + p.cancel(); + return result; + }); + }); + + specify("immediately cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.map(inputs, function(){}) + .lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); + + specify("eventually cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.map(inputs, function(){}) + .lastly(function() {finalled++; resolve(); }); + + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); + + specify("immediately cancelled output while waiting on promise-for-input", function() { + var cancelled = 0; + var finalled = 0; + var input = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }).lastly(function() { + finalled++; + }); + + var all = Promise.map(input, function(){}) + .lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 1); + assert.equal(finalled, 2); + }); + }); + }); + + specify("eventually cancelled output while waiting on promise-for-input", function() { + var cancelled = 0; + var finalled = 0; + var input = new Promise(function(_, __, onCancel) { onCancel(function(){ cancelled++; }); }).lastly(function() { + finalled++; + }); + + var all = Promise.map(input, function(){}) + .lastly(function() {finalled++; resolve(); }); + + var resolve; + var result = new Promise(function() {resolve = arguments[0]}); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 1); + assert.equal(finalled, 2); + }); + }); + }); + + specify("result cancelled immediately while there are in-flight returned promises", function() { + var cancelled = 0; + var finalled = 0; + + var all = Promise.map([1, 2, 3], function(value, index) { + return new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + }).lastly(function() { + finalled++; + }); + all.cancel(); + return awaitLateQueue(function() { + assert.equal(4, finalled); + assert.equal(3, cancelled); + }); + }); + + specify("result cancelled eventually while there are in-flight returned promises", function() { + var cancelled = 0; + var finalled = 0; + + var all = Promise.map([1, 2, 3], function(value, index) { + return new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + }).lastly(function() { + finalled++; + }); + + return awaitLateQueue(function() { + all.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(4, finalled); + assert.equal(3, cancelled); + }); + }); + }); + }); + + specify("returned promise cancelled immediately while there are in-flight returned promises", function() { + var cancelled = 0; + var finalled = 0; + + Promise.map([1, 2, 3], function(value, index) { + var ret = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}); + if (index === 2) { + ret.cancel(); + } + return ret; + }).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + + specify("returned promise cancelled eventually while there are in-flight returned promises", function() { + var cancelled = 0; + var finalled = 0; + + Promise.map([1, 2, 3], function(value, index) { + var ret = new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}); + if (index === 2) { + awaitLateQueue(function() { + ret.cancel(); + }); + } + return ret; + }).lastly(function() { + finalled++; + }); + + return awaitLateQueue(function() { + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + }); + }); +}); +describe("Cancellation with .bind", function() { + specify("immediately cancelled promise passed as ctx", function() { + var finalled = 0; + var cancelled = 0; + var ctx = new Promise(function(_, __, onCancel) {}); + ctx.cancel(); + Promise.bind(ctx).lastly(function() { + finalled++; + }).suppressUnhandledRejections(); + return awaitLateQueue(function() { + assert.equal(1, finalled); + assert.equal(0, cancelled); + }); + }); + + specify("eventually cancelled promise passed as ctx", function() { + var finalled = 0; + var cancelled = 0; + var ctx = new Promise(function(_, __, onCancel) {}); + Promise.bind(ctx).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + ctx.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(1, finalled); + assert.equal(0, cancelled); + }); + }); + }); + }); + + specify("main promise is immediately cancelled while waiting on binding", function() { + var finalled = 0; + var cancelled = 0; + var resolve; + var ctx = new Promise(function(_, __, onCancel) {resolve = arguments[0];}); + var main = new Promise(function(_, __, onCancel) {}); + main.cancel(); + main.bind(ctx).lastly(function() { + finalled++; + }).suppressUnhandledRejections(); + return awaitLateQueue(function() { + resolve(); + return ctx; + }).then(function() { + return awaitLateQueue(function() { + assert.equal(1, finalled); + assert.equal(0, cancelled); + }); + }); + }); + + specify("main promise is eventually cancelled while waiting on binding", function() { + var finalled = 0; + var cancelled = 0; + var ctx = new Promise(function(_, __, onCancel) {}); + var main = new Promise(function(_, __, onCancel) {}); + + main.bind(ctx).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + main.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(1, finalled); + assert.equal(0, cancelled); + }); + }); + }); + }); + + specify("main promise is immediately cancelled with immediate binding", function() { + var finalled = 0; + var cancelled = 0; + var ctx = {}; + var main = new Promise(function(_, __, onCancel) {}); + main.bind(ctx).lastly(function() { + assert.equal(this, ctx); + finalled++; + }); + main.cancel(); + return awaitLateQueue(function() { + assert.equal(1, finalled); + assert.equal(0, cancelled); + }); + }); + + specify("main promise is eventually cancelled with immediate binding", function() { + var finalled = 0; + var cancelled = 0; + var ctx = {}; + var main = new Promise(function(_, __, onCancel) {}); + main.bind(ctx).lastly(function() { + assert.equal(this, ctx); + finalled++; + }); + return awaitLateQueue(function() { + main.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(1, finalled); + assert.equal(0, cancelled); + }); + }); + }); + }); + + specify("result is immediately cancelled while waiting for binding", function() { + var finalled = 0; + var cancelled = 0; + var ctx = new Promise(function(_, __, onCancel) { + onCancel(function() { + cancelled++; + }); + }).lastly(function() { + finalled++; + }); + var result = Promise.bind(ctx).lastly(function() { + finalled++; + }); + result.cancel(); + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + + specify("result is eventually cancelled while waiting for binding", function() { + var finalled = 0; + var cancelled = 0; + var ctx = new Promise(function(_, __, onCancel) { + onCancel(function() { + cancelled++; + }); + }).lastly(function() { + finalled++; + }); + + var result = Promise.bind(ctx).lastly(function() { + finalled++; + }); + return awaitLateQueue(function() { + result.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + }); + }); + + specify("result is immediately cancelled while waiting for main promise", function() { + var finalled = 0; + var cancelled = 0; + var ctx = {}; + var main = new Promise(function(_, __, onCancel) { + onCancel(function() { + cancelled++; + }); + }).lastly(function() { + finalled++; + }); + + var result = main.bind(ctx).lastly(function() { + assert.equal(this, ctx); + finalled++; + }); + result.cancel(); + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + + specify("result is eventually cancelled while waiting for main promise", function() { + var finalled = 0; + var cancelled = 0; + var ctx = {}; + var main = new Promise(function(_, __, onCancel) { + onCancel(function() { + cancelled++; + }); + }).lastly(function() { + finalled++; + }); + + var result = main.bind(ctx).lastly(function() { + assert.equal(this, ctx); + finalled++; + }); + return awaitLateQueue(function() { + result.cancel(); + return Promise.resolve().then(function() { + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(1, cancelled); + }); + }); + }); + }); +}); + +describe("Cancellation with .join", function() { + specify("immediately cancelled input inside array", function() { + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + Promise.join(1,2,p, assert.fail).then(reject, function(e) { + assert(e instanceof Promise.CancellationError); + resolve(); + }); + return result; + }); + + specify("eventually cancelled input inside array", function() { + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + var p = new Promise(function(_, __, onCancel) {}); + Promise.join(1,2,p, assert.fail).then(reject, reject).lastly(resolve); + return awaitLateQueue(function() { + p.cancel(); + return result; + }); + }); + + specify("immediately cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.join(inputs[0], inputs[1], inputs[2], assert.fail) + .then(reject, reject) + .lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); + + specify("eventually cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + ]; + + var all = Promise.join(inputs[0], inputs[1], inputs[2], assert.fail) + .then(reject, reject) + .lastly(function() {finalled++; resolve(); }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + }); + }); + }); +}); + +describe("Cancellation with .reflect", function() { + specify("immediately cancelled", function() { + var promise = new Promise(function(_, __, onCancel) {}); + promise.cancel(); + return promise.reflect().then(function(value) { + assert(!value.isFulfilled()); + assert(!value.isRejected()); + assert(!value.isPending()); + assert(value.isCancelled()); + }); + }); + + specify("eventually cancelled", function() { + var promise = new Promise(function(_, __, onCancel) {}); + + var ret = promise.reflect().then(function(value) { + assert(!value.isFulfilled()); + assert(!value.isRejected()); + assert(!value.isPending()); + assert(value.isCancelled()); + }); + + Promise.delay(1).then(function() { + promise.cancel(); + }); + return ret; + }); +}); + +describe("Cancellation with .using", function() { + specify("immediately cancelled input", function() { + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + + var disposerCalled = false; + var disposable = new Promise(function(_, __, onCancel) { + setTimeout(arguments[0], 1); + }).disposer(function() { + disposerCalled = true; + }); + + Promise.using(1, disposable, p, assert.fail).then(reject, function(e) { + assert(e instanceof Promise.CancellationError); + assert(disposerCalled); + resolve(); + }); + + return result; + }); + + specify("eventually cancelled input", function() { + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + var p = new Promise(function(_, __, onCancel) {}); + + var disposerCalled = false; + var disposable = new Promise(function(_, __, onCancel) { + setTimeout(arguments[0], 1); + }).disposer(function() { + disposerCalled = true; + }); + + Promise.using(1, disposable, p, assert.fail).then(reject, reject).lastly(function() { + assert(disposerCalled); + resolve(); + }); + + return awaitLateQueue(function() { + p.cancel(); + return result; + }); + }); + + specify("eventually cancelled input with 1 fulfilled disposer", function() { + var resolve, reject; + var fulfillResource; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + var p = new Promise(function(_, __, onCancel) {}); + + var disposerCalled = false; + var disposable = new Promise(function(_, __, onCancel) {fulfillResource = arguments[0];}).disposer(function() { + disposerCalled = true; + }); + + Promise.using(1,disposable,p, assert.fail).then(reject, reject).lastly(function() { + assert(disposerCalled); + resolve(); + }); + + return awaitLateQueue(function() { + fulfillResource({}); + p.cancel(); + return result; + }); + }); + + specify("immediately cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var disposerCalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + .disposer(function() { + disposerCalled++; + }), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + .disposer(function() { + disposerCalled++; + }), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + .disposer(function() { + disposerCalled++; + }) + ]; + + var all = Promise.using(inputs[0], inputs[1], inputs[2], assert.fail) + .then(reject, reject) + .lastly(function() {finalled++; resolve(); }); + + all.cancel(); + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + assert.equal(disposerCalled, 0); + }); + }); + }); + + specify("eventually cancelled output", function() { + var cancelled = 0; + var finalled = 0; + var disposerCalled = 0; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + .disposer(function() { + disposerCalled++; + }), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + .disposer(function() { + disposerCalled++; + }), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + .disposer(function() { + disposerCalled++; + }) + ]; + + var all = Promise.using(inputs[0], inputs[1], inputs[2], assert.fail) + .then(reject, reject) + .lastly(function() {finalled++; resolve(); }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + Promise.delay(1).then(function() { + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 3); + assert.equal(finalled, 4); + assert.equal(disposerCalled, 0); + }); + }); + }); + + specify("eventually cancelled output with 1 disposer fulfilled", function() { + var cancelled = 0; + var finalled = 0; + var disposerCalled = 0; + var fulfillResource; + var inputs = [ + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + .disposer(function() { + disposerCalled++; + }), + new Promise(function(_, __, onCancel) {fulfillResource = arguments[0];}) + .disposer(function() { + disposerCalled++; + }), + new Promise(function(_, __, onCancel) { onCancel(function() { cancelled++; }); }) + .lastly(function() {finalled++}) + .disposer(function() { + disposerCalled++; + }) + ]; + + var all = Promise.using(inputs[0], inputs[1], inputs[2], assert.fail) + .then(reject, reject) + .lastly(function() {finalled++; resolve(); }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + Promise.delay(1).then(function() { + fulfillResource({}) + all.cancel(); + }); + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 2); + assert.equal(finalled, 3); + assert.equal(disposerCalled, 1); + }); + }); + }); + + specify("result immediately cancelled when inside handler", function() { + var disposerCalled = 0; + var cancelled = 0; + var finalled = 0; + var resource1 = Promise.resolve().disposer(function() { + disposerCalled++; + }); + + var resource2 = Promise.resolve().disposer(function() { + disposerCalled++; + }); + + var all = Promise.using(resource1, resource2, function(res1, res2) { + var ret = new Promise(function(_, __, onCancel) {}); + all.cancel(); + return ret; + }).then(reject, reject) + .lastly(function() {finalled++; resolve(); }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 0); + assert.equal(finalled, 1); + assert.equal(disposerCalled, 2); + }); + }); + }); + + specify("result eventually cancelled when inside handler", function() { + var disposerCalled = 0; + var cancelled = 0; + var finalled = 0; + var resource1 = Promise.resolve().disposer(function() { + disposerCalled++; + }); + + var resource2 = Promise.resolve().disposer(function() { + disposerCalled++; + }); + + var all = Promise.using(resource1, resource2, function(res1, res2) { + var ret = new Promise(function(_, __, onCancel) {}); + Promise.delay(1).then(function() { + all.cancel(); + }); + return ret; + }).then(reject, reject) + .lastly(function() {finalled++; resolve(); }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 0); + assert.equal(finalled, 1); + assert.equal(disposerCalled, 2); + }); + }); + }); + + specify("promise returned from handler immediately cancelled", function() { + var disposerCalled = 0; + var cancelled = 0; + var finalled = 0; + var resource1 = Promise.resolve().disposer(function() { + disposerCalled++; + }); + + var resource2 = Promise.resolve().disposer(function() { + disposerCalled++; + }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + + var all = Promise.using(resource1, resource2, function(res1, res2) { + var ret = new Promise(function(_, __, onCancel) {}); + ret.cancel(); + return ret; + }).then(reject, function(e) { + if(!(e instanceof Promise.CancellationError)) reject(new Error()); + }).lastly(function() {finalled++; resolve(); }); + + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 0); + assert.equal(finalled, 1); + assert.equal(disposerCalled, 2); + }); + }); + }); + + specify("promise returned from handler eventually cancelled", function() { + var disposerCalled = 0; + var cancelled = 0; + var finalled = 0; + var resource1 = Promise.resolve().disposer(function() { + disposerCalled++; + }); + + var resource2 = Promise.resolve().disposer(function() { + disposerCalled++; + }); + + var all = Promise.using(resource1, resource2, function(res1, res2) { + var ret = new Promise(function(_, __, onCancel) {}); + Promise.delay(1).then(function() { + ret.cancel(); + }); + return ret; + }).then(reject, reject) + .lastly(function() {finalled++; resolve(); }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(cancelled, 0); + assert.equal(finalled, 1); + assert.equal(disposerCalled, 2); + }); + }); + }); +}); + +describe("Multi-branch cancellation", function() { + specify("3 branches, 1 cancels", function() { + var successCalls = 0; + var rootGotCancelled = false; + var resolveRoot; + var root = new Promise(function(resolve, __, onCancel) { + onCancel(function() { + rootGotCancelled = true; + }); + resolveRoot = resolve; + }); + + var a = root.then(function() { + successCalls++; + }); + var b = root.then(function() { + successCalls++; + }); + var c = root.then(function() { + successCalls++; + }); + + return awaitLateQueue(function() { + b.cancel(); + }).then(function() { + return awaitLateQueue(resolveRoot); + }).then(function() { + return awaitLateQueue(function() { + assert(!rootGotCancelled); + assert.equal(2, successCalls); + }); + }); + }); + + specify("3 branches, 3 cancels", function() { + var successCalls = 0; + var rootGotCancelled = false; + var resolveRoot; + var root = new Promise(function(resolve, __, onCancel) { + onCancel(function() { + rootGotCancelled = true; + }); + resolveRoot = resolve; + }); + + var a = root.then(function() { + successCalls++; + }); + var b = root.then(function() { + successCalls++; + }); + var c = root.then(function() { + successCalls++; + }); + + return awaitLateQueue(function() { + a.cancel(); + b.cancel(); + c.cancel(); + }).then(function() { + return awaitLateQueue(resolveRoot); + }).then(function() { + return awaitLateQueue(function() { + assert(rootGotCancelled); + assert.equal(0, successCalls); + }); + }); + }); + + specify("3 branches, root cancels", function() { + var successCalls = 0; + var rootGotCancelled = false; + var resolveRoot; + var root = new Promise(function(resolve, __, onCancel) { + onCancel(function() { + rootGotCancelled = true; + }); + resolveRoot = resolve; + }); + + var a = root.then(function() { + successCalls++; + }); + var b = root.then(function() { + successCalls++; + }); + var c = root.then(function() { + successCalls++; + }); + + return awaitLateQueue(function() { + root.cancel(); + }).then(function() { + return awaitLateQueue(resolveRoot); + }).then(function() { + return awaitLateQueue(function() { + assert(rootGotCancelled); + assert.equal(0, successCalls); + }); + }); + }); + + specify("3 branches, each have 3 branches, all children of b cancel", function() { + var successCalls = 0; + var rootGotCancelled = false; + var resolveRoot; + var root = new Promise(function(resolve, __, onCancel) { + onCancel(function() { + rootGotCancelled = true; + }); + resolveRoot = resolve; + }); + + var a = root.then(function() { + successCalls++; + }); + + var a1 = a.then(function() { + successCalls++; + }); + + var a2 = a.then(function() { + successCalls++; + }); + + var a3 = a.then(function() { + successCalls++; + }); + + var b = root.then(function() { + successCalls++; + }); + + var b1 = b.then(function() { + successCalls++; + }); + + var b2 = b.then(function() { + successCalls++; + }); + + var b3 = b.then(function() { + successCalls++; + }); + + var c = root.then(function() { + successCalls++; + }); + + var c1 = c.then(function() { + successCalls++; + }); + + var c2 = c.then(function() { + successCalls++; + }); + + var c3 = c.then(function() { + successCalls++; + }); + + return awaitLateQueue(function() { + b1.cancel(); + b2.cancel(); + b3.cancel(); + }).then(function() { + return awaitLateQueue(resolveRoot); + }).then(function() { + return awaitLateQueue(function() { + assert(!rootGotCancelled); + assert.equal(8, successCalls); + assert(b.isCancelled()); + assert(b1.isCancelled()); + assert(b2.isCancelled()); + assert(b3.isCancelled()); + }); + }); + }); + + specify("3 branches, each have 3 branches, all grand children cancel", function() { + var successCalls = 0; + var rootGotCancelled = false; + var resolveRoot; + var root = new Promise(function(resolve, __, onCancel) { + onCancel(function() { + rootGotCancelled = true; + }); + resolveRoot = resolve; + }); + + var a = root.then(function() { + successCalls++; + }); + + var a1 = a.then(function() { + successCalls++; + }); + + var a2 = a.then(function() { + successCalls++; + }); + + var a3 = a.then(function() { + successCalls++; + }); + + var b = root.then(function() { + successCalls++; + }); + + var b1 = b.then(function() { + successCalls++; + }); + + var b2 = b.then(function() { + successCalls++; + }); + + var b3 = b.then(function() { + successCalls++; + }); + + var c = root.then(function() { + successCalls++; + }); + + var c1 = c.then(function() { + successCalls++; + }); + + var c2 = c.then(function() { + successCalls++; + }); + + var c3 = c.then(function() { + successCalls++; + }); + + return awaitLateQueue(function() { + a1.cancel(); + a2.cancel(); + a3.cancel(); + b1.cancel(); + b2.cancel(); + b3.cancel(); + c1.cancel(); + c2.cancel(); + c3.cancel(); + }).then(function() { + return awaitLateQueue(resolveRoot); + }).then(function() { + return awaitLateQueue(function() { + assert(rootGotCancelled); + assert.equal(0, successCalls); + assert(a.isCancelled()); + assert(a1.isCancelled()); + assert(a2.isCancelled()); + assert(a3.isCancelled()); + assert(b.isCancelled()); + assert(b1.isCancelled()); + assert(b2.isCancelled()); + assert(b3.isCancelled()); + assert(c.isCancelled()); + assert(c1.isCancelled()); + assert(c2.isCancelled()); + assert(c3.isCancelled()); + }); + }); + }); +}); + + + +if (testUtils.isNodeJS) { + describe("issues", function() { + specify("cancels the promise chain within a domain GH963", function() { + var called = 0; + var thens = 0; + var resolveChain; + var Domain = require("domain"); + var domain = Domain.create(); + + domain.enter(); + + var root = new Promise(function(resolve, reject, onCancel) { + resolveChain = resolve; + onCancel(function() { + called++; + }); + }).then(function() { + thens++; + }).then(function() { + thens++; + }).then(function() { + thens++; + }).lastly(function() { + called++; + }); + + root.cancel(); + resolveChain(); + return awaitLateQueue(function() { + assert.equal(0, thens); + assert.equal(2, called); + domain.exit(); + }); + }); + }); + + describe("GH926", function() { + var clear, set; + var clears = 0; + before(function() { + clears = 0; + set = setTimeout; + clear = clearTimeout; + setTimeout = function() { + return set.apply(this, arguments); + }; + clearTimeout = function() { + clears++; + return clear.apply(this, arguments); + }; + }); + + after(function() { + clears = 0; + setTimeout = set; + clearTimeout = clear; + }); + + specify("GH926", function() { + var calls = 0; + var p = new Promise(function(resolve, reject, onCancel) { + onCancel(function() { calls++; }); + }) + .timeout(10000000) + .lastly(function() { + calls++; + }); + + p.cancel(); + + return awaitLateQueue(function() { + assert.equal(2, calls); + assert.equal(1, clears); + }); + }); + }); + + describe("GH1000", function() { + var clear, set; + var clears = 0, sets = 0; + beforeEach(function() { + clears = 0; + sets = 0; + set = setTimeout; + clear = clearTimeout; + setTimeout = function() { + sets++; + return set.apply(this, arguments); + }; + clearTimeout = function() { + clears++; + return clear.apply(this, arguments); + }; + }); + + afterEach(function() { + clears = 0; + sets = 0; + setTimeout = set; + clearTimeout = clear; + }); + + specify("delay", function() { + var calls = 0, + never = 0; + var p = Promise + .delay(10000000) + .then(function () { + never++; + }); + + p.lastly(function() { + if (p.isCancelled()) { + calls++; + } + }); + + p.cancel(); + + return awaitLateQueue(function() { + assert.equal(0, never); + assert.equal(1, calls); + assert.equal(1, clears); + }); + }); + + specify("delay with value", function() { + var calls = 0, + never = 0; + + var p = Promise + .delay(10000000, true) + .then(function () { + never++; + }); + + p.lastly(function() { + if (p.isCancelled()) { + calls++; + } + }); + + + + return Promise.delay(10) + .then(function () { + p.cancel(); + return awaitLateQueue(function() { + assert.equal(0, never); + assert.equal(1, calls); + assert.equal(1, clears); + }); + }); + }); + + specify("cancel delay cancels inner promise", function() { + var calls = 0, + never = 0; + var pInner = Promise.delay(1000) + .then(function () { + never++; + }); + + pInner.lastly(function() { + if (pInner.isCancelled()) { + calls++; + } + }); + + var pOuter = Promise + .delay(10000000, pInner) + .then(function () { + never++; + }); + + pOuter.lastly(function() { + if (pOuter.isCancelled()) { + calls++; + } + }); + + + + pOuter.cancel(); + return awaitLateQueue(function() { + assert.equal(0, never); + assert.equal(2, calls); + }); + }); + + specify("cancel inner promise cancels delay", function() { + var calls = 0, + never = 0; + var pInner = Promise.delay(1000) + .then(function () { + never++; + }); + + pInner.lastly(function() { + if (pInner.isCancelled()) { + calls++; + } + }); + + var pOuter = Promise + .delay(10000000, pInner) + .then(function () { + never++; + }); + + pOuter.lastly(function() { + if (pOuter.isCancelled()) { + calls++; + } + }); + + + + pInner.cancel(); + return awaitLateQueue(function() { + assert.equal(0, never); + assert.equal(2, calls); + }); + }); + }); +} diff --git a/test/mocha/catch_filter.js b/test/mocha/catch_filter.js new file mode 100644 index 0000000..0199c9d --- /dev/null +++ b/test/mocha/catch_filter.js @@ -0,0 +1,431 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +var CustomError = function(){}; + +CustomError.prototype = new Error(); + +var predicateFilter = function(e) { + return (/invalid/).test(e.message); +} + +function BadError(msg) { + this.message = msg; + return this; +} + +function predicatesUndefined(e) { + return e === void 0; +} + +function predicatesPrimitiveString(e) { + return /^asd$/.test(e); +} + +var token = {}; +var returnToken = function() { + return token; +}; + +var assertToken = function(val) { + assert.strictEqual(token, val); +}; + +describe("A promise handler that throws a TypeError must be caught", function() { + + specify("in a middle.caught filter", function() { + var a = Promise.defer(); + a.fulfill(3); + return a.promise.then(function(){ + a.b.c.d() + }).then(assert.fail).caught(SyntaxError, function(e){ + assert.fail(); + }).caught(Promise.TypeError, returnToken) + .then(assertToken); + }); + + + specify("in a generic.caught filter that comes first", function() { + var a = Promise.defer(); + + a.fulfill(3); + return a.promise.then(function(){ + a.b.c.d() + }).then(assert.fail, returnToken).caught(SyntaxError, function(e){ + assert.fail(); + }).caught(Promise.TypeError, function(e){ + assert.fail(); + }).then(assertToken); + + }); + + specify("in an explicitly generic.caught filter that comes first", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + a.b.c.d() + }) + .then(assert.fail) + .caught(Error, returnToken) + .caught(SyntaxError, assert.fail) + .caught(Promise.TypeError, assert.fail) + .then(assertToken); + }); + + specify("in a specific handler after thrown in generic", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + a.b.c.d() + }).then(assert.fail, function(e){ + throw e + }).caught(SyntaxError, assert.fail) + .then(assert.fail) + .caught(Promise.TypeError, returnToken) + .then(assertToken); + + + }); + + + specify("in a multi-filter handler", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + a.b.c.d() + }) + .then(assert.fail) + .caught(SyntaxError, TypeError, returnToken) + .then(assertToken); + }); + + + specify("in a specific handler after non-matching multi.caught handler", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + a.b.c.d() + }) + .then(assert.fail) + .caught(SyntaxError, CustomError, assert.fail) + .caught(Promise.TypeError, returnToken) + .then(assertToken) + }); + +}); + + +describe("A promise handler that throws a custom error", function() { + + specify("Is filtered if inheritance was done even remotely properly", function() { + var a = Promise.defer(); + var b = new CustomError(); + a.fulfill(3); + return a.promise.then(function(){ + throw b; + }) + .then(assert.fail) + .caught(SyntaxError, assert.fail) + .caught(Promise.TypeError, assert.fail) + .caught(CustomError, function(e){ + assert.equal(e, b); + return token; + }) + .then(assertToken); + + + }); + + specify("Is filtered along with built-in errors", function() { + var a = Promise.defer(); + var b = new CustomError(); + a.fulfill(3); + return a.promise.then(function(){ + throw b; + }) + .then(assert.fail) + .caught(Promise.TypeError, SyntaxError, CustomError, returnToken) + .caught(assert.fail) + .then(assertToken) + }); + + specify("Throws after matched type handler throws", function() { + var err = new Promise.TypeError(); + var err2 = new Error(); + return Promise.reject(err).caught(Promise.TypeError, function() { + throw err2; + }).then(assert.fail, function(e) { + assert.strictEqual(err2, e); + }); + }); +}); + +describe("A promise handler that throws a CustomError must be caught", function() { + specify("in a middle.caught filter", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + throw new CustomError() + }) + .caught(SyntaxError, assert.fail) + .caught(CustomError, returnToken) + .then(assertToken); + }); + + + specify("in a generic.caught filter that comes first", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + throw new CustomError() + }).then(assert.fail, returnToken) + .caught(SyntaxError, assert.fail) + .caught(CustomError, assert.fail) + .then(assertToken) + }); + + specify("in an explicitly generic.caught filter that comes first", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + throw new CustomError() + }) + .caught(Error, returnToken) + .caught(SyntaxError, assert.fail) + .caught(CustomError, assert.fail) + .then(assertToken); + }); + + specify("in a specific handler after thrown in generic", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + throw new CustomError() + }).then(assert.fail, function(e){ + throw e + }) + .caught(SyntaxError, assert.fail) + .caught(CustomError, returnToken) + .then(assertToken); + + }); + + + specify("in a multi-filter handler", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + throw new CustomError() + }) + .caught(SyntaxError, CustomError, returnToken) + .then(assertToken) + + }); + + + specify("in a specific handler after non-matching multi.caught handler", function() { + var a = Promise.defer(); + a.fulfill(3); + + return a.promise.then(function(){ + throw new CustomError() + }) + .caught(SyntaxError, TypeError, assert.fail) + .caught(CustomError, returnToken) + .then(assertToken); + }); + +}); + +describe("A promise handler that is caught in a filter", function() { + + specify("is continued normally after returning a promise in filter", function() { + var a = Promise.defer(); + var c = Promise.defer(); + var b = new CustomError(); + a.fulfill(3); + setTimeout(function(){ + c.fulfill(3); + }, 1); + return a.promise.then(function(){ + throw b; + }).caught(SyntaxError, function(e){ + assert.fail(); + }).caught(Promise.TypeError, function(e){ + assert.fail(); + }).caught(CustomError, function(e){ + assert.equal(e, b); + return c.promise.thenReturn(token); + }).then(assertToken, assert.fail, assert.fail); + }); + + specify("is continued normally after returning a promise in original handler", function() { + var a = Promise.defer(); + var c = Promise.defer(); + a.fulfill(3); + setTimeout(function(){ + c.fulfill(3); + }, 1); + return a.promise.then(function(){ + return c.promise; + }).caught(SyntaxError, function(e){ + assert.fail(); + }).caught(Promise.TypeError, function(e){ + assert.fail(); + }).caught(CustomError, function(e){ + assert.fail(); + }); + + }); + + specify("should throw type error for not passing function", function() { + try { + var a = Promise.reject(new Error("asd")); + a.caught(Promise.TypeError, "string"); + throw new Error("fail"); + } catch (e) { + if (e instanceof Promise.TypeError) { + return true; + } else { + throw new Error("fail"); + } + } + }); +}); + +describe("A promise handler with a predicate filter", function() { + + specify("will catch a thrown thing matching the filter", function() { + var a = Promise.defer(); + a.fulfill(3); + return a.promise.then(function(){ + throw new Error("horrible invalid error string"); + }).caught(predicateFilter, returnToken) + .then(assertToken); + + }); + specify("will NOT catch a thrown thing not matching the filter", function() { + var a = Promise.defer(); + a.fulfill(3); + return a.promise.then(function(){ + throw new Error("horrible valid error string"); + }).caught(predicateFilter, function(e){ + assert.fail(); + }).then(assert.fail, function(){}) + }); + + specify("will assert.fail when a predicate is a bad error class", function() { + var a = Promise.defer(); + a.fulfill(3); + return a.promise.then(function(){ + throw new Error("horrible custom error"); + }).caught(BadError, function(e){ + assert.fail(); + }).then(assert.fail, returnToken) + .then(assertToken); + + }); + + specify("will catch a thrown undefiend", function(){ + var a = Promise.defer(); + a.fulfill(3); + return a.promise.then(function(){ + throw void 0; + }).caught(function(e) { return false }, function(e){ + assert.fail(); + }).caught(predicatesUndefined, returnToken) + .then(assertToken); + + }); + + specify("will catch a thrown string", function(){ + var a = Promise.defer(); + a.fulfill(3); + return a.promise.then(function(){ + throw "asd"; + }).caught(function(e) { return false }, function(e){ + assert.fail(); + }).caught(predicatesPrimitiveString, returnToken) + .then(assertToken); + + }); + + specify("will assert.fail when a predicate throws", function() { + var a = Promise.defer(); + a.fulfill(3); + return a.promise.then(function(){ + throw new CustomError("error happens"); + }).then(assert.fail, function(e) { return e.f.g; }, function(e){ + assert.fail(); + }).caught(TypeError, returnToken) + .then(assertToken); + }); +}); + +describe("object property predicates", function() { + specify("matches 1 property loosely", function() { + var e = new Error(); + e.code = "3"; + return Promise.resolve() + .then(function() { + throw e; + }) + .caught({code: 3}, function(err) { + assert.equal(e, err); + }); + }); + + specify("matches 2 properties loosely", function() { + var e = new Error(); + e.code = "3"; + e.code2 = "3"; + return Promise.resolve() + .then(function() { + throw e; + }) + .caught({code: 3, code2: 3}, function(err) { + assert.equal(e, err); + }); + }); + + specify("doesn't match inequal properties", function() { + var e = new Error(); + e.code = "3"; + e.code2 = "4"; + return Promise.resolve() + .then(function() { + throw e; + }) + .caught({code: 3, code2: 3}, function(err) { + assert.fail(); + }) + .caught(function(v) {return v === e}, function() {}); + }); + + specify("doesn't match primitives even if the property matches", function() { + var e = "string"; + var length = e.length; + return Promise.resolve() + .then(function() { + throw e; + }) + .caught({length: length}, function(err) { + assert.fail(); + }) + .caught(function(v) {return typeof v === "string"}, function(err) { + assert.equal(e, err); + }); + }); +}); + diff --git a/test/mocha/collections_thenables.js b/test/mocha/collections_thenables.js new file mode 100644 index 0000000..7a79747 --- /dev/null +++ b/test/mocha/collections_thenables.js @@ -0,0 +1,355 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +function Thenable(value, defer, reject) { + this.value = value; + this.defer = !!defer; + this.reject = !!reject; +} + +Thenable.prototype.then = function Then$then(onFulfilled, onRejected) { + var fn = this.reject ? onRejected : onFulfilled; + var value = this.value; + + if (this.defer) { + setTimeout(function(){ + fn(value); + }, 1) + } + else { + fn(value); + } + +}; + +function testFulfillSync(name, cb, a1, a2, a3) { + var thenables = [new Thenable(1), new Thenable(2), new Thenable(3)]; + + specify("Promise." + name + " thenables that fulfill synchronously", function(){ + return cb(Promise[name](thenables, a1, a2, a3)); + }); + +} + +function testFulfillAsync(name, cb, a1, a2, a3) { + var thenables = [new Thenable(1, true), new Thenable(2, true), new Thenable(3, true)]; + + specify("Promise." + name + " thenables that fulfill asynchronously", function(){ + return cb(Promise[name](thenables, a1, a2, a3)); + }); +} + +function testRejectSync(name, cb, a1, a2, a3) { + var thenables = [new Thenable(1, false, true), new Thenable(2, false, true), new Thenable(3, false, true)]; + + specify("Promise." + name + " thenables that reject synchronously", function(){ + return cb(Promise[name](thenables, a1, a2, a3)); + }); + +} + +function testRejectAsync(name, cb, a1, a2, a3) { + var thenables = [new Thenable(1, true, true), new Thenable(2, true, true), new Thenable(3, true, true)]; + + specify("Promise." + name + " thenables that reject asynchronously", function(){ + return cb(Promise[name](thenables, a1, a2, a3)); + }); +} + + +describe("Using collection methods with thenables", function() { + var name = "race"; + testFulfillSync(name, function(promise) { + return promise.then(function(v){ + assert(v === 1); + }); + }); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert(v === 1); + }); + }); + testRejectSync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v === 1); + }); + }); + testRejectAsync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v === 1); + }); + }); +}); + +describe("Using collection methods with thenables", function() { + var name = "all"; + testFulfillSync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, [1,2,3]); + }); + }); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, [1,2,3]); + }); + }); + testRejectSync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v === 1); + }); + }); + testRejectAsync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v === 1); + }); + }); +}); + +describe("Using collection methods with thenables", function() { + var name = "settle"; + testFulfillSync(name, function(promise) { + return promise.then(function(v) { + assert(v[0].value() === 1) + assert(v[1].value() === 2) + assert(v[2].value() === 3) + }); + }); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert(v[0].value() === 1) + assert(v[1].value() === 2) + assert(v[2].value() === 3) + }); + }); + testRejectSync(name, function(promise) { + return promise.then(function(v){ + assert(v[0].error() === 1) + assert(v[1].error() === 2) + assert(v[2].error() === 3) + }); + }); + testRejectAsync(name, function(promise) { + return promise.then(function(v){ + assert(v[0].error() === 1) + assert(v[1].error() === 2) + assert(v[2].error() === 3) + }); + }); +}); + +describe("Using collection methods with thenables", function() { + var name = "any"; + testFulfillSync(name, function(promise) { + return promise.then(function(v){ + assert(v === 1); + }); + }); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert(v === 1); + }); + }); + testRejectSync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v[0] === 1); + assert(v[1] === 2); + assert(v[2] === 3); + }); + }); + testRejectAsync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v[0] === 1); + assert(v[1] === 2); + assert(v[2] === 3); + }); + }); +}); + +describe("Using collection methods with thenables", function() { + var name = "some"; + testFulfillSync(name, function(promise) { + return promise.then(function(v){ + assert(v[0] === 1); + }); + }, 1); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert(v[0] === 1); + }); + }, 1); + testRejectSync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v[0] === 1); + assert(v[1] === 2); + assert(v[2] === 3); + }); + }, 1); + testRejectAsync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v[0] === 1); + assert(v[1] === 2); + assert(v[2] === 3); + }); + }, 1); +}); + +describe("Using collection methods with thenables", function() { + var name = "join"; + testFulfillSync(name, function(promise) { + return promise.then(function(v){ + assert(v[0][0].value === 1); + assert(v[0][1].value === 2); + assert(v[0][2].value === 3); + assert(v[1] === 1); + assert(v[2] === 2); + assert(v[3] === 3); + }); + }, 1, 2, 3); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert(v[0][0].value === 1); + assert(v[0][1].value === 2); + assert(v[0][2].value === 3); + assert(v[1] === 1); + assert(v[2] === 2); + assert(v[3] === 3); + }); + }, 1, 2, 3); + testRejectSync(name, function(promise) { + return promise.then(function(v){ + assert(v[0][0].value === 1); + assert(v[0][1].value === 2); + assert(v[0][2].value === 3); + assert(v[1] === 1); + assert(v[2] === 2); + assert(v[3] === 3); + }); + }, 1, 2, 3); + testRejectAsync(name, function(promise) { + return promise.then(function(v){ + assert(v[0][0].value === 1); + assert(v[0][1].value === 2); + assert(v[0][2].value === 3); + assert(v[1] === 1); + assert(v[2] === 2); + assert(v[3] === 3); + }); + }, 1, 2, 3); +}); + +function mapper(v) { + return { + then: function(f) { + f(v*2); + } + }; +} +function reducer(a, b) { + return a + b; +} +function filterer(v) { + return { + then: function(f) { + f(v > 0); + } + }; +} + +describe("Using collection methods with thenables", function() { + var name = "map"; + testFulfillSync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, [2,4,6]); + }); + }, mapper); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, [2,4,6]); + }); + }, mapper); + testRejectSync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }, mapper); + testRejectAsync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }, mapper); +}); + +describe("Using collection methods with thenables", function() { + var name = "reduce"; + testFulfillSync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, 6); + }); + }, reducer); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, 6); + }); + }, reducer); + testRejectSync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }, reducer); + testRejectAsync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }, reducer); +}); + +describe("Using collection methods with thenables", function() { + var name = "filter"; + testFulfillSync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, [1,2,3]); + }); + }, filterer); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, [1,2,3]); + }); + }, filterer); + testRejectSync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }, filterer); + testRejectAsync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }, filterer); +}); + +describe("Using collection methods with thenables", function() { + var name = "props"; + testFulfillSync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, {0: 1, 1: 2, 2: 3}); + }); + }, filterer); + testFulfillAsync(name, function(promise) { + return promise.then(function(v){ + assert.deepEqual(v, {0: 1, 1: 2, 2: 3}); + }); + }, filterer); + testRejectSync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }, filterer); + testRejectAsync(name, function(promise) { + return promise.then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }, filterer); +}); diff --git a/test/mocha/constructor.js b/test/mocha/constructor.js new file mode 100644 index 0000000..c9153fe --- /dev/null +++ b/test/mocha/constructor.js @@ -0,0 +1,201 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +function fulfills(value, test) { + specify("immediately-fulfilled", function() { + return test(new Promise(function(resolve){ + resolve(value); + })); + }); + + specify("eventually-fulfilled", function() { + return test(new Promise(function(resolve){ + setTimeout(function(){ + resolve(value); + }, 1); + })); + }); +}; + +function rejects(reason, test) { + specify("immediately-rejected", function() { + return test(new Promise(function(resolve, reject){ + reject(reason); + })); + }); + + specify("eventually-rejected", function() { + return test(new Promise(function(resolve, reject){ + setTimeout(function(){ + reject(reason); + }, 1); + })); + }); +}; + +function testFulfilled(value, test) { + describe("immediate value", function(){ + fulfills(value, test); + }); + + describe("already fulfilled promise for value", function(){ + fulfills(Promise.resolve(value), test); + }); + + describe("immediately fulfilled promise for value", function(){ + var a = Promise.defer(); + fulfills(a.promise, test); + a.resolve(value); + }); + + describe("eventually fulfilled promise for value", function(){ + var a = Promise.defer(); + fulfills(a.promise, test); + setTimeout(function(){ + a.resolve(value); + }, 1) + + }); + + describe("synchronous thenable for value", function () { + fulfills({ + then: function (f) { + f(value); + } + }, test); + }); + + describe("asynchronous thenable for value", function () { + fulfills({ + then: function (f) { + setTimeout(function () { + f(value); + }, 1); + } + }, test); + }); +} + +function testRejected(reason, test) { + describe("immediate reason", function(){ + rejects(reason, test); + }); +} + + +describe("Promise constructor", function() { + it("should throw type error when called as function", function() { + try { + Promise(function(){}); + } + catch (e) { + return; + } + assert.fail(); + }); + + it("should throw type error when passed non-function", function() { + try { + new Promise({}); + } + catch (e) { + return; + } + assert.fail(); + }); + + + var defaultThis = (function(){ + return this; + })(); + + it("calls the resolver as a function", function(){ + new Promise(function() { + assert(this === defaultThis); + }); + }); + + it("passes arguments even if parameters are not defined", function(){ + new Promise(function() { + assert(arguments.length === 2 || arguments.length === 3); + }); + }); + + + it("should reject with any thrown error", function() { + var e = new Error(); + return new Promise(function(){ + throw e; + }).then(assert.fail, function(err) { + assert(err === e) + }); + }); + + it("should call the resolver function synchronously", function() { + var e = new Error(); + var a = 0; + new Promise(function(){ + a = 1; + }); + assert(a === 1); + }); + + + describe("resolves the promise with the given object value", function() { + var value = {}; + testFulfilled(value, function(promise) { + return promise.then(function(v){ + assert(v === value); + }); + }); + }); + + describe("resolves the promise with the given primitive value", function() { + var value = 3; + testFulfilled(value, function(promise) { + return promise.then(function(v){ + assert(v === value); + }); + }); + }); + + describe("resolves the promise with the given undefined value", function() { + var value = void 0; + testFulfilled(value, function(promise) { + return promise.then(function(v){ + assert(v === value); + }); + }); + }); + + describe("rejects the promise with the given object reason", function() { + var reason = {}; + testRejected(reason, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v === reason); + }); + }); + }); + + describe("rejects the promise with the given primitive reason", function() { + var reason = 3; + testRejected(reason, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v === reason); + }); + }); + }); + + describe("rejects the promise with the given undefined reason", function() { + var reason = void 0; + testRejected(reason, function(promise) { + return promise.then(assert.fail, function(v){ + assert(v === reason); + }); + }); + }); + +}); diff --git a/test/mocha/cycles.js b/test/mocha/cycles.js new file mode 100644 index 0000000..ad26407 --- /dev/null +++ b/test/mocha/cycles.js @@ -0,0 +1,68 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +var helpers = require("./helpers/testThreeCases.js"); +var TypeError = Promise.TypeError; + + +describe("Cyclical promises should throw TypeError when", function(){ + describe("returning from fulfill", function() { + helpers.testFulfilled(3, function(promise) { + var self = promise.then(function() { + return self; + }); + + return self.then(assert.fail).caught(TypeError, testUtils.noop); + }); + }); + + describe("returning from reject", function() { + helpers.testRejected(3, function(promise) { + var self = promise.then(assert.fail, function() { + return self; + }); + + return self.then(assert.fail).caught(TypeError, testUtils.noop); + }); + }); + + describe("fulfill with itself when using a ", function() { + specify("deferred", function() { + var d = Promise.defer(); + d.fulfill(d.promise); + return d.promise.then(assert.fail).caught(TypeError, testUtils.noop); + }); + + specify("constructor", function() { + var resolve; + var p = new Promise(function(r) { + resolve = r; + }); + resolve(p); + return p.then(assert.fail).caught(TypeError, testUtils.noop); + }); + }); + + describe("reject with itself when using a ", function() { + specify("deferred", function() { + var d = Promise.defer(); + d.reject(d.promise); + return d.promise.then(assert.fail).caught(function(v) { + assert.equal(d.promise, v); + }); + }); + + specify("constructor", function() { + var reject; + var p = new Promise(function(f, r) { + reject = r; + }); + reject(p); + return p.then(assert.fail).caught(function(v) { + assert.equal(p, v); + }); + }); + }); +}); diff --git a/test/mocha/direct_resolving.js b/test/mocha/direct_resolving.js new file mode 100644 index 0000000..11c752f --- /dev/null +++ b/test/mocha/direct_resolving.js @@ -0,0 +1,257 @@ +"use strict"; + +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +var helpers = require("./helpers/testThreeCases.js"); +var TypeError = Promise.TypeError; + +function passthru(fn) { + return function() { + fn(); + }; +} + +function wrap(fn, val) { + var args = [].slice.call(arguments, 1); + return function() { + return fn.apply(this, args); + } +} + +function returnValue(value) { + helpers.testFulfilled(void 0, function(promise) { + return promise.thenReturn(value).then(function(v){ + assert(v === value); + }); + }); +} + +function throwValue(value) { + helpers.testFulfilled(void 0, function(promise) { + return promise.thenThrow(value).then(assert.fail, function(v) { + assert(v === value); + }); + }); +} + +function returnThenable(thenable, expected) { + helpers.testFulfilled(void 0, function(promise) { + return promise.thenReturn(thenable).then(function(v){ + assert(v === expected); + }); + }); +} + +function returnThenableReject(thenable, expected) { + helpers.testFulfilled(void 0, function(promise) { + return promise.thenReturn(thenable).then(assert.fail, function(v){ + assert(v === expected); + }); + }); +} + +describe("thenReturn", function () { + + describe("primitives", function() { + describe("null", wrap(returnValue, null)); + describe("undefined", wrap(returnValue, void 0)); + describe("string", wrap(returnValue, "asd")); + describe("number", wrap(returnValue, 3)); + describe("boolean", wrap(returnValue, true)); + }); + + describe("objects", function() { + describe("plain", wrap(returnValue, {})); + describe("function", wrap(returnValue, function(){})); + describe("built-in function", wrap(returnValue, Array)); + describe("built-in object", wrap(returnValue, Math)); + }); + + describe("thenables", function() { + describe("which fulfill", function() { + describe("immediately", wrap(returnThenable, { + then: function(f) { + f(10); + } + }, 10)); + describe("eventually", wrap(returnThenable, { + then: function(f) { + setTimeout(function() { + f(10); + }, 1); + } + }, 10)); + }); + describe("which reject", function(){ + describe("immediately", wrap(returnThenableReject, { + then: function(f, r) { + r(10); + } + }, 10)); + describe("eventually", wrap(returnThenableReject, { + then: function(f, r) { + setTimeout(function() { + r(10); + }, 1); + } + }, 10)); + }); + }); + + describe("promises", function() { + describe("which fulfill", function() { + var d1 = Promise.defer(); + var d2 = Promise.defer(); + describe("already", wrap(returnThenable, Promise.resolve(10), 10)); + describe("immediately", wrap(returnThenable, d1.promise, 10)); + describe("eventually", wrap(returnThenable, d2.promise, 10)); + d1.fulfill(10); + setTimeout(function(){ + d2.fulfill(10); + }, 1); + }); + describe("which reject", function() { + var d1 = Promise.defer(); + var d2 = Promise.defer(); + var alreadyRejected = Promise.reject(10); + alreadyRejected.then(assert.fail, function(){}); + describe("already", wrap(returnThenableReject, alreadyRejected, 10)); + describe("immediately", wrap(returnThenableReject, d1.promise, 10)); + describe("eventually", wrap(returnThenableReject, d2.promise, 10)); + d1.reject(10); + setTimeout(function(){ + d2.reject(10); + }, 1); + + d1.promise.caught(function(){}); + d2.promise.caught(function(){}); + }); + + }); + + describe("doesn't swallow errors", function() { + var e = {}; + helpers.testRejected(e, function(promise){ + return promise.thenReturn(3).then(assert.fail, function(err) { + assert(err = e); + }); + }); + }); +}); + +describe("thenThrow", function () { + + describe("primitives", function() { + describe("null", wrap(throwValue, null)); + describe("undefined", wrap(throwValue, void 0)); + describe("string", wrap(throwValue, "asd")); + describe("number", wrap(throwValue, 3)); + describe("boolean", wrap(throwValue, true)); + }); + + describe("objects", function() { + describe("plain", wrap(throwValue, {})); + describe("function", wrap(throwValue, function(){})); + describe("built-in function", wrap(throwValue, Array)); + describe("built-in object", wrap(throwValue, Math)); + }); + + describe("doesn't swallow errors", function() { + var e = {}; + helpers.testRejected(e, function(promise){ + return promise.thenThrow(3).then(assert.fail, function(err) { + assert(err = e); + }); + }); + }); +}); + +describe("catchReturn", function () { + + specify("catches and returns", function() { + return Promise.reject(3).catchReturn(1).then(function(val) { + assert.strictEqual(1, val); + }); + }); + + specify("doesn't catch succesful promise", function() { + return Promise.resolve(3).catchReturn(1).then(function(val) { + assert.strictEqual(3, val); + }); + }); + + specify("supports 1 error type", function() { + var e = new Error(); + e.prop = 3; + var predicate = function(e) {return e.prop === 3}; + return Promise.reject(e) + .catchReturn(TypeError, 1) + .catchReturn(predicate, 2) + .then(function(val) { + assert.strictEqual(2, val); + }); + }); +}); + +describe("catchThrow", function () { + + specify("catches and throws", function() { + return Promise.reject(3).catchThrow(1).then(assert.fail, function(val) { + assert.strictEqual(1, val); + }); + }); + + specify("doesn't catch succesful promise", function() { + return Promise.resolve(3).catchThrow(1).then(function(val) { + assert.strictEqual(3, val); + }); + }); + + specify("supports 1 error type", function() { + var e = new Error(); + e.prop = 3; + var predicate = function(e) {return e.prop === 3}; + return Promise.reject(e) + .catchThrow(TypeError, 1) + .catchThrow(predicate, 2) + .then(assert.fail, function(val) { + assert.strictEqual(2, val); + }); + }); +}); + + +describe("gh-627", function() { + it("can return undefined", function() { + return Promise.bind(42) + .thenReturn(undefined) + .then(function (value) { + assert.strictEqual(value, undefined); + }); + }); + it("can throw undefined", function() { + return Promise.bind(42) + .thenThrow(undefined) + .then(assert.fail, function (reason) { + assert.strictEqual(reason, undefined); + }); + }); + + it("can catch return undefined", function() { + return Promise.bind(42).thenThrow(new Error()) + .catchReturn() + .then(function (value) { + assert.strictEqual(value, undefined); + }); + }); + it("can catch throw undefined", function() { + return Promise.bind(42).thenThrow(new Error()) + .catchThrow() + .then(assert.fail, function (reason) { + assert.strictEqual(reason, undefined); + }); + }); +}); diff --git a/test/mocha/domain.js b/test/mocha/domain.js new file mode 100644 index 0000000..f911ed2 --- /dev/null +++ b/test/mocha/domain.js @@ -0,0 +1,503 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +if (testUtils.isRecentNode) { + describe("domain", function() { + afterEach(function() { + Promise.onPossiblyUnhandledRejection(null); + }); + + specify("gh-148", function() { + var called = false; + var e = new Error("the error"); + Promise.resolve(23).then(function(){called = true}); + return testUtils.awaitDomainException(function(E) { + assert.equal(e, E); + assert(called); + }, function() { + Promise.onPossiblyUnhandledRejection(function(error) { + throw error; + }); + var P = new Promise(function(_, reject){reject(e);}); + }); + }); + + specify("gh-521-promisified", function() { + return new Promise(function(resolve, reject) { + var domain = require('domain').create(); + var data = {}; + + function callsBack(cb) { + setTimeout(function() { + cb(null, 1); + }, 1); + } + + var promisified = Promise.promisify(callsBack); + domain.on('error', reject); + domain.run(function() { + process.domain.data = data; + resolve(promisified().then(function() { + assert.strictEqual(process.domain.data, data); + assert.strictEqual(process.domain, domain); + })); + }); + }); + }); + + specify("gh-521-constructed", function() { + return new Promise(function(resolve, reject) { + var domain = require('domain').create(); + var data = {asd: 3}; + domain.on('error', reject); + domain.run(function() { + var promise = new Promise(function(resolve) { + setTimeout(resolve, 1); + }); + + process.domain.data = data; + resolve(promise.then(function() { + assert.strictEqual(process.domain.data, data); + assert.strictEqual(process.domain, domain); + })); + }); + }); + }); + }); + + + describe("domain preservation" , function() { + var Domain = require("domain"); + + function createGroupDone(limit, next) { + + return function done(err) { + if (err) { + return next(err); + } + if (--limit <= 0) { + next(); + } + }; + } + + before(function () { + var current; + while((current = process.domain)) { + current.exit(); + } + }); + + afterEach(function () { + var current; + while((current = process.domain)) { + current.exit(); + } + }); + + it("should preserve empty domain and this function", function(done) { + + var deferred = new Promise.defer(); + var p = deferred.promise; + + p.then(function shouldBeEmpty() { + assert.equal(false, !!process.domain); + }).bind({ + ref: 'foo' + }).then(function shouldKeepThisAndEmptyDomain() { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + }).caught(done); + + deferred.resolve("ok"); + + }); + + it("should preserve empty domain, nodeify", function(done) { + done = createGroupDone(3, done); + + var deferred = new Promise.defer(); + var p = deferred.promise; + + p.then(function shouldBeEmpty() { + assert.equal(false, !!process.domain); + done(); + }).bind({ + ref: 'foo' + }).then(function shouldKeepThisAndEmptyDomain() { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + }).nodeify(function shouldKeepThisAndEmptyDomain() { + try { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + } catch (err) { + done(err); + } + }).caught(done); + + deferred.resolve("ok"); + + }); + + it("should preserve corresponding state of domain", function(done) { + + done = createGroupDone(6, done); + + var deferred = new Promise.defer(); + var p = deferred.promise; + + p.then(function shouldBeEmpty() { + assert.equal(false, !!process.domain); + done(); + }).bind({ + ref: 'foo' + }).then(function shouldKeepThisAndEmptyDomain() { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + }).nodeify(function shouldKeepThisAndEmptyDomain() { + try { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + } catch (err) { done(err); } + }).caught(done); + + var domain = Domain.create(); + domain.run(function () { + p.then(function shouldNoBeEmpty() { + assert.equal(domain, process.domain); + done(); + }).bind({ + ref: 'bar' + }).then(function shouldKeepThisAndDomain() { + assert.equal(domain, process.domain); + assert.equal('bar', this.ref); + done(); + }).caught(done).nodeify(function shouldKeepThisAndDomain() { + try { + assert.equal(domain, process.domain); + assert.equal('bar', this.ref); + done(); + } catch (err) { done(err); } + }); + }); + + deferred.resolve("ok"); + + }); + + it('should preserve corresponding state of domain, complex', function(done) { + + done = createGroupDone(9, done); + + var deferred = new Promise.defer(); + var p = deferred.promise; + p.then(function shouldBeEmpty() { + assert.equal(false, !!process.domain); + done(); + }).bind({ + ref: 'foo' + }).then(function shouldKeepThisAndEmptyDomain() { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + }).caught(done).nodeify(function shouldKeepThisAndEmptyDomain() { + try { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + } + catch (err) { done(err); } + }, done); + + var domain1 = Domain.create(); + domain1.run(function () { + p.then(function shouldNoBeEmpty() { + assert.equal(domain1, process.domain); + done(); + }).bind({ + ref: 'bar' + }).then(function shouldKeepThisAndDomain() { + assert.equal(domain1, process.domain); + assert.equal('bar', this.ref); + done(); + }).caught(done).nodeify(function shouldKeepThisAndDomain() { + try { + assert.equal(domain1, process.domain); + assert.equal('bar', this.ref); + done(); + } + catch (err) { done(err); } + }, done); + }); + + var domain2 = Domain.create(); + domain2.run(function () { + p.then(function shouldNoBeEmpty() { + assert.equal(domain2, process.domain); + done(); + }).bind({ + ref: 'qaz' + }).then(function shouldKeepThisAndDomain() { + assert.equal(domain2, process.domain); + assert.equal('qaz', this.ref); + done(); + }).caught(done).nodeify(function shouldKeepThisAndDomain() { + try { + assert.equal(domain2, process.domain); + assert.equal('qaz', this.ref); + done(); + } + catch (err) { done(err); } + }); + }); + + deferred.resolve("ok"); + + }); + + it('should preserve corresponding state of domain in reject', function(done) { + + done = createGroupDone(4, done); + + var deferred = new Promise.defer(); + var p = deferred.promise; + + p.bind({ + ref: 'foo' + }).caught(function shouldKeepThisAndEmptyDomain() { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + }).caught(done).nodeify(function shouldKeepThisAndEmptyDomain() { + try { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + } + catch (err) { done(err); } + }); + + var domain = Domain.create(); + domain.run(function () { + p.bind({ + ref: 'bar' + }).caught(function shouldNoBeEmpty() { + assert.equal(true, !!process.domain); + assert.equal('bar', this.ref); + done(); + }).caught(done).nodeify(function shouldKeepThisAndDomain(err) { + try { + assert.equal(true, !!process.domain); + assert.equal('bar', this.ref); + done(); + } + catch (err) { done(err); } + }).caught(done); + }); + + deferred.reject('bad'); + + }); + + it('should preserve corresponding state of domain in reject, complex', function(done) { + + done = createGroupDone(6, done); + + var deferred = new Promise.defer(); + var p = deferred.promise; + p.bind({ + ref: 'foo' + }).caught(function shouldBeEmpty() { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + }).caught(done).nodeify(function shouldKeepThisAndEmptyDomain() { + try { + assert.equal(false, !!process.domain); + assert.equal('foo', this.ref); + done(); + } + catch (err) { done(err); } + }); + + var domain1 = Domain.create(); + domain1.run(function () { + p.bind({ + ref: 'bar' + }).caught(function shouldNoBeEmpty() { + assert.equal(domain1, process.domain); + assert.equal('bar', this.ref); + done(); + }).caught(done).nodeify(function shouldKeepThisAndDomain() { + try { + assert.equal(domain1, process.domain); + assert.equal('bar', this.ref); + done(); + } + catch (err) { done(err); } + }); + }); + + var domain2 = Domain.create(); + domain2.run(function () { + p.bind({ + ref: 'qaz' + }).caught(function shouldNoBeEmpty() { + assert.equal(domain2, process.domain); + assert.equal('qaz', this.ref); + done(); + }).caught(done).nodeify(function shouldKeepThisAndDomain() { + try { + assert.equal(domain2, process.domain); + assert.equal('qaz', this.ref); + done(); + } + catch (err) { done(err); } + }); + }); + + deferred.reject('bad'); + + }); + + it('should preserve domain when using .join', function() { + var domain = Domain.create(); + var d1 = new Promise(function(resolve, reject) { + Domain.create().run(function() { + setTimeout(resolve, 1); + }); + }); + var d2 = new Promise(function(resolve, reject) { + Domain.create().run(function() { + setTimeout(resolve, 1); + }); + }); + + return new Promise(function(resolve, reject) { + domain.on("error", reject); + domain.run(function() { + resolve(Promise.join(d1, d2, function() { + assert.strictEqual(domain, process.domain); + })); + }); + }); + }); + + it('should preserve domain when using .using', function() { + var domain = Domain.create(); + var d1 = new Promise(function(resolve, reject) { + Domain.create().run(function() { + setTimeout(resolve, 1); + }); + }); + var d2 = new Promise(function(resolve, reject) { + Domain.create().run(function() { + setTimeout(resolve, 1); + }); + }); + + return new Promise(function(resolve, reject) { + domain.on("error", reject); + domain.run(function() { + resolve(Promise.using(d1, d2, function() { + assert.strictEqual(domain, process.domain); + })); + }); + }); + }); + + it('should preserve domain when using .map', function() { + var domain = Domain.create(); + var d1 = new Promise(function(resolve, reject) { + Domain.create().run(function() { + setTimeout(resolve, 1); + }); + }); + + return new Promise(function(resolve, reject) { + domain.on("error", reject); + domain.run(function() { + resolve(Promise.map([d1, null, Promise.resolve(1), Promise.delay(1)], function() { + return process.domain; + }).then(function(domains) { + assert.deepEqual([domain, domain, domain, domain], domains); + assert.equal(process.domain, domain); + })); + }); + }); + }); + + it('should preserve domain when using .filter', function() { + var domain = Domain.create(); + var d1 = new Promise(function(resolve, reject) { + Domain.create().run(function() { + setTimeout(resolve, 1); + }); + }); + + return new Promise(function(resolve, reject) { + domain.on("error", reject); + domain.run(function() { + resolve(Promise.filter([d1, null, Promise.resolve(1), Promise.delay(1)], function() { + assert.equal(process.domain, domain); + })); + }); + }); + }); + + it('should preserve domain when using .reduce', function() { + var domain = Domain.create(); + var d1 = new Promise(function(resolve, reject) { + Domain.create().run(function() { + setTimeout(resolve, 1); + }); + }); + + return new Promise(function(resolve, reject) { + domain.on("error", reject); + domain.run(function() { + resolve(Promise.reduce([d1, null, Promise.resolve(1), Promise.delay(1)], function() { + assert.equal(process.domain, domain); + })); + }); + }); + }); + + it('should preserve domain when using .each', function() { + var domain = Domain.create(); + var d1 = new Promise(function(resolve, reject) { + Domain.create().run(function() { + setTimeout(resolve, 1); + }); + }); + + return new Promise(function(resolve, reject) { + domain.on("error", reject); + domain.run(function() { + resolve(Promise.each([d1, null, Promise.resolve(1), Promise.delay(1)], function() { + assert.equal(process.domain, domain); + })); + }); + }); + }); + + it("should not crash with already rejected promise", function() { + return new Promise(function(resolve) { + Domain.create().run(function() { + Promise.resolve(1).timeout(200).then(function() { + resolve(); + }) + }); + }); + }) + }); + +} diff --git a/test/mocha/done.js b/test/mocha/done.js new file mode 100644 index 0000000..5cf484e --- /dev/null +++ b/test/mocha/done.js @@ -0,0 +1,131 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var isNodeJS = testUtils.isNodeJS; + +/*! + * +Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ +describe("done", function () { + var errCount = 0; + var safeError = new Error("safe_error"); + + describe("when the promise is fulfilled", function () { + describe("and the callback does not throw", function () { + it("should call the callback and return nothing", function () { + var called = false; + var promise = Promise.resolve(); + + var returnValue = promise.done(function () { + called = true; + }); + + return promise.lastly(function () { + assert.equal(called,true); + assert.equal(returnValue,undefined); + }); + }); + }); + + if (isNodeJS) { + describe("and the callback throws", function () { + it("should rethrow that error in the next turn and return nothing", function() { + var turn = 0; + process.nextTick(function () { + ++turn; + }); + + var returnValue = Promise.resolve().done( + function () { + throw safeError; + } + ); + + return testUtils.awaitProcessExit(function(e) { + assert.equal(turn,1); + assert.equal(returnValue,undefined); + }); + }); + }); + } + }); + + + describe("when the promise is rejected", function () { + describe("and the errback handles it", function () { + it("should call the errback and return nothing", function () { + var called = false; + + var promise = Promise.reject("unsafe_error"); + + var returnValue = promise.done( + function () { }, + function () { + called = true; + } + ); + + return promise.caught(function(){}).lastly(function () { + assert.equal(called,true); + assert.equal(returnValue,undefined); + }); + }); + }); + + if (isNodeJS) { + describe("and the errback throws", function () { + it("should rethrow that error in the next turn and return nothing", function() { + var turn = 0; + process.nextTick(function () { + ++turn; + }); + + var returnValue = Promise.reject("unsafe_error").done( + null, + function () { + throw safeError; + } + ); + return testUtils.awaitProcessExit(function(e) { + assert.equal(turn,1); + assert.equal(returnValue,undefined); + }); + }); + }); + + + describe("and there is no errback", function () { + it("should throw the original error in the next turn", function() { + var turn = 0; + process.nextTick(function () { + ++turn; + }); + + var returnValue = Promise.reject(safeError).done(); + return testUtils.awaitProcessExit(function(e) { + assert.equal(turn,1); + assert.equal(returnValue,undefined); + }); + }); + }); + } + }); +}); diff --git a/test/mocha/each.js b/test/mocha/each.js new file mode 100644 index 0000000..ef86ec9 --- /dev/null +++ b/test/mocha/each.js @@ -0,0 +1,184 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +function promised(val) { + return new Promise(function(f) { + setTimeout(function() { + f(val); + }, 1); + }); +} + +function thenabled(val, arr) { + return { + then: function(f){ + setTimeout(function() { + if (arr) arr.push(val); + f(val); + }, 1); + } + }; +} + +describe("Promise.each", function() { + + it("should return the array's values mapped", function() { + var a = [promised(1), promised(2), promised(3)]; + var b = []; + return Promise.resolve(a).mapSeries(function(val) { + b.push(3-val); + return val + 2; + }).then(function(ret) { + assert.deepEqual(ret, [3,4,5]); + assert.deepEqual(b, [2, 1, 0]); + }); + }); + + + it("takes value, index and length", function() { + var a = [promised(1), promised(2), promised(3)]; + var b = []; + return Promise.resolve(a).each(function(value, index, length) { + b.push(value, index, length); + }).then(function(ret) { + assert.deepEqual(b, [1, 0, 3, 2, 1, 3, 3, 2, 3]); + }); + }); + + it("waits for returned promise before proceeding next", function() { + var a = [promised(1), promised(2), promised(3)]; + var b = []; + return Promise.resolve(a).each(function(value) { + b.push(value); + return Promise.delay(1).then(function(){ + b.push(value*2); + }); + }).then(function(ret) { + assert.deepEqual(b, [1,2,2,4,3,6]); + }); + }); + + it("waits for returned thenable before proceeding next", function() { + var b = [1, 2, 3]; + var a = [thenabled(1), thenabled(2), thenabled(3)]; + return Promise.resolve(a).each(function(val) { + b.push(val * 50); + return thenabled(val * 500, b); + }).then(function(ret) { + assert.deepEqual(b, [1, 2, 3, 50, 500, 100, 1000, 150, 1500]); + }); + }); + + it("doesnt iterate with an empty array", function() { + return Promise.each([], function(val) { + throw new Error(); + }).then(function(ret) { + assert.deepEqual(ret, []); + }); + }); + + it("iterates with an array of single item", function() { + var b = []; + return Promise.each([promised(1)], function(val) { + b.push(val); + return thenabled(val*2, b); + }).then(function(ret) { + assert.deepEqual(b, [1,2]); + }); + }); +}); + +describe("Promise.prototype.each", function() { + + it("should return the array's values", function() { + var a = [promised(1), promised(2), promised(3)]; + var b = []; + return Promise.resolve(a).each(function(val) { + b.push(3-val); + return val; + }).then(function(ret) { + assert.deepEqual(ret, [1,2,3]); + assert.deepEqual(b, [2, 1, 0]); + }); + }); + + + it("takes value, index and length", function() { + var a = [promised(1), promised(2), promised(3)]; + var b = []; + return Promise.resolve(a).each(function(value, index, length) { + b.push(value, index, length); + }).then(function(ret) { + assert.deepEqual(b, [1, 0, 3, 2, 1, 3, 3, 2, 3]); + }); + }); + + it("waits for returned promise before proceeding next", function() { + var a = [promised(1), promised(2), promised(3)]; + var b = []; + return Promise.resolve(a).each(function(value) { + b.push(value); + return Promise.delay(1).then(function(){ + b.push(value*2); + }); + }).then(function(ret) { + assert.deepEqual(b, [1,2,2,4,3,6]); + }); + }); + + it("waits for returned thenable before proceeding next", function() { + var b = [1, 2, 3]; + var a = [thenabled(1), thenabled(2), thenabled(3)]; + return Promise.resolve(a).each(function(val) { + b.push(val * 50); + return thenabled(val * 500, b); + }).then(function(ret) { + assert.deepEqual(b, [1, 2, 3, 50, 500, 100, 1000, 150, 1500]); + }); + }); + + it("doesnt iterate with an empty array", function() { + return Promise.resolve([]).each(function(val) { + throw new Error(); + }).then(function(ret) { + assert.deepEqual(ret, []); + }); + }); + + it("iterates with an array of single item", function() { + var b = []; + return Promise.resolve([promised(1)]).each(function(val) { + b.push(val); + return thenabled(val*2, b); + }).then(function(ret) { + assert.deepEqual(b, [1,2]); + }); + }); +}); + +describe("mapSeries and each", function() { + it("is mixed", function() { + return Promise.mapSeries([1, 2, 3], function(value) { + return value * 2; + }).then(function(result) { + assert.deepEqual(result, [2, 4, 6]); + }).then(function() { + return Promise.each([1, 2, 3], function(value) { + return value * 2; + }).then(function(result) { + assert.deepEqual(result, [1, 2, 3]); + }); + }).thenReturn([1, 2, 3]).mapSeries(function(value) { + return value * 2; + }).then(function(result) { + assert.deepEqual(result, [2, 4, 6]); + }).thenReturn([1, 2, 3]).each(function(value) { + return value * 2; + }).then(function(result) { + assert.deepEqual(result, [1, 2, 3]); + }); + }) +}); diff --git a/test/mocha/error.js b/test/mocha/error.js new file mode 100644 index 0000000..bd2e101 --- /dev/null +++ b/test/mocha/error.js @@ -0,0 +1,184 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +describe("Promise.prototype.error", function(){ + describe("catches stuff originating from explicit rejections", function() { + specify("using callback", function() { + var e = new Promise.TypeError("sup"); + function callsback(a, b, c, fn) { + fn(e); + } + callsback = Promise.promisify(callsback); + + return callsback(1, 2, 3).error(function(err) { + assert(err === e); + }); + }); + }); + + describe("does not catch stuff originating from thrown errors", function() { + specify("using constructor", function() { + var e = new Error("sup"); + return new Promise(function(resolve, reject) { + throw e; + }).error(function(err) { + assert.fail(); + }).then(assert.fail, function(err){ + assert(err === e); + }); + }); + specify("using thenable", function() { + var e = new Error("sup"); + var thenable = { + then: function(resolve, reject){ + reject(e); + } + }; + return Promise.cast(thenable).error(function(err) { + console.error(err); + assert.fail(); + }).then(assert.fail, function(err) { + assert(err === e); + }); + }); + specify("using callback", function() { + var e = new Error("sup"); + function callsback(a, b, c, fn) { + throw e; + } + callsback = Promise.promisify(callsback); + + return callsback(1, 2, 3).error(function(err) { + assert.fail(); + }).then(assert.fail, function(err){ + assert(err === e); + }); + }); + }); +}) + +if (testUtils.ecmaScript5) { + describe("Weird errors", function() { + specify("unwritable stack", function() { + var e = new Error(); + var stack = e.stack; + Object.defineProperty(e, "stack", { + configurable: true, + get: function() {return stack;}, + set: function() {throw new Error("cannot set");} + }); + return new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }).caught(function(err) { + assert.equal(e, err); + }); + }); + }); +} + +describe("Error constructors", function() { + describe("OperationalError", function() { + it("should work without new", function() { + var a = Promise.OperationalError("msg"); + assert.strictEqual(a.message, "msg"); + assert(a instanceof Error); + }); + + it("should work with new", function() { + var a = new Promise.OperationalError("msg"); + assert.strictEqual(a.message, "msg"); + assert(a instanceof Error); + }); + + it("should retain custom properties", function() { + var message; + var name; + function f(cb) { + var err = new Error("custom message"); + message = err.message; + name = err.name; + err.code = "ENOENT"; + err.path = "C:\\"; + cb(err); + } + return Promise.promisify(f)().error(function(e) { + assert.strictEqual(e.message, message); + assert.strictEqual(e.name, name); + assert(e instanceof Promise.OperationalError); + assert.strictEqual(e.code, "ENOENT"); + assert.strictEqual(e.path, "C:\\"); + }); + }); + }); + + describe("CancellationError", function() { + it("should work without new", function() { + var a = Promise.CancellationError("msg"); + assert.strictEqual(a.message, "msg"); + assert(a instanceof Error); + }); + + it("should work with new", function() { + var a = new Promise.CancellationError("msg"); + assert.strictEqual(a.message, "msg"); + assert(a instanceof Error); + }); + }); + + describe("TimeoutError", function() { + it("should work without new", function() { + var a = Promise.TimeoutError("msg"); + assert.strictEqual(a.message, "msg"); + assert(a instanceof Error); + }); + + it("should work with new", function() { + var a = new Promise.TimeoutError("msg"); + assert.strictEqual(a.message, "msg"); + assert(a instanceof Error); + }); + }); + + describe("AggregateError", function() { + it("should work without new", function() { + var a = Promise.AggregateError("msg"); + assert.strictEqual(a.message, "msg"); + assert(a instanceof Error); + }); + + it("should work with new", function() { + var a = new Promise.AggregateError("msg"); + assert.strictEqual(a.message, "msg"); + assert(a instanceof Error); + }); + + if (testUtils.isNodeJS) { + it("should stringify without circular errors", function() { + var a = Promise.AggregateError(); + a.push(new Error("1")); + a.push(new Error("2")); + a.push(new Error("3")); + a = a.toString(); + assert(a.indexOf("Error: 1") >= 0); + assert(a.indexOf("Error: 2") >= 0); + assert(a.indexOf("Error: 3") >= 0); + }); + + it("should stringify with circular errors", function() { + var a = Promise.AggregateError(); + a.push(new Error("1")); + a.push(a); + a.push(new Error("3")); + a = a.toString(); + assert(a.indexOf("Error: 1") >= 0); + assert(a.indexOf("[Circular AggregateError]") >= 0); + assert(a.indexOf("Error: 3") >= 0); + }); + } + }); + + +}); diff --git a/test/mocha/filter.js b/test/mocha/filter.js new file mode 100644 index 0000000..724ae11 --- /dev/null +++ b/test/mocha/filter.js @@ -0,0 +1,120 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + + +describe("Promise filter", function() { + + function ThrownError() {} + + + var arr = [1,2,3]; + + function assertArr(arr) { + assert(arr.length === 2); + assert(arr[0] === 1); + assert(arr[1] === 3); + } + + function assertErr(e) { + assert(e instanceof ThrownError); + } + + function assertFail() { + assert.fail(); + } + + describe("should accept eventual booleans", function() { + specify("immediately fulfilled", function() { + return Promise.filter(arr, function(v) { + return new Promise(function(r){ + r(v !== 2); + }); + }).then(assertArr); + }); + + specify("already fulfilled", function() { + return Promise.filter(arr, function(v) { + return Promise.resolve(v !== 2); + }).then(assertArr); + }); + + specify("eventually fulfilled", function() { + return Promise.filter(arr, function(v) { + return new Promise(function(r){ + setTimeout(function(){ + r(v !== 2); + }, 1); + }); + }).then(assertArr); + }); + + specify("immediately rejected", function() { + return Promise.filter(arr, function(v) { + return new Promise(function(v, r){ + r(new ThrownError()); + }); + }).then(assertFail, assertErr); + }); + specify("already rejected", function() { + return Promise.filter(arr, function(v) { + return Promise.reject(new ThrownError()); + }).then(assertFail, assertErr); + }); + specify("eventually rejected", function() { + return Promise.filter(arr, function(v) { + return new Promise(function(v, r){ + setTimeout(function(){ + r(new ThrownError()); + }, 1); + }); + }).then(assertFail, assertErr); + }); + + + specify("immediately fulfilled thenable", function() { + return Promise.filter(arr, function(v) { + return { + then: function(f, r) { + f(v !== 2); + } + }; + }).then(assertArr); + }); + specify("eventually fulfilled thenable", function() { + return Promise.filter(arr, function(v) { + return { + then: function(f, r) { + setTimeout(function(){ + f(v !== 2); + }, 1); + } + }; + }).then(assertArr); + }); + + specify("immediately rejected thenable", function() { + return Promise.filter(arr, function(v) { + return { + then: function(f, r) { + r(new ThrownError()); + } + }; + }).then(assertFail, assertErr); + }); + specify("eventually rejected thenable", function() { + return Promise.filter(arr, function(v) { + return { + then: function(f, r) { + setTimeout(function(){ + r(new ThrownError()); + }, 1); + } + }; + }).then(assertFail, assertErr); + }); + + }); +}); diff --git a/test/mocha/finally.js b/test/mocha/finally.js new file mode 100644 index 0000000..1a34082 --- /dev/null +++ b/test/mocha/finally.js @@ -0,0 +1,274 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +/*! + * +Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ + +describe("finally", function () { + + var exception1 = new Error("boo!"); + var exception2 = new Promise.TypeError("evil!"); + + describe("when nothing is passed", function() { + it("should do nothing", function() { + return Promise.resolve("foo") + .lastly() + .lastly() + .lastly() + .lastly() + .then(function(val){ + assert(val === "foo"); + }) + }); + }); + + describe("when the promise is fulfilled", function () { + + it("should call the callback", function() { + var called = false; + + return Promise.resolve("foo") + .lastly(function () { + called = true; + }) + .then(function () { + assert.equal(called,true); + }); + }); + + it("should fulfill with the original value", function() { + return Promise.resolve("foo") + .lastly(function () { + return "bar"; + }) + .then(function (result) { + assert.equal(result,"foo"); + }); + }); + + describe("when the callback returns a promise", function () { + + describe("that is fulfilled", function () { + it("should fulfill with the original reason after that promise resolves", function() { + var promise = Promise.delay(1); + + return Promise.resolve("foo") + .lastly(function () { + return promise; + }) + .then(function (result) { + assert.equal(promise.isPending(),false); + assert.equal(result,"foo"); + }); + }); + }); + + describe("that is rejected", function () { + it("should reject with this new rejection reason", function() { + return Promise.resolve("foo") + .lastly(function () { + return Promise.reject(exception1); + }) + .then(function () { + assert.equal(false,true); + }, + function (exception) { + assert.equal(exception,exception1); + }); + }); + }); + + }); + + describe("when the callback throws an exception", function () { + it("should reject with this new exception", function() { + return Promise.resolve("foo") + .lastly(function () { + throw exception1; + }) + .then(function () { + assert.equal(false,true); + }, + function (exception) { + assert.equal(exception,exception1); + }); + }); + }); + + }); + + describe("when the promise is rejected", function () { + + it("should call the callback", function() { + var called = false; + + return Promise.reject(exception1) + .lastly(function () { + called = true; + }) + .then(function () { + assert.fail(); + }, function () { + assert.equal(called,true); + }); + }); + + it("should reject with the original reason", function() { + return Promise.reject(exception1) + .lastly(function () { + return "bar"; + }) + .then(function () { + assert.equal(false,true); + }, + function (exception) { + assert.equal(exception,exception1); + }); + }); + + describe("when the callback returns a promise", function () { + + describe("that is fulfilled", function () { + it("should reject with the original reason after that promise resolves", function() { + var promise = Promise.delay(1); + + return Promise.reject(exception1) + .lastly(function () { + return promise; + }) + .then(function () { + assert.equal(false,true); + }, + function (exception) { + assert.equal(exception,exception1); + assert.equal(promise.isPending(),false); + }); + }); + }); + + describe("that is rejected", function () { + it("should reject with the new reason", function() { + return Promise.reject(exception1) + .lastly(function () { + return Promise.reject(exception2); + }) + .then(function () { + assert.equal(false,true); + }, + function (exception) { + assert.equal(exception,exception2); + }); + }); + }); + + }); + + describe("when the callback throws an exception", function () { + it("should reject with this new exception", function() { + return Promise.reject(exception1) + .lastly(function () { + throw exception2; + }) + .then(function () { + assert.equal(false,true); + }, + function (exception) { + assert.equal(exception,exception2); + }); + }); + }); + + }); + + describe("when the callback returns a thenable", function () { + + describe("that will fulfill", function () { + it("should reject with the original reason after that", function() { + var promise = { + then: function(fn) { + setTimeout(function(){ + fn(15); + }, 1); + } + }; + + return Promise.reject(exception1) + .lastly(function () { + return promise; + }) + .then(function () { + assert.equal(false,true); + }, + function (exception) { + assert.equal(exception,exception1); + }); + }); + }); + + describe("that is rejected", function () { + it("should reject with the new reason", function() { + var promise = { + then: function(f, fn) { + setTimeout(function(){ + fn(exception2); + }, 1); + } + }; + + return Promise.reject(exception1) + .lastly(function () { + return promise; + }) + .then(function () { + assert.equal(false,true); + }, + function (exception) { + assert.equal(exception,exception2); + }); + }); + it("should reject with the new primitive reason", function() { + var primitive = 3; + var promise = { + then: function(f, fn) { + setTimeout(function(){ + fn(primitive); + }, 1); + } + }; + + return Promise.reject(exception1) + .lastly(function () { + return promise; + }) + .then(function () { + assert.equal(false,true); + }, + function (exception) { + assert.strictEqual(exception, primitive); + }); + }); + }); + + + + }); +}); diff --git a/test/mocha/following.js b/test/mocha/following.js new file mode 100644 index 0000000..52ee19a --- /dev/null +++ b/test/mocha/following.js @@ -0,0 +1,185 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +describe("Using deferreds", function() { + describe("a promise A that is following a promise B", function() { + specify("Must not react to fulfill/reject/ that don't come from promise B", function() { + var deferred = Promise.defer(); + var promiseA = deferred.promise; + var promiseB = Promise.defer().promise; + var called = 0; + function incrementCalled() { + called++; + } + + promiseA.then( + incrementCalled, + incrementCalled + ); + deferred.fulfill(promiseB); + + deferred.fulfill(1); + deferred.reject(1); + return Promise.delay(1).then(function() { + assert.equal(0, called); + assert.equal(promiseA.isPending(), true); + assert.equal(promiseB.isPending(), true); + }); + }); + + specify("Must not start following another promise C", function() { + var deferred = Promise.defer(); + var promiseA = deferred.promise; + var promiseB = Promise.defer().promise; + var deferredC = Promise.defer(); + var promiseC = deferredC.promise; + var called = 0; + function incrementCalled() { + called++; + } + + + promiseA.then( + incrementCalled, + incrementCalled + ); + deferred.fulfill(promiseB); + deferred.fulfill(promiseC); + + deferredC.fulfill(1); + deferredC.reject(1); + + return promiseC.then(function() { + assert.equal(called, 0); + assert.equal(promiseA.isPending(), true); + assert.equal(promiseB.isPending(), true); + assert.equal(promiseC.isPending(), false); + }); + }); + + specify("Must react to fulfill/reject that come from promise B", function() { + var deferred = Promise.defer(); + var promiseA = deferred.promise; + var deferredFollowee = Promise.defer(); + var promiseB = deferredFollowee.promise; + var called = 0; + function incrementCalled() { + called++; + } + var c = 0; + + var ret = promiseA.then(function(v){ + c++; + assert.equal(c, 1); + assert.equal(called, 0); + }, incrementCalled); + + deferred.fulfill(promiseB); + + + deferredFollowee.fulfill(1); + deferredFollowee.reject(1); + return ret; + }); + }); +}); + +describe("Using static immediate methods", function() { + describe("a promise A that is following a promise B", function() { + specify("Should be instantly fulfilled with Bs fulfillment value if B was fulfilled", function() { + var val = {}; + var B = Promise.resolve(val); + var A = Promise.resolve(B); + assert.equal(A.value(), val); + assert.equal(A.value(), B.value()); + }); + + specify("Should be instantly fulfilled with Bs parent fulfillment value if B was fulfilled with a parent", function() { + var val = {}; + var parent = Promise.resolve(val); + var B = Promise.resolve(parent); + var A = Promise.resolve(B); + assert.equal(A.value(), val); + assert.equal(A.value(), B.value()); + assert.equal(A.value(), parent.value()); + }); + }); + + describe("Rejecting a promise A with promise B", function(){ + specify("Should reject promise A with B as reason ", function() { + var val = {}; + var B = Promise.resolve(val); + var A = Promise.reject(B); + assert.equal(A.reason(), B); + A.then(assert.fail, function(){}); + }); + }); +}); + +describe("Using constructor", function() { + describe("a promise A that is following a promise B", function() { + specify("Must not react to fulfill/reject that don't come from promise B", function() { + var resolveA; + var rejectA; + var promiseA = new Promise(function() { + resolveA = arguments[0]; + rejectA = arguments[1]; + }); + var promiseB = new Promise(function(){}); + var called = 0; + function incrementCalled() { + called++; + } + + promiseA.then( + incrementCalled, + incrementCalled + ); + + resolveA(promiseB); + resolveA(1); + rejectA(1); + return Promise.delay(1).then(function() { + assert.equal(0, called); + assert.equal(promiseA.isPending(), true); + assert.equal(promiseB.isPending(), true); + }); + }); + + specify("Must not start following another promise C", function() { + var resolveA; + var promiseA = new Promise(function(){ + resolveA = arguments[0]; + }); + var promiseB = new Promise(function(){}); + var resolveC, rejectC; + var promiseC = new Promise(function(){ + resolveC = arguments[0]; + rejectC = arguments[1]; + }); + var called = 0; + function incrementCalled() { + called++; + } + + promiseA.then( + incrementCalled, + incrementCalled, + incrementCalled + ); + resolveA(promiseB); + resolveA(promiseC); + resolveC(1); + rejectC(1); + return promiseC.then(function() { + assert.equal(called, 0); + assert.equal(promiseA.isPending(), true); + assert.equal(promiseB.isPending(), true); + assert.equal(promiseC.isPending(), false); + }); + }); + }); +}); diff --git a/test/mocha/generator.js b/test/mocha/generator.js new file mode 100644 index 0000000..eac2d7c --- /dev/null +++ b/test/mocha/generator.js @@ -0,0 +1,754 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var assertLongTrace = require("./helpers/assert_long_trace.js"); +var awaitLateQueue = testUtils.awaitLateQueue; + +function get(arg) { + return { + then: function(ful, rej) { + ful(arg) + } + } +} + +function fail(arg) { + return { + then: function(ful, rej) { + rej(arg) + } + }; +} + +Promise.coroutine.addYieldHandler(function(yieldedValue) { + if (Array.isArray(yieldedValue)) return Promise.all(yieldedValue); +}); + +var error = new Error("asd"); + +describe("yielding", function() { + + specify("non-promise should throw", function() { + return Promise.coroutine(function*(){ + + var a = yield {}; + assert.fail(); + return 4; + + })().then(assert.fail).caught(function(e){ + assert(e instanceof TypeError); + }); + }); + + specify("an array should implicitly Promise.all them", function() { + var a = Promise.defer(); + var ap = a.promise; + var b = Promise.defer(); + var bp = b.promise; + var c = Promise.defer(); + var cp = c.promise; + + + setTimeout(function(){ + a.fulfill(1); + b.fulfill(2); + c.fulfill(3); + }, 1); + return Promise.coroutine(function*(){ + return yield [ap, bp, cp]; + })().then(function(r) { + //.spread will also implicitly use .all() so that cannot be used here + var a = r[0]; var b = r[1]; var c = r[2]; + assert(a === 1); + assert(b === 2); + assert(c === 3); + }); + }); + + specify("non-promise should throw but be catchable", function() { + + return Promise.coroutine(function*(){ + try { + var a = yield {}; + assert.fail(); + } + catch (e){ + assert(e instanceof TypeError); + return 4; + } + + })().then(function(val){ + assert.equal(val, 4); + }); + }); + + specify("yielding a function should not call the function", function() { + let functionWasCalled = false; + return Promise.coroutine(function*(){ + try { + yield (function() {functionWasCalled = true;}); + } + catch(e){ + assert(e instanceof TypeError); + assert.equal(functionWasCalled, false); + return 4; + } + })().then(function(val){ + assert.equal(val, 4); + }); + }); +}); + +describe("thenables", function(){ + + specify("when they fulfill, the yielded value should be that fulfilled value", function(){ + + return Promise.coroutine(function*(){ + + var a = yield get(3); + assert.equal(a, 3); + return 4; + + })().then(function(arg){ + assert.equal(arg, 4); + }); + + }); + + + specify("when they reject, and the generator doesn't have try.caught, it should immediately reject the promise", function(){ + + return Promise.coroutine(function*(){ + var a = yield fail(error); + assert.fail(); + + })().then(assert.fail).then(assert.fail, function(e){ + assert.equal(e, error); + }); + + }); + + specify("when they reject, and the generator has try.caught, it should continue working normally", function(){ + + return Promise.coroutine(function*(){ + try { + var a = yield fail(error); + } + catch (e) { + return e; + } + assert.fail(); + + })().then(function(v){ + assert.equal(v, error); + }); + + }); + + specify("when they fulfill but then throw, it should become rejection", function(){ + + return Promise.coroutine(function*(){ + var a = yield get(3); + assert.equal(a, 3); + throw error; + })().then(assert.fail, function(e){ + assert.equal(e, error); + }); + }); +}); + +describe("yield loop", function(){ + + specify("should work", function(){ + return Promise.coroutine(function* () { + var a = [1,2,3,4,5]; + + for (var i = 0, len = a.length; i < len; ++i) { + a[i] = yield get(a[i] * 2); + } + + return a; + })().then(function(arr){ + assert.deepEqual([2,4,6,8,10], arr); + }); + }); + + specify("inside yield should work", function(){ + return Promise.coroutine(function *() { + var a = [1,2,3,4,5]; + + return yield Promise.all(a.map(function(v){ + return Promise.coroutine(function *() { + return yield get(v*2); + })(); + })); + })().then(function(arr){ + assert.deepEqual([2,4,6,8,10], arr); + }); + }); + + specify("with simple map should work", function(){ + return Promise.coroutine(function *() { + var a = [1,2,3,4,5]; + + return yield Promise.map(a, function(v){ + return Promise.cast(get(v*2)); + }); + })().then(function(arr){ + assert.deepEqual([2,4,6,8,10], arr); + }); + }); + +}); + + +describe("Promise.coroutine", function() { + describe("thenables", function() { + specify("when they fulfill, the yielded value should be that fulfilled value", function(){ + + return Promise.coroutine(function*(){ + + var a = yield get(3); + assert.equal(a, 3); + return 4; + + })().then(function(arg){ + assert.equal(arg, 4); + }); + + }); + + + specify("when they reject, and the generator doesn't have try.caught, it should immediately reject the promise", function(){ + + return Promise.coroutine(function*(){ + var a = yield fail(error); + assert.fail(); + + })().then(assert.fail).then(assert.fail, function(e){ + assert.equal(e, error); + }); + + }); + + specify("when they reject, and the generator has try.caught, it should continue working normally", function(){ + + return Promise.coroutine(function*(){ + try { + var a = yield fail(error); + } + catch (e) { + return e; + } + assert.fail(); + + })().then(function(v){ + assert.equal(v, error); + }); + + }); + + specify("when they fulfill but then throw, it should become rejection", function(){ + + return Promise.coroutine(function*(){ + var a = yield get(3); + assert.equal(a, 3); + throw error; + })().then(assert.fail).then(assert.fail, function(e){ + assert.equal(e, error); + }); + }); + + specify("when they are already fulfilled, the yielded value should be returned asynchronously", function(){ + var value; + + var promise = Promise.coroutine(function*(){ + yield Promise.resolve(); + value = 2; + })(); + + value = 1; + + return promise.then(function(){ + assert.equal(value, 2); + }); + }); + + specify("when they are already rejected, the yielded reason should be thrown asynchronously", function(){ + var value; + + var promise = Promise.coroutine(function*(){ + try { + yield Promise.reject(); + } + catch (e) { + value = 2; + } + })(); + + value = 1; + + return promise.then(function(){ + assert.equal(value, 2); + }); + }); + }); + + describe("yield loop", function(){ + + specify("should work", function(){ + return Promise.coroutine(function* () { + var a = [1,2,3,4,5]; + + for (var i = 0, len = a.length; i < len; ++i) { + a[i] = yield get(a[i] * 2); + } + + return a; + })().then(function(arr){ + assert.deepEqual([2,4,6,8,10], arr); + }); + }); + + specify("inside yield should work", function(){ + return Promise.coroutine(function *() { + var a = [1,2,3,4,5]; + + return yield Promise.all(a.map(function(v){ + return Promise.coroutine(function *() { + return yield get(v*2); + })(); + })); + })().then(function(arr){ + assert.deepEqual([2,4,6,8,10], arr); + }); + }); + + specify("with simple map should work", function(){ + return Promise.coroutine(function *() { + var a = [1,2,3,4,5]; + + return yield Promise.map(a, function(v){ + return Promise.cast(get(v*2)); + }); + })().then(function(arr){ + assert.deepEqual([2,4,6,8,10], arr); + }); + }); + + }); + + describe("when using coroutine as a method", function(){ + + function MyClass() { + this.goblins = 3; + } + + MyClass.prototype.spawnGoblins = Promise.coroutine(function* () { + this.goblins = yield get(this.goblins+1); + }); + + + specify("generator function's receiver should be the instance too", function() { + var a = new MyClass(); + var b = new MyClass(); + + return Promise.join(a.spawnGoblins().then(function(){ + return a.spawnGoblins() + }), b.spawnGoblins()).then(function(){ + assert.equal(a.goblins, 5); + assert.equal(b.goblins, 4); + }); + + }); + }); +}); + +describe("Spawn", function() { + it("should work", function() { + return Promise.spawn(function*() { + return yield Promise.resolve(1); + }).then(function(value) { + assert.strictEqual(value, 1); + }); + }); + it("should return rejected promise when passed non function", function() { + return Promise.spawn({}).then(assert.fail, function(err) { + assert(err instanceof Promise.TypeError); + }); + }); +}); + +describe("custom yield handlers", function() { + specify("should work with timers", function() { + var n = 0; + Promise.coroutine.addYieldHandler(function(v) { + if (typeof v === "number") { + n = 1; + return Promise.resolve(n); + } + }); + + + return Promise.coroutine(function*() { + return yield 50; + })().then(function(value) { + assert.equal(value, 1); + assert.equal(n, 1); + }); + }); + + var _ = (function() { + var promise = null; + Promise.coroutine.addYieldHandler(function(v) { + if (v === void 0 && promise != null) { + return promise; + } + promise = null; + }); + return function() { + var cb; + promise = Promise.fromNode(function(callback) { + cb = callback; + }); + return cb; + }; + })(); + + specify("Should work with callbacks", function(){ + var callbackApiFunction = function(a, b, c, cb) { + setTimeout(function(){ + cb(null, [a, b, c]); + }, 1); + }; + + return Promise.coroutine(function*() { + return yield callbackApiFunction(1, 2, 3, _()); + })().then(function(result) { + assert(result.length === 3); + assert(result[0] === 1); + assert(result[1] === 2); + assert(result[2] === 3); + }); + }); + + specify("should work with thunks", function(){ + Promise.coroutine.addYieldHandler(function(v) { + if (typeof v === "function") { + var cb; + var promise = Promise.fromNode(function(callback) { + cb = callback; + }); + try { v(cb); } catch (e) { cb(e); } + return promise; + } + }); + + var thunk = function(a) { + return function(callback) { + setTimeout(function(){ + callback(null, a*a); + }, 1); + }; + }; + + return Promise.coroutine(function*() { + return yield thunk(4); + })().then(function(result) { + assert(result === 16); + }); + }); + + specify("individual yield handler", function() { + var dummy = {}; + var yieldHandler = function(value) { + if (value === dummy) return Promise.resolve(3); + }; + var coro = Promise.coroutine(function* () { + return yield dummy; + }, {yieldHandler: yieldHandler}); + + return coro().then(function(result) { + assert(result === 3); + }); + }); + + specify("yield handler that throws", function() { + var dummy = {}; + var unreached = false; + var err = new Error(); + var yieldHandler = function(value) { + if (value === dummy) throw err; + }; + + var coro = Promise.coroutine(function* () { + yield dummy; + unreached = true; + }, {yieldHandler: yieldHandler}); + + return coro().then(assert.fail, function(e) { + assert.strictEqual(e, err); + assert(!unreached); + }); + }); + + specify("yield handler is not a function", function() { + try { + Promise.coroutine.addYieldHandler({}); + } catch (e) { + assert(e instanceof Promise.TypeError); + return; + } + assert.fail(); + }); +}); + +if (Promise.hasLongStackTraces()) { + describe("Long stack traces with coroutines as context", function() { + it("1 level", function() { + return Promise.coroutine(function* () { + yield Promise.delay(10); + throw new Error(); + })().then(assert.fail, function(e) { + assertLongTrace(e, 1+1, [2]); + }); + }); + it("4 levels", function() { + var secondLevel = Promise.coroutine(function* () { + yield thirdLevel(); + }); + var thirdLevel = Promise.coroutine(function* () { + yield fourthLevel(); + }); + var fourthLevel = Promise.coroutine(function* () { + throw new Error(); + }); + + return Promise.coroutine(function* () { + yield secondLevel(); + })().then(assert.fail, function(e) { + assertLongTrace(e, 4+1, [2, 2, 2, 2]); + }); + }); + }); +} + +describe("Cancellation with generators", function() { + specify("input immediately cancelled", function() { + var cancelled = 0; + var finalled = 0; + var unreached = 0; + + var p = new Promise(function(_, __, onCancel) {}); + p.cancel(); + + var asyncFunction = Promise.coroutine(function* () { + try { + yield p; + unreached++; + } catch(e) { + if (e === Promise.coroutine.returnSentinel) throw e; + unreached++; + } finally { + yield Promise.resolve(); + finalled++; + } + unreached++; + }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + + asyncFunction() + .then(reject, function(e) { + if(!(e instanceof Promise.CancellationError)) reject(new Error()); + }) + .lastly(function() { + finalled++; + resolve(); + }); + + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(0, cancelled); + assert.equal(0, unreached); + }); + }); + }); + + specify("input eventually cancelled", function() { + var cancelled = 0; + var finalled = 0; + var unreached = 0; + + var p = new Promise(function(_, __, onCancel) {}); + var asyncFunction = Promise.coroutine(function* () { + try { + yield p; + unreached++; + } catch(e) { + if (e === Promise.coroutine.returnSentinel) throw e; + unreached++; + } finally { + yield Promise.resolve(); + finalled++; + } + unreached++; + }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + + asyncFunction() + .then(reject, reject) + .lastly(function() { + finalled++; + resolve(); + }); + + Promise.delay(1).then(function() { + p.cancel(); + }); + + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(2, finalled); + assert.equal(0, cancelled); + assert.equal(0, unreached); + }); + }); + }); + + specify("output immediately cancelled", function() { + var cancelled = 0; + var finalled = 0; + var unreached = 0; + + var p = new Promise(function(_, __, onCancel) { + onCancel(function() { + cancelled++; + }); + }).lastly(function() { + finalled++; + }); + + var asyncFunction = Promise.coroutine(function* () { + try { + yield p; + unreached++; + } catch(e) { + if (e === Promise.coroutine.returnSentinel) throw e; + unreached++; + } finally { + yield Promise.resolve() + finalled++; + } + unreached++; + }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + + var output = asyncFunction() + .then(reject, reject) + .lastly(function() { + finalled++; + resolve(); + }); + + output.cancel(); + + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(3, finalled); + assert.equal(1, cancelled); + assert.equal(0, unreached); + }); + }); + }); + + specify("output eventually cancelled", function() { + var cancelled = 0; + var finalled = 0; + var unreached = 0; + + var p = new Promise(function(_, __, onCancel) { + onCancel(function() { + cancelled++; + }); + }).lastly(function() { + finalled++; + }); + + var asyncFunction = Promise.coroutine(function* () { + try { + yield p; + unreached++; + } catch(e) { + if (e === Promise.coroutine.returnSentinel) throw e; + unreached++; + } finally { + yield Promise.resolve() + finalled++; + } + unreached++; + }); + + var resolve, reject; + var result = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + + var output = asyncFunction() + .then(reject, reject) + .lastly(function() { + finalled++; + resolve(); + }); + + Promise.delay(1).then(function() { + output.cancel(); + }); + + return result.then(function() { + return awaitLateQueue(function() { + assert.equal(3, finalled); + assert.equal(1, cancelled); + assert.equal(0, unreached); + }); + }); + }); + + + specify("finally block runs before finally handler", function(done) { + var finallyBlockCalled = false; + var asyncFn = Promise.coroutine(function* () { + try { + yield Promise.delay(100); + } finally { + yield Promise.delay(100); + finallyBlockCalled = true; + } + }); + var p = asyncFn(); + Promise.resolve().then(function() { + p.cancel(); + }); + p.finally(function() { + assert.ok(finallyBlockCalled, "finally block should have been called before finally handler"); + done(); + }).catch(done); + }); +}); diff --git a/test/mocha/get.js b/test/mocha/get.js new file mode 100644 index 0000000..513f63f --- /dev/null +++ b/test/mocha/get.js @@ -0,0 +1,83 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var join = Promise.join; + +describe("indexed getter", function() { + var p = Promise.resolve([0, 1, 2, 3, 4, 5, 7, 5,10]); + specify("gets positive index", function() { + var first = p.get(0); + var fourth = p.get(3); + var last = p.get(8); + + return join(first, fourth, last, function(a, b, c) { + assert(a === 0); + assert(b === 3); + assert(c === 10); + }); + }); + + specify("gets negative index", function() { + var last = p.get(-1); + var first = p.get(-20); + + return join(last, first, function(a, b) { + assert.equal(a, 10); + assert.equal(b, 0); + }); + }); +}); + +describe("identifier getter", function() { + var p = Promise.resolve(new RegExp("", "")); + specify("gets property", function() { + var ci = p.get("ignoreCase"); + var g = p.get("global"); + var lastIndex = p.get("lastIndex"); + var multiline = p.get("multiline"); + + return join(ci, g, lastIndex, multiline, function(ci, g, lastIndex, multiline) { + assert(ci === false); + assert(g === false); + assert(lastIndex === 0); + assert(multiline === false); + }); + }); + + specify("gets same property", function() { + var o = {o: 1}; + var o2 = {o: 2}; + o = Promise.resolve(o).get("o"); + o2 = Promise.resolve(o2).get("o"); + return join(o, o2, function(one, two) { + assert.strictEqual(1, one); + assert.strictEqual(2, two); + }); + }); +}); + +describe("non identifier getters", function() { + var p = Promise.resolve({"-": "val"}); + specify("get property", function() { + return p.get("-").then(function(val) { + assert(val === "val"); + }); + }); + + specify.skip("overflow cache", function() { + var a = new Array(1024); + var o = {}; + for (var i = 0; i < a.length; ++i) { + a[i] = "get" + i; + o["get" + i] = i*2; + } + var b = Promise.map(a, function(item, index) { + return Promise.resolve(o).get(a[index]); + }).filter(function(value, index) { + return value === index * 2; + }).then(function(values) { + assert.strictEqual(values.length, a.length); + }); + return b; + }); +}); diff --git a/test/mocha/getNewLibraryCopy.js b/test/mocha/getNewLibraryCopy.js new file mode 100644 index 0000000..16eb10d --- /dev/null +++ b/test/mocha/getNewLibraryCopy.js @@ -0,0 +1,30 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +describe("Promise.getNewLibraryCopy", function() { + it("should return an independent copy of Bluebird library", function() { + var Promise2 = Promise.getNewLibraryCopy(); + Promise2.x = 123; + + assert.equal(typeof Promise2.prototype.then, "function"); + assert.notEqual(Promise2, Promise); + + assert.equal(Promise2.x, 123); + assert.notEqual(Promise.x, 123); + }); + it("should return copy of Bluebird library with its own getNewLibraryCopy method", function() { + var Promise2 = Promise.getNewLibraryCopy(); + var Promise3 = Promise2.getNewLibraryCopy(); + Promise3.x = 123; + + assert.equal(typeof Promise3.prototype.then, "function"); + assert.notEqual(Promise3, Promise); + assert.notEqual(Promise3, Promise2); + + assert.equal(Promise3.x, 123); + assert.notEqual(Promise.x, 123); + assert.notEqual(Promise2.x, 123); + }); +}); diff --git a/test/mocha/github-2xx-76.js b/test/mocha/github-2xx-76.js new file mode 100644 index 0000000..4dbd66a --- /dev/null +++ b/test/mocha/github-2xx-76.js @@ -0,0 +1,21 @@ +"use strict"; + + +Promise.longStackTraces(); +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var isNodeJS = testUtils.isNodeJS; + + +if (isNodeJS) { + describe("github276 - stack trace cleaner", function(){ + specify("message with newline and a$_b should not be removed", function(){ + return Promise.resolve(1).then(function() { + throw new Error("Blah\n a$_b"); + }).then(assert.fail, function(e) { + var msg = e.stack.split('\n')[1] + assert(msg.indexOf('a$_b') >= 0, 'message should contain a$_b'); + }); + }); + }); +} diff --git a/test/mocha/github-3.6.4.js b/test/mocha/github-3.6.4.js new file mode 100644 index 0000000..2d0c1e3 --- /dev/null +++ b/test/mocha/github-3.6.4.js @@ -0,0 +1,40 @@ +"use strict"; + +var assert = require("assert"); +var Promise = adapter; + +function defer() { + var resolve, reject; + var promise = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + return { + resolve: resolve, + reject: reject, + promise: promise + }; +} + + +describe("github-364", function() { + specify("resolve between thens", function(done) { + var calls = 0; + var def = defer(); + + def.promise.then(function() { + calls++ + }); + def.resolve(); + def.promise.then(function() { + calls++ + }).then(function() { + calls++ + }).then(function() { + Promise.delay(11).then(function() { + assert.equal(calls, 3); + done(); + }); + }); + }); +}); diff --git a/test/mocha/github-3.7.3.js b/test/mocha/github-3.7.3.js new file mode 100644 index 0000000..99b2b4b --- /dev/null +++ b/test/mocha/github-3.7.3.js @@ -0,0 +1,14 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var Promise = adapter; + +describe("github-373", function() { + specify("unhandled unsuccessful Promise.join should result in correct error being reported", function() { + var err = new Error("test"); + var rejected = Promise.delay(1).thenThrow(err); + Promise.join(rejected, Promise.resolve(1), function(){}); + return testUtils.onUnhandledSucceed(err); + }); +}); diff --git a/test/mocha/github-4.1.7.js b/test/mocha/github-4.1.7.js new file mode 100644 index 0000000..8f152a3 --- /dev/null +++ b/test/mocha/github-4.1.7.js @@ -0,0 +1,46 @@ +var Promise = adapter; +var assert = require("assert"); + +describe("Github #417", function() { + + specify("minimal repro", function() { + var promise = new Promise(function(resolve) { + resolve(Promise.resolve().then(function() { + return new Promise(function(resolve) { + setTimeout(resolve, 1); + }); + })); + }); + + return promise.then(function() { + assert(promise.isResolved()); + }); + }); + + specify("original repro", function() { + var called = 0; + var bar = Promise.method(function() { + return Promise.bind(this) + .then(Promise.method(function() { + called++; + })); + }); + + var foo = Promise.method(function() { + return Promise.bind(this) + .then(Promise.method(function() { + return bar(); + })) + .bind(this) + .lastly(Promise.method(function() { + called++; + })); + }); + + return foo().then(function() { + called++; + assert.equal(3, called); + }); + }); +}); + diff --git a/test/mocha/github36.js b/test/mocha/github36.js new file mode 100644 index 0000000..dd2094c --- /dev/null +++ b/test/mocha/github36.js @@ -0,0 +1,59 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +describe("github36", function(){ + specify("should work", function() { + return new Promise(function(resolve, reject) { + var called = 0; + var donecalled = false; + var _d = Promise.defer(); + + _d.resolve() + + var f1 = function() { + return _d.promise.then(function() { + return true; + }) + } + + var f2 = function() { + var d1 = Promise.defer() + + setTimeout(function() { + d1.resolve() + }, 1) + + return d1.promise.then(function() { + return _d.promise.then(function() { + }) + }); + } + + var f3 = function() { + called++; + if (called > 15) { + return resolve(); + } + var promise = f1().then(function() { + f2() + .then(function() { + f3() + }) + }) + + promise.lastly(function() { + setTimeout(function() { + f3() + }, 1) + }) + + } + + f3(); + }); + }); +}); + diff --git a/test/mocha/helpers/assert_long_trace.js b/test/mocha/helpers/assert_long_trace.js new file mode 100644 index 0000000..e1fb571 --- /dev/null +++ b/test/mocha/helpers/assert_long_trace.js @@ -0,0 +1,86 @@ +var assert = require("assert"); + +function assertLongTrace(error, expectedJumpCount, expectedFramesForJumpsMap) { + var envFramePattern = /(?:\(node.js:|\(module.js:|\(timers.js:|\bcheckTimers\b|\bdrainQueue\b|\btimerLoop\b|\b_onImmediate\b|\b_immediateCallback\b)/; + var stack = error.stack.split("\n"); + var frameLinePattern = /(^\s+at|@|\s+\(No stack trace\))/; + var previousEventPattern = /^From previous event/; + var firstLine; + for (var i = 0; i < stack.length; ++i) { + if (previousEventPattern.test(stack[i])) { + throw new Error("From previous event before any frames"); + } + if (frameLinePattern.test(stack[i])) { + firstLine = i - 1; + break; + } + } + var prev = stack[firstLine - 1]; + var jumpCount = 1; + var jumpIndex = 0; + var currentJumpFramesCount = 0; + var envFramesCount = 0; + for (var i = firstLine; i < stack.length; ++i) { + var line = stack[i]; + if (previousEventPattern.test(line)) { + var jumpContainsOnlyEnvFrames = + currentJumpFramesCount === 0 && envFramesCount > 0; + if (!jumpContainsOnlyEnvFrames) { + if (previousEventPattern.test(prev)) { + throw new Error("2 consecutive From previous events"); + } + if (jumpIndex < expectedFramesForJumpsMap.length) { + var expectedFrames = expectedFramesForJumpsMap[jumpIndex]; + var expectedMessage = typeof expectedFrames === "number" + ? (expectedFrames + "") + : (expectedFrames[0] + "-" + expectedFrames[1]); + var message = "Expected " + (jumpIndex+1) + "th jump to contain " + + expectedMessage + " frames " + + "but it contains " + currentJumpFramesCount + " frames"; + if (typeof expectedFrames === "number") { + assert(expectedFrames === currentJumpFramesCount, message); + } else { + assert(expectedFrames[0] <= currentJumpFramesCount && + currentJumpFramesCount <= expectedFrames[1], + message); + } + } + jumpCount++; + jumpIndex++; + } + currentJumpFramesCount = 0; + envFramesCount = 0; + } else if (frameLinePattern.test(line)) { + if (envFramePattern.test(line)) { + envFramesCount++; + } else { + currentJumpFramesCount++; + } + } + prev = line; + } + assert.strictEqual( + previousEventPattern.test(stack[stack.length - 1]), false, + "The last line cannot be 'From previous event:'"); + if (typeof expectedJumpCount === "number") { + assert.strictEqual(expectedJumpCount, jumpCount, "Expected " + + expectedJumpCount + " jumps but saw " + jumpCount + " jumps"); + } else { + assert(expectedJumpCount[0] <= jumpCount && + jumpCount <= expectedJumpCount[1], + "Expected " + + expectedJumpCount[0] + "-" + expectedJumpCount[1] + + " jumps but saw " + jumpCount + " jumps" + ); + } + + if (jumpCount > (expectedFramesForJumpsMap.length + 1)) { + throw new Error("All jumps except the last one require an "+ + "expected frame count. " + + "Got expected frame counts for only " + + expectedFramesForJumpsMap.length + " while " + (jumpCount-1) + + " was expected"); + } + +} +module.exports = assertLongTrace; diff --git a/test/mocha/helpers/bluebird0_7_0.js b/test/mocha/helpers/bluebird0_7_0.js new file mode 100644 index 0000000..f8abe3d --- /dev/null +++ b/test/mocha/helpers/bluebird0_7_0.js @@ -0,0 +1,2272 @@ +/* jshint -W014, -W116, -W106 */ +/* global process, unreachable */ +/** + * @preserve Copyright (c) 2013 Petka Antonov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions:

+ * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +(function( global, Function, Array, Error, Object ) {"use strict"; + +var ASSERT = (function(){/* jshint -W014, -W116 */ + var AssertionError = (function() { + function AssertionError( a ) { + this.constructor$( a ); + this.message = a; + this.name = "AssertionError"; + } + AssertionError.prototype = new Error(); + AssertionError.prototype.constructor = AssertionError; + AssertionError.prototype.constructor$ = Error; + return AssertionError; + })(); + + return function assert( boolExpr, message ) { + if( boolExpr === true ) return; + + var ret = new AssertionError( message ); + if( Error.captureStackTrace ) { + Error.captureStackTrace( ret, assert ); + } + if( console && console.error ) { + console.error( ret.stack + "" ); + } + throw ret; + + }; +})(); + +var errorObj = {e: {}}; +var rescape = /[\r\n\u2028\u2029']/g; + +var replacer = function( ch ) { + return "\\u" + (("0000") + + (ch.charCodeAt(0).toString(16))).slice(-4); +}; + +function safeToEmbedString( str ) { + return str.replace( rescape, replacer ); +} + +function tryCatch1( fn, receiver, arg ) { + ASSERT(((typeof fn) === "function"), + "typeof fn === \u0022function\u0022"); + try { + return fn.call( receiver, arg ); + } + catch( e ) { + errorObj.e = e; + return errorObj; + } +} + +function tryCatch2( fn, receiver, arg, arg2 ) { + ASSERT(((typeof fn) === "function"), + "typeof fn === \u0022function\u0022"); + try { + return fn.call( receiver, arg, arg2 ); + } + catch( e ) { + errorObj.e = e; + return errorObj; + } +} + +function tryCatchApply( fn, args ) { + ASSERT(((typeof fn) === "function"), + "typeof fn === \u0022function\u0022"); + try { + return fn.apply( void 0, args ); + } + catch( e ) { + errorObj.e = e; + return errorObj; + } +} + +var create = Object.create || function( proto ) { + function F(){} + F.prototype = proto; + return new F(); +}; + +function makeNodePromisified( callback, receiver ) { + + function getCall(count) { + var args = new Array(count); + for( var i = 0, len = args.length; i < len; ++i ) { + args[i] = "a" + (i+1); + } + var comma = count > 0 ? "," : ""; + return ( receiver === void 0 + ? "callback("+args.join(",")+ comma +" fn);" + : "callback.call(receiver, "+args.join(",") + comma + " fn);" ) + + "break;"; + } + + return new Function("Promise", "callback", "receiver", + "return function promisified( a1, a2, a3, a4, a5 ) {\"use strict\";" + + "var len = arguments.length;" + + "var resolver = Promise.pending( promisified );" + + "" + + "var fn = function fn( err, value ) {" + + "if( err ) {" + + "resolver.reject( err );" + + "}" + + "else {" + + "resolver.fulfill( value );" + + "}" + + "};" + + "switch( len ) {" + + "case 5:" + getCall(5) + + "case 4:" + getCall(4) + + "case 3:" + getCall(3) + + "case 2:" + getCall(2) + + "case 1:" + getCall(1) + + "case 0:" + getCall(0) + + "default: callback.apply(receiver, arguments); break;" + + "}" + + "return resolver.promise;" + + "" + + "};" + )(Promise, callback, receiver); +} + + +var inherits = function( Child, Parent ) { + var hasProp = {}.hasOwnProperty; + + function T() { + this.constructor = Child; + this.constructor$ = Parent; + for (var propertyName in Parent.prototype) { + if (hasProp.call( Parent.prototype, propertyName) && + propertyName.charAt(propertyName.length-1) !== "$" + ) { + this[ propertyName + "$"] = Parent.prototype[propertyName]; + } + } + } + T.prototype = Parent.prototype; + Child.prototype = new T(); + return Child.prototype; +}; + +function subError( constructorName, nameProperty, defaultMessage ) { + defaultMessage = safeToEmbedString("" + defaultMessage ); + nameProperty = safeToEmbedString("" + nameProperty ); + + return new Function("create", "'use strict';\n" + + constructorName + ".prototype = create(Error.prototype);" + + constructorName + ".prototype.constructor = "+constructorName+";" + + "function "+constructorName+"(msg){" + + "if( Error.captureStackTrace ) {" + + "Error.captureStackTrace(this, this.constructor);" + + "}" + + "Error.call(this, msg);" + + "this.message = typeof msg === 'string'" + + "? msg" + + ": '"+defaultMessage+"';" + + "this.name = '"+nameProperty+"';" + + "} return "+constructorName+";")(create); +} + +if( typeof global.TypeError === "undefined" ) { + global.TypeError = subError( "TypeError", "TypeError" ); +} +var CancellationError = subError( "CancellationError", + "Cancel", "cancellation error" ); +var TimeoutError = subError( "TimeoutError", "Timeout", "timeout error" ); + + +var CapturedTrace = (function() { + +var rignore = new RegExp( + "\\b(?:Promise(?:Array)?\\$_\\w+|tryCatch(?:1|2|Apply)|setTimeout" + + "|makeNodePromisified|processImmediate|nextTick" + + "|Async\\$\\w+)\\b" +); + +var rtraceline = null; +var formatStack = null; + +function CapturedTrace( ignoreUntil ) { + ASSERT(((typeof ignoreUntil) === "function"), + "typeof ignoreUntil === \u0022function\u0022"); + ASSERT(((typeof ignoreUntil.name) === "string"), + "typeof ignoreUntil.name === \u0022string\u0022"); + ASSERT((ignoreUntil.name.length > 0), + "ignoreUntil.name.length > 0"); + this.captureStackTrace( ignoreUntil ); +} +var method = inherits( CapturedTrace, Error ); + +method.captureStackTrace = +function CapturedTrace$captureStackTrace( ignoreUntil ) { + captureStackTrace( this, ignoreUntil ); +}; + +CapturedTrace.possiblyUnhandledRejection = +function CapturedTrace$PossiblyUnhandledRejection( reason ) { + if( typeof console === "object" ) { + var stack = reason.stack; + var message = "Possibly unhandled " + formatStack( stack, reason ); + if( typeof console.error === "function" ) { + console.error( message ); + } + else if( typeof console.log === "function" ) { + console.log( message ); + } + } +}; + +CapturedTrace.combine = function CapturedTrace$Combine( current, prev ) { + var curLast = current.length - 1; + for( var i = prev.length - 1; i >= 0; --i ) { + var line = prev[i]; + if( current[ curLast ] === line ) { + current.pop(); + curLast--; + } + else { + break; + } + } + var lines = current.concat( prev ); + + var ret = []; + + + for( var i = 0, len = lines.length; i < len; ++i ) { + if( rignore.test( lines[i] ) || + ( i > 0 && !rtraceline.test( lines[i] ) ) + ) { + continue; + } + ret.push( lines[i] ); + } + return ret; +}; + +CapturedTrace.isSupported = function CapturedTrace$IsSupported() { + return typeof captureStackTrace === "function"; +}; + +var captureStackTrace = (function stackDetection() { + if( typeof Error.stackTraceLimit === "number" && + typeof Error.captureStackTrace === "function" ) { + rtraceline = /^\s*at\s*/; + formatStack = function( stack, error ) { + return ( typeof stack === "string" ) + ? stack + : error.name + ". " + error.message; + }; + return Error.captureStackTrace; + } + var err = new Error(); + + if( typeof err.stack === "string" && + typeof "".startsWith === "function" && + ( err.stack.startsWith("stackDetection@")) && + stackDetection.name === "stackDetection" ) { + + Object.defineProperty( Error, "stackTraceLimit", { + writable: true, + enumerable: false, + configurable: false, + value: 25 + }); + rtraceline = /@/; + var rline = /[@\n]/; + + formatStack = function( stack, error ) { + return ( typeof stack === "string" ) + ? ( error.name + ". " + error.message + "\n" + stack ) + : ( error.name + ". " + error.message ); + }; + + return function captureStackTrace(o, fn) { + var name = fn.name; + var stack = new Error().stack; + var split = stack.split( rline ); + var i, len = split.length; + for (i = 0; i < len; i += 2) { + if (split[i] === name) { + break; + } + } + ASSERT(((i + 2) < split.length), + "i + 2 < split.length"); + split = split.slice(i + 2); + len = split.length - 2; + var ret = ""; + for (i = 0; i < len; i += 2) { + ret += split[i]; + ret += "@"; + ret += split[i + 1]; + ret += "\n"; + } + o.stack = ret; + }; + } + else { + return null; + } +})(); + +return CapturedTrace;})(); + +function GetterCache(){} +function FunctionCache(){} + + +var getterCache = new GetterCache(), + functionCache = new FunctionCache(), + rjsident = /^[a-zA-Z$_][a-zA-Z0-9$_]*$/, + rkeyword = new RegExp( + "^(?:__proto__|undefined|NaN|Infinity|this|false|true|null|eval|" + + "arguments|break|case|catch|continue|debugger|default|delete|do|" + + "else|finally|for|function|if|in|instanceof|new|return|switch|th" + + "row|try|typeof|var|void|while|with|class|enum|export|extends|im" + + "port|super|implements|interface|let|package|private|protected|pu" + + "blic|static|yield)$" + ), + hasProp = {}.hasOwnProperty; + + + +function isJsIdentifier( val ) { + return rjsident.test(val) && + !rkeyword.test(val); +} + +function formatPropertyRead( val ) { + if( isJsIdentifier(val) ) { + return "." + val; + } + else { + return "['"+safeToEmbedString(val)+"']"; + } +} + +function getGetter( propertyName ) { + if( hasProp.call( getterCache, propertyName ) ) { + return getterCache[propertyName]; + } + var fn = new Function("obj", "return obj"+ + formatPropertyRead(""+propertyName) + +";"); + getterCache[propertyName] = fn; + return fn; +} + +function getFunction( propertyName ) { + if( hasProp.call( functionCache, propertyName ) ) { + return functionCache[propertyName]; + } + var fn = new Function("obj", "return obj"+ + formatPropertyRead(""+propertyName) + +"();"); + functionCache[propertyName] = fn; + return fn; +} +var Async = (function() { + +var deferFn = typeof process !== "undefined" ? + ( typeof global.setImmediate !== "undefined" + ? function( fn ){ + global.setImmediate( fn ); + } + : function( fn ) { + process.nextTick( fn ); + } + + ) : + ( typeof setTimeout !== "undefined" + ? function( fn ) { + setTimeout( fn, 4 ); + } + : function( fn ) { + fn(); + } +) ; + +function Async() { + this._isTickUsed = false; + this._length = 0; + this._backupBuffer = []; + var functionBuffer = this._functionBuffer = + new Array( 1000 * 3 ); + var self = this; + this.consumeFunctionBuffer = function Async$consumeFunctionBuffer() { + self._consumeFunctionBuffer(); + }; + + for( var i = 0, len = functionBuffer.length; i < len; ++i ) { + functionBuffer[i] = void 0; + } +} +var method = Async.prototype; + +method.haveItemsQueued = function Async$haveItemsQueued() { + return this._length > 0; +}; + +method.invokeLater = function Async$invokeLater( fn, receiver, arg ) { + ASSERT(((typeof fn) === "function"), + "typeof fn === \u0022function\u0022"); + ASSERT((arguments.length === 3), + "arguments.length === 3"); + this._backupBuffer.push( fn, receiver, arg ); + if( !this._isTickUsed ) { + deferFn( this.consumeFunctionBuffer ); + this._isTickUsed = true; + } +}; + +method.invoke = function Async$invoke( fn, receiver, arg ) { + ASSERT(((typeof fn) === "function"), + "typeof fn === \u0022function\u0022"); + ASSERT((arguments.length === 3), + "arguments.length === 3"); + var functionBuffer = this._functionBuffer, + len = functionBuffer.length, + length = this._length; + + if( length === len ) { + functionBuffer.push( fn, receiver, arg ); + } + else { + ASSERT((length < len), + "length < len"); + functionBuffer[ length + 0 ] = fn; + functionBuffer[ length + 1 ] = receiver; + functionBuffer[ length + 2 ] = arg; + } + this._length = length + 3; + + if( !this._isTickUsed ) { + deferFn( this.consumeFunctionBuffer ); + this._isTickUsed = true; + } +}; + +method._consumeFunctionBuffer = function Async$_consumeFunctionBuffer() { + var functionBuffer = this._functionBuffer; + ASSERT(this._isTickUsed, + "this._isTickUsed"); + for( var i = 0; i < this._length; i += 3 ) { + functionBuffer[ i + 0 ].call( + functionBuffer[ i + 1 ], + functionBuffer[ i + 2 ] ); + + functionBuffer[ i + 0 ] = + functionBuffer[ i + 1 ] = + functionBuffer[ i + 2 ] = + void 0; + } + this._reset(); + if( this._backupBuffer.length ) { + var buffer = this._backupBuffer; + for( var i = 0; i < buffer.length; i+= 3 ) { + buffer[ i + 0 ].call( + buffer[ i + 1 ] , + buffer[ i + 2 ] ); + } + buffer.length = 0; + } +}; + +method._reset = function Async$_reset() { + this._isTickUsed = false; + this._length = 0; +}; + + +return Async;})(); + +var async = new Async(); +var Thenable = (function() { + +function Thenable() { + this.errorObj = errorObj; + this.__id__ = 0; + this.treshold = 1000; + this.thenableCache = new Array( this.treshold ); + this.promiseCache = new Array( this.treshold ); + this._compactQueued = false; +} +var method = Thenable.prototype; + +method.couldBe = function Thenable$couldBe( ret ) { + if( ret === null || + typeof ret === "undefined" || + typeof ret === "string" || + typeof ret === "boolean" || + typeof ret === "number" ) { + return false; + } + var id = ret.__id_$thenable__; + if( typeof id === "number" && + this.thenableCache[id] !== void 0 ) { + return true; + } + return ("then" in ret); +}; + +method.is = function Thenable$is( ret, ref ) { + var id = ret.__id_$thenable__; + if( typeof id === "number" && + this.thenableCache[id] !== void 0 ) { + ref.ref = this.thenableCache[id]; + ref.promise = this.promiseCache[id]; + return true; + } + return this._thenableSlowCase( ret, ref ); +}; + +method.addCache = function Thenable$_addCache( thenable, promise ) { + var id = this.__id__; + this.__id__ = id + 1; + var descriptor = this._descriptor( id ); + Object.defineProperty( thenable, "__id_$thenable__", descriptor ); + this.thenableCache[id] = thenable; + this.promiseCache[id] = promise; + ASSERT((this.thenableCache[thenable.__id_$thenable__] === thenable), + "this.thenableCache[ thenable.__id_$thenable__ ] === thenable"); + if( this.thenableCache.length > this.treshold && + !this._compactQueued) { + this._compactQueued = true; + async.invokeLater( this._compactCache, this, void 0 ); + } +}; + +method.deleteCache = function Thenable$deleteCache( thenable ) { + var id = thenable.__id_$thenable__; + ASSERT(((typeof id) === "number"), + "typeof id === \u0022number\u0022"); + ASSERT(((id | 0) === id), + "(id | 0) === id"); + if( id === -1 ) { + return; + } + ASSERT((id > -1), + "id > -1"); + ASSERT((id < this.__id__), + "id < this.__id__"); + ASSERT((this.thenableCache[id] === thenable), + "this.thenableCache[id] === thenable"); + this.thenableCache[id] = void 0; + this.promiseCache[id] = void 0; + thenable.__id_$thenable__ = -1;}; + +var descriptor = { + value: 0, + enumerable: false, + writable: true, + configurable: true +}; +method._descriptor = function Thenable$_descriptor( id ) { + descriptor.value = id; + return descriptor; +}; + +method._compactCache = function Thenable$_compactCache() { + var arr = this.thenableCache; + var promiseArr = this.promiseCache; + var skips = 0; + var j = 0; + for( var i = 0, len = arr.length; i < len; ++i ) { + var item = arr[ i ]; + if( item === void 0 ) { + skips++; + } + else { + promiseArr[ j ] = promiseArr[ i ]; + item.__id_$thenable__ = j; + arr[ j++ ] = item; + } + } + var newId = arr.length - skips; + if( newId === this.__id__ ) { + this.treshold *= 2; + } + else for( var i = newId, len = arr.length; i < len; ++i ) { + promiseArr[ j ] = arr[i] = void 0; + } + + this.__id__ = newId; + this._compactQueued = false; +}; + +method._thenableSlowCase = function Thenable$_thenableSlowCase( ret, ref ) { + try { + var then = ret.then; + if( typeof then === "function" ) { + ref.ref = then; + return true; + } + return false; + } + catch(e) { + this.errorObj.e = e; + ref.ref = this.errorObj; + return true; + } +}; + + + + +return Thenable;})(); +var CatchFilter = (function() { + +function CatchFilter( instances, callback ) { + this._instances = instances; + this._callback = callback; +} +var method = CatchFilter.prototype; + +method.doFilter = function( e ) { + if( e === null || typeof e !== "object" ) { + throw e; + } + var cb = this._callback; + for( var i = 0, len = this._instances.length; i < len; ++i ) { + var item = this._instances[i]; + if( e instanceof item ) { + var ret = tryCatch1( cb, void 0, e ); + if( ret === errorObj ) { + throw ret.e; + } + return ret; + } + } + throw e; +}; + +return CatchFilter;})(); +var Promise = (function() { + +function isObject( value ) { + if( value === null ) { + return false; + } + return ( typeof value === "object" || + typeof value === "function" ); +} + +function isPromise( obj ) { + if( typeof obj !== "object" ) return false; + return obj instanceof Promise; +} + +var Err = Error; +function isError( obj ) { + if( typeof obj !== "object" ) return false; + return obj instanceof Err; +} + +var Arr = Array; +var isArray = Arr.isArray || function( obj ) { + return obj instanceof Arr; +}; + + +var APPLY = {}; +var thenable = new Thenable( errorObj ); + +function Promise( resolver ) { + if( typeof resolver === "function" ) + this._resolveResolver( resolver ); + + + this._bitField = 67108864; + this._fulfill0 = void 0; + this._reject0 = void 0; + this._progress0 = void 0; + this._promise0 = void 0; + this._receiver0 = void 0; + this._resolvedValue = void 0; + this._cancellationParent = void 0; + if( longStackTraces ) this._traceParent = this._peekContext(); +} + +var method = Promise.prototype; + +var longStackTraces = true; +Promise.longStackTraces = function() { + if( async.haveItemsQueued() && + longStackTraces === false + ) { + throw new Error("Cannot enable long stack traces " + + "after promises have been created"); + } + longStackTraces = true; +}; + +method._setTrace = function _setTrace( fn ) { + ASSERT((this._trace == null), + "this._trace == null"); + if( longStackTraces ) { + this._trace = new CapturedTrace( + typeof fn === "function" + ? fn + : _setTrace + ); + } + return this; +}; + +method.toString = function Promise$toString() { + return "[object Promise]"; +}; + + +method.caught = method["catch"] = function Promise$catch( fn ) { + var len = arguments.length; + if( len > 1 ) { + var catchInstances = new Array( len - 1 ), + j = 0, i; + for( i = 0; i < len - 1; ++i ) { + var item = arguments[i]; + if( typeof item === "function" && + ( item.prototype instanceof Error || + item === Error ) ) { + catchInstances[j++] = item; + } + } + catchInstances.length = j; + fn = arguments[i]; + var catchFilter = new CatchFilter( catchInstances, fn ); + return this._then( void 0, catchFilter.doFilter, void 0, + catchFilter, void 0, this.caught ); + } + return this._then( void 0, fn, void 0, void 0, void 0, this.caught ); +}; + +method.progressed = function Promise$progressed( fn ) { + return this._then( void 0, void 0, fn, void 0, void 0, this.progressed ); +}; + + +function thrower( r ) { + throw r; +} +function slowFinally( ret, reasonOrValue ) { + if( this.isFulfilled() ) { + return ret._then(function() { + return reasonOrValue; + }, thrower, void 0, this, void 0, slowFinally ); + } + else { + return ret._then(function() { + throw reasonOrValue; + }, thrower, void 0, this, void 0, slowFinally ); + } +} +method.lastly = method["finally"] = function Promise$finally( fn ) { + var r = function( reasonOrValue ) { + var ret = fn( reasonOrValue ); + if( isPromise( ret ) ) { + return slowFinally.call( this, ret, reasonOrValue ); + } + if( this.isRejected() ) throw reasonOrValue; + return reasonOrValue; + }; + return this._then( r, r, void 0, this, void 0, this.anyway ); +}; + +method.inspect = function Promise$inspect() { + return new PromiseInspection( this ); +}; + +method.cancel = function Promise$cancel() { + if( !this.isCancellable() ) return this; + var cancelTarget = this; + while( cancelTarget._cancellationParent !== void 0 ) { + cancelTarget = cancelTarget._cancellationParent; + } + if( cancelTarget === this ) { + var err = new CancellationError(); + this._attachExtraTrace( err ); + async.invoke( this._reject, this, err ); + } + else { + async.invoke( cancelTarget.cancel, cancelTarget, void 0 ); + } + return this; +}; + +method.uncancellable = function Promise$uncancellable() { + var ret = new Promise(); + ret._setTrace(); + ret._unsetCancellable(); + ret._assumeStateOf( this, true ); + return ret; +}; + +method.fork = function Promise$fork( didFulfill, didReject, didProgress ) { + var ret = this._then( didFulfill, didReject, didProgress, + void 0, void 0, this.fork ); + ret._cancellationParent = void 0; + return ret; +}; + +method.call = function Promise$call( propertyName ) { + var len = arguments.length; + + if( len < 2 ) { + return this._callFast( propertyName ); + } + else { + var args = new Array(len-1); + for( var i = 1; i < len; ++i ) { + args[ i - 1 ] = arguments[ i ]; + } + return this._callSlow( propertyName, args ); + } + +}; + +method.get = function Promise$get( propertyName ) { + return this._then( + getGetter( propertyName ), + void 0, + void 0, + void 0, + void 0, + this.get + ); +}; + +method.then = function Promise$then( didFulfill, didReject, didProgress ) { + return this._then( didFulfill, didReject, didProgress, + void 0, void 0, this.then ); +}; + +method.spread = function Promise$spread( didFulfill, didReject ) { + return this._then( didFulfill, didReject, void 0, + APPLY, void 0, this.spread ); +}; +method.isFulfilled = function Promise$isFulfilled() { + return ( this._bitField & 268435456 ) > 0; +}; + +method.isRejected = function Promise$isRejected() { + return ( this._bitField & 134217728 ) > 0; +}; + +method.isPending = function Promise$isPending() { + return !this.isResolved(); +}; + +method.isResolved = function Promise$isResolved() { + return ( this._bitField & 402653184 ) > 0; +}; + +method.isCancellable = function Promise$isCancellable() { + return !this.isResolved() && + this._cancellable(); +}; + +method.toJSON = function Promise$toJSON() { + var inspection = this.inspect(); + var ret = { + isFulfilled: false, + isRejected: false + }; + if( inspection.isFulfilled() ) { + ret.fulfillmentValue = inspection.value(); + ret.isFulfilled = true; + } + else if( inspection.isRejected() ) { + ret.rejectionReason = inspection.error(); + ret.isRejected = true; + } + return ret; +}; + +method.map = function Promise$map( fn ) { + return Promise.map( this, fn ); +}; + +method.all = function Promise$all() { + return Promise.all( this ); +}; + +method.any = function Promise$any() { + return Promise.any( this ); +}; + +method.settle = function Promise$settle() { + return Promise.settle( this ); +}; + +method.some = function Promise$some( count ) { + return Promise.some( this, count ); +}; + +method.reduce = function Promise$reduce( fn, initialValue ) { + return Promise.reduce( this, fn, initialValue ); +}; + +Promise.is = isPromise; + +Promise.settle = function Promise$Settle( promises ) { + var ret = Promise._all( promises, SettledPromiseArray ); + return ret.promise(); +}; + +Promise.all = function Promise$All( promises ) { + var ret = Promise._all( promises, PromiseArray ); + return ret.promise(); +}; + +Promise.join = function Promise$Join() { + var ret = new Array( arguments.length ); + for( var i = 0, len = ret.length; i < len; ++i ) { + ret[i] = arguments[i]; + } + return Promise._all( ret, PromiseArray ).promise(); +}; + +Promise.any = function Promise$Any( promises ) { + var ret = Promise._all( promises, AnyPromiseArray ); + return ret.promise(); +}; + +Promise.some = function Promise$Some( promises, howMany ) { + var ret = Promise._all( promises, SomePromiseArray ); + if( ( howMany | 0 ) !== howMany ) { + throw new TypeError("howMany must be an integer"); + } + var len = ret.length(); + howMany = Math.max(0, Math.min( howMany, len ) ); + ret._howMany = howMany; + return ret.promise(); +}; + + +function mapper( fulfilleds ) { + var fn = this; + var shouldDefer = false; + for( var i = 0, len = fulfilleds.length; i < len; ++i ) { + var fulfill = fn(fulfilleds[i]); + if( !shouldDefer && isPromise( fulfill ) ) { + if( fulfill.isFulfilled() ) { + fulfilleds[i] = fulfill._resolvedValue; + continue; + } + else { + shouldDefer = true; + } + } + fulfilleds[i] = fulfill; + } + return shouldDefer ? Promise.all( fulfilleds ) : fulfilleds; +} +Promise.map = function Promise$Map( promises, fn ) { + if( typeof fn !== "function" ) + throw new TypeError( "fn is not a function" ); + return Promise.all( promises )._then( + mapper, + void 0, + void 0, + fn, + void 0, + Promise.all + ); +}; + +function reducer( fulfilleds, initialValue ) { + var fn = this; + var len = fulfilleds.length; + var accum; + var startIndex = 0; + + if( initialValue !== void 0 ) { + accum = initialValue; + startIndex = 0; + } + else { + accum = len > 0 ? fulfilleds[0] : void 0; + startIndex = 1; + } + for( var i = startIndex; i < len; ++i ) { + accum = fn( accum, fulfilleds[i], i, len ); + } + return accum; +} + +function slowReduce( promises, fn, initialValue ) { + return Promise._all( promises, PromiseArray, slowReduce ) + .promise() + .then( function( fulfilleds ) { + return reducer.call( fn, fulfilleds, initialValue ); + }); +} + + +Promise.reduce = function Promise$Reduce( promises, fn, initialValue ) { + if( typeof fn !== "function" ) + throw new TypeError( "fn is not a function"); + if( initialValue !== void 0 ) { + return slowReduce( promises, fn, initialValue ); + } + return Promise + .all( promises ) + ._then( reducer, void 0, void 0, fn, void 0, Promise.all ); +}; + +Promise.fulfilled = function Promise$Fulfilled( value ) { + var ret = new Promise(); + ret._setTrace(); + if( ret._tryAssumeStateOf( value, false ) ) { + return ret; + } + ret._cleanValues(); + ret._setFulfilled(); + ret._resolvedValue = value; + return ret; +}; + +Promise.rejected = function Promise$Rejected( reason ) { + var ret = new Promise(); + ret._setTrace(); + ret._cleanValues(); + ret._setRejected(); + ret._resolvedValue = reason; + return ret; +}; + +Promise.pending = function Promise$Pending( caller ) { + var promise = new Promise(); + promise._setTrace( caller ); + return new PromiseResolver( promise ); +}; + + +Promise.cast = function Promise$Cast( obj ) { + var ret = cast( obj ); + if( !( ret instanceof Promise ) ) { + return Promise.fulfilled( ret ); + } + return ret; +}; + +Promise.onPossiblyUnhandledRejection = +function Promise$OnPossiblyUnhandledRejection( fn ) { + if( typeof fn === "function" ) { + CapturedTrace.possiblyUnhandledRejection = fn; + } + else { + CapturedTrace.possiblyUnhandledRejection = void 0; + } +}; + +Promise.promisify = function Promise$Promisify( callback, receiver) { + return makeNodePromisified( callback, receiver ); +}; + +method._then = +function Promise$_then( + didFulfill, + didReject, + didProgress, + receiver, + internalData, + caller +) { + var haveInternalData = internalData !== void 0; + var ret = haveInternalData ? internalData : new Promise(); + + if( longStackTraces && !haveInternalData ) { + ret._traceParent = this; + ret._setTrace( typeof caller === "function" ? caller : this._then ); + } + + var callbackIndex = + this._addCallbacks( didFulfill, didReject, didProgress, ret, receiver ); + + if( this.isResolved() ) { + async.invoke( this._resolveLast, this, callbackIndex ); + } + else if( !haveInternalData && this.isCancellable() ) { + ret._cancellationParent = this; + } + + if( this._isDelegated() ) { + this._unsetDelegated(); + ASSERT((! this.isResolved()), + "!this.isResolved()"); + var x = this._resolvedValue; + if( !this._tryThenable( x ) ) { + async.invoke( this._fulfill, this, x ); + } + } + return ret; +}; + +method._length = function Promise$_length() { + ASSERT(isPromise(this), + "isPromise( this )"); + ASSERT((arguments.length === 0), + "arguments.length === 0"); + return this._bitField & 16777215; +}; + +method._isFollowingOrFulfilledOrRejected = +function Promise$_isFollowingOrFulfilledOrRejected() { + return ( this._bitField & 939524096 ) > 0; +}; + +method._setLength = function Promise$_setLength( len ) { + this._bitField = ( this._bitField & -16777216 ) | + ( len & 16777215 ) ; +}; + +method._cancellable = function Promise$_cancellable() { + return ( this._bitField & 67108864 ) > 0; +}; + +method._setFulfilled = function Promise$_setFulfilled() { + this._bitField = this._bitField | 268435456; +}; + +method._setRejected = function Promise$_setRejected() { + this._bitField = this._bitField | 134217728; +}; + +method._setFollowing = function Promise$_setFollowing() { + this._bitField = this._bitField | 536870912; +}; + +method._setDelegated = function Promise$_setDelegated() { + this._bitField = this._bitField | -1073741824; + +}; + +method._isDelegated = function Promise$_isDelegated() { + return ( this._bitField & -1073741824 ) === -1073741824; +}; + +method._unsetDelegated = function Promise$_unsetDelegated() { + this._bitField = this._bitField & ( ~-1073741824 ); +}; + +method._setCancellable = function Promise$_setCancellable() { + this._bitField = this._bitField | 67108864; +}; + +method._unsetCancellable = function Promise$_unsetCancellable() { + this._bitField = this._bitField & ( ~67108864 ); +}; + +method._receiverAt = function Promise$_receiverAt( index ) { + ASSERT(((typeof index) === "number"), + "typeof index === \u0022number\u0022"); + ASSERT((index >= 0), + "index >= 0"); + ASSERT((index < this._length()), + "index < this._length()"); + ASSERT(((index % 5) === 0), + "index % CALLBACK_SIZE === 0"); + if( index === 0 ) return this._receiver0; + return this[ index + 4 - 5 ]; +}; + +method._promiseAt = function Promise$_promiseAt( index ) { + ASSERT(((typeof index) === "number"), + "typeof index === \u0022number\u0022"); + ASSERT((index >= 0), + "index >= 0"); + ASSERT((index < this._length()), + "index < this._length()"); + ASSERT(((index % 5) === 0), + "index % CALLBACK_SIZE === 0"); + if( index === 0 ) return this._promise0; + return this[ index + 3 - 5 ]; +}; + +method._fulfillAt = function Promise$_fulfillAt( index ) { + ASSERT(((typeof index) === "number"), + "typeof index === \u0022number\u0022"); + ASSERT((index >= 0), + "index >= 0"); + ASSERT((index < this._length()), + "index < this._length()"); + ASSERT(((index % 5) === 0), + "index % CALLBACK_SIZE === 0"); + if( index === 0 ) return this._fulfill0; + return this[ index + 0 - 5 ]; +}; + +method._rejectAt = function Promise$_rejectAt( index ) { + ASSERT(((typeof index) === "number"), + "typeof index === \u0022number\u0022"); + ASSERT((index >= 0), + "index >= 0"); + ASSERT((index < this._length()), + "index < this._length()"); + ASSERT(((index % 5) === 0), + "index % CALLBACK_SIZE === 0"); + if( index === 0 ) return this._reject0; + return this[ index + 1 - 5 ]; +}; + +method._progressAt = function Promise$_progressAt( index ) { + ASSERT(((typeof index) === "number"), + "typeof index === \u0022number\u0022"); + ASSERT((index >= 0), + "index >= 0"); + ASSERT((index < this._length()), + "index < this._length()"); + ASSERT(((index % 5) === 0), + "index % CALLBACK_SIZE === 0"); + if( index === 0 ) return this._progress0; + return this[ index + 2 - 5 ]; +}; + +var fulfiller = new Function("p", + "'use strict';return function Promise$_fulfiller(a){ p.fulfill( a ); }" ); +var rejecter = new Function("p", + "'use strict';return function Promise$_rejecter(a){ p.reject( a ); }" ); + +method._resolveResolver = function Promise$_resolveResolver( resolver ) { + ASSERT(((typeof resolver) === "function"), + "typeof resolver === \u0022function\u0022"); + this._setTrace( this._resolveResolver ); + var p = new PromiseResolver( this ); + this._pushContext(); + var r = tryCatch2( resolver, this, fulfiller( p ), rejecter( p ) ); + this._popContext(); + if( r === errorObj ) { + p.reject( r.e ); + } +}; + +method._addCallbacks = function Promise$_addCallbacks( + fulfill, + reject, + progress, + promise, + receiver +) { + fulfill = typeof fulfill === "function" ? fulfill : void 0; + reject = typeof reject === "function" ? reject : void 0; + progress = typeof progress === "function" ? progress : void 0; + var index = this._length(); + + if( index === 0 ) { + this._fulfill0 = fulfill; + this._reject0 = reject; + this._progress0 = progress; + this._promise0 = promise; + this._receiver0 = receiver; + this._setLength( index + 5 ); + return index; + } + + this[ index - 5 + 0 ] = fulfill; + this[ index - 5 + 1 ] = reject; + this[ index - 5 + 2 ] = progress; + this[ index - 5 + 3 ] = promise; + this[ index - 5 + 4 ] = receiver; + + this._setLength( index + 5 ); + return index; +}; + +method._callFast = function Promise$_callFast( propertyName ) { + return this._then( + getFunction( propertyName ), + void 0, + void 0, + void 0, + void 0, + this.call + ); +}; + +method._callSlow = function Promise$_callSlow( propertyName, args ) { + ASSERT(isArray(args), + "isArray( args )"); + ASSERT((args.length > 0), + "args.length > 0"); + return this._then( function( obj ) { + return obj[propertyName].apply( obj, args ); + }, + void 0, + void 0, + void 0, + void 0, + this.call + ); +}; + +method._resolveLast = function Promise$_resolveLast( index ) { + ASSERT(((typeof index) === "number"), + "typeof index === \u0022number\u0022"); + ASSERT((index >= 0), + "index >= 0"); + ASSERT((index < this._length()), + "index < this._length()"); + var promise = this._promiseAt( index ); + var receiver = this._receiverAt( index ); + var fn; + + if( this.isFulfilled() ) { + fn = this._fulfillAt( index ); + } + else if( this.isRejected() ) { + fn = this._rejectAt( index ); + } + else unreachable(); + + var obj = this._resolvedValue; + var ret = obj; + if( fn !== void 0 ) { + this._resolvePromise( fn, receiver, obj, promise ); + } + else if( this.isFulfilled() ) { + promise._fulfill( ret ); + } + else { + promise._reject( ret ); + } +}; + +method._spreadSlowCase = +function Promise$_spreadSlowCase( targetFn, promise, values ) { + promise._assumeStateOf( + Promise.all( values )._then( targetFn, void 0, void 0, APPLY, void 0, + this._spreadSlowCase ), + false + ); +}; + + +function cast( obj ) { + if( isObject( obj ) ) { + if( obj instanceof Promise ) { + return obj; + } + var ref = { ref: null, promise: null }; + if( thenable.is( obj, ref ) ) { + if( ref.promise != null ) { + return ref.promise; + } + var resolver = Promise.pending(); + var result = ref.ref; + if( result === errorObj ) { + resolver.reject( result.e ); + return resolver.promise; + } + thenable.addCache( obj, resolver.promise ); + var called = false; + var ret = tryCatch2( result, obj, function t( a ) { + if( called ) return; + called = true; + async.invoke( thenable.deleteCache, thenable, obj ); + var b = cast( a ); + if( b === a ) { + resolver.fulfill( a ); + } + else { + b._then( + resolver.fulfill, + resolver.reject, + void 0, + resolver, + void 0, + t + ); + } + }, function t( a ) { + if( called ) return; + called = true; + async.invoke( thenable.deleteCache, thenable, obj ); + resolver.reject( a ); + }); + if( ret === errorObj && !called ) { + resolver.reject( ret.e ); + async.invoke( thenable.deleteCache, thenable, obj ); + } + return resolver.promise; + } + } + return obj; +} + +method._resolveThenable = function Promise$_resolveThenable( x, ref ) { + if( ref.promise != null ) { + this._assumeStateOf( ref.promise, true ); + return; + } + if( ref.ref === errorObj ) { + this._attachExtraTrace( ref.ref.e ); + async.invoke( this._reject, this, ref.ref.e ); + } + else { + thenable.addCache( x, this ); + var then = ref.ref; + var localX = x; + var localP = this; + var key = {}; + var called = false; + var t = function t( v ) { + if( called && this !== key ) return; + called = true; + var b = cast( v ); + + if( b !== v || + ( b instanceof Promise && b.isPending() ) ) { + b._then( t, r, void 0, key, void 0, t); + return; + } + + var fn = localP._fulfill; + if( b instanceof Promise ) { + var fn = b.isFulfilled() + ? localP._fulfill : localP._reject; + ASSERT(b.isResolved(), + "b.isResolved()"); + v = v._resolvedValue; + b = cast( v ); + ASSERT(((b instanceof Promise) || (b === v)), + "b instanceof Promise || b === v"); + if( b !== v || + ( b instanceof Promise && b !== v ) ) { + b._then( t, r, void 0, key, void 0, t); + return; + } + } + async.invoke( fn, localP, v ); + async.invoke( thenable.deleteCache, + thenable, localX ); + }; + + var r = function r( v ) { + if( called && this !== key ) return; + called = true; + + var b = cast( v ); + + if( b !== v || + ( b instanceof Promise && b.isPending() ) ) { + b._then( t, r, void 0, key, void 0, t); + return; + } + + var fn = localP._reject; + if( b instanceof Promise ) { + var fn = b.isFulfilled() + ? localP._fulfill : localP._reject; + ASSERT(b.isResolved(), + "b.isResolved()"); + v = v._resolvedValue; + b = cast( v ); + if( b !== v || + ( b instanceof Promise && b.isPending() ) ) { + b._then( t, r, void 0, key, void 0, t); + return; + } + } + + async.invoke( fn, localP, v ); + async.invoke( thenable.deleteCache, + thenable, localX ); + + }; + var threw = tryCatch2( then, x, t, r); + if( threw === errorObj && + !called ) { + this._attachExtraTrace( threw.e ); + async.invoke( this._reject, this, threw.e ); + async.invoke( thenable.deleteCache, thenable, x ); + } + } +}; + +method._tryThenable = function Promise$_tryThenable( x ) { + var ref; + if( !thenable.is( x, ref = {ref: null, promise: null} ) ) { + return false; + } + this._resolveThenable( x, ref ); + return true; +}; + +var ignore = CatchFilter.prototype.doFilter; +method._resolvePromise = function Promise$_resolvePromise( + onFulfilledOrRejected, receiver, value, promise +) { + if( isError( value ) ) { + value.__handled = true; + } + + if( !isPromise( promise ) ) { + return onFulfilledOrRejected.call( receiver, value, promise ); + } + + var x; + if( receiver === APPLY ) { + if( isArray( value ) ) { + for( var i = 0, len = value.length; i < len; ++i ) { + if( isPromise( value[i] ) ) { + this._spreadSlowCase( + onFulfilledOrRejected, + promise, + value + ); + return; + } + } + promise._pushContext(); + x = tryCatchApply( onFulfilledOrRejected, value ); + } + else { + this._spreadSlowCase( onFulfilledOrRejected, promise, value ); + return; + } + } + else { + promise._pushContext(); + x = tryCatch1( onFulfilledOrRejected, receiver, value ); + } + + promise._popContext(); + + if( x === errorObj ) { + if( onFulfilledOrRejected !== ignore ) { + promise._attachExtraTrace( x.e ); + } + async.invoke( promise._reject, promise, x.e ); + } + else if( x === promise ) { + async.invoke( + promise._reject, + promise, + new TypeError( "Circular thenable chain" ) + ); + } + else { + if( promise._tryAssumeStateOf( x, true ) ) { + return; + } + else if( thenable.couldBe( x ) ) { + + if( promise._length() === 0 ) { + promise._resolvedValue = x; + promise._setDelegated(); + return; + } + else if( promise._tryThenable( x ) ) { + return; + } + } + async.invoke( promise._fulfill, promise, x ); + } +}; + +method._assumeStateOf = +function Promise$_assumeStateOf( promise, mustAsync ) { + ASSERT(isPromise(promise), + "isPromise( promise )"); + ASSERT(((typeof mustAsync) === "boolean"), + "typeof mustAsync === \u0022boolean\u0022"); + ASSERT((this._isFollowingOrFulfilledOrRejected() === false), + "this._isFollowingOrFulfilledOrRejected() === false"); + this._setFollowing(); + if( promise.isPending() ) { + if( promise._cancellable() ) { + this._cancellationParent = promise; + } + promise._then( + this._resolveFulfill, + this._resolveReject, + this._resolveProgress, + this, + void 0, + this._tryAssumeStateOf + ); + } + else if( promise.isFulfilled() ) { + if( mustAsync ) + async.invoke( this._resolveFulfill, this, promise._resolvedValue ); + else + this._resolveFulfill( promise._resolvedValue ); + } + else { + if( mustAsync ) + async.invoke( this._resolveReject, this, promise._resolvedValue ); + else + this._resolveReject( promise._resolvedValue ); + } + + if( longStackTraces && + promise._traceParent == null ) { + promise._traceParent = this; + } +}; + +method._tryAssumeStateOf = +function Promise$_tryAssumeStateOf( value, mustAsync ) { + if( !isPromise( value ) || + this._isFollowingOrFulfilledOrRejected() ) return false; + + this._assumeStateOf( value, mustAsync ); + return true; +}; + + + +method._attachExtraTrace = function Promise$_attachExtraTrace( error ) { + if( longStackTraces && + isError( error ) ) { + var promise = this; + var stack = error.stack.split("\n"); + var headerLineCount = 1; + + while( promise != null && + promise._trace != null ) { + stack = CapturedTrace.combine( + stack, + promise._trace.stack.split( "\n" ) + ); + promise = promise._traceParent; + } + + var max = Error.stackTraceLimit + headerLineCount; + var len = stack.length; + if( len > max ) { + stack.length = max; + } + if( stack.length <= headerLineCount ) { + error.stack = "(No stack trace)"; + } + else { + error.stack = stack.join("\n"); + } + } +}; + +method._notifyUnhandledRejection = +function Promise$_notifyUnhandledRejection( reason ) { + if( !reason.__handled ) { + reason.__handled = true; + CapturedTrace.possiblyUnhandledRejection( reason ); + } +}; + +method._unhandledRejection = function Promise$_unhandledRejection( reason ) { + if( !reason.__handled ) { + async.invokeLater( this._notifyUnhandledRejection, this, reason ); + } +}; + +method._cleanValues = function Promise$_cleanValues() { + this._cancellationParent = void 0; +}; + +method._fulfill = function Promise$_fulfill( value ) { + if( this._isFollowingOrFulfilledOrRejected() ) return; + this._resolveFulfill( value ); + +}; + +method._reject = function Promise$_reject( reason ) { + if( this._isFollowingOrFulfilledOrRejected() ) return; + this._resolveReject( reason ); +}; + +method._progress = function Promise$_progress( progressValue ) { + if( this._isFollowingOrFulfilledOrRejected() ) return; + this._resolveProgress( progressValue ); + +}; + +method._resolveFulfill = function Promise$_resolveFulfill( value ) { + ASSERT(this.isPending(), + "this.isPending()"); + this._cleanValues(); + this._setFulfilled(); + this._resolvedValue = value; + var len = this._length(); + for( var i = 0; i < len; i+= 5 ) { + var fn = this._fulfillAt( i ); + var promise = this._promiseAt( i ); + var receiver = this._receiverAt( i ); + if( fn !== void 0 ) { + this._resolvePromise( + fn, + receiver, + value, + promise + ); + + } + else { + async.invoke( promise._fulfill, promise, value ); + } + } +}; + +method._resolveReject = function Promise$_resolveReject( reason ) { + ASSERT(this.isPending(), + "this.isPending()"); + this._cleanValues(); + this._setRejected(); + this._resolvedValue = reason; + var len = this._length(); + var rejectionWasHandled = false; + for( var i = 0; i < len; i+= 5 ) { + var fn = this._rejectAt( i ); + var promise = this._promiseAt( i ); + if( fn !== void 0 ) { + rejectionWasHandled = true; + this._resolvePromise( + fn, + this._receiverAt( i ), + reason, + promise + ); + } + else { + if( !rejectionWasHandled ) + rejectionWasHandled = promise._length() > 0; + async.invoke( promise._reject, promise, reason ); + } + } + if( !rejectionWasHandled && + isError( reason ) && + CapturedTrace.possiblyUnhandledRejection !== void 0 + + ) { + if( reason.__handled !== true ) { + reason.__handled = false; + async.invoke( + this._unhandledRejection, + this, + reason + ); + } + } + +}; + +method._resolveProgress = function Promise$_resolveProgress( progressValue ) { + ASSERT(this.isPending(), + "this.isPending()"); + var len = this._length(); + for( var i = 0; i < len; i += 5 ) { + var fn = this._progressAt( i ); + var promise = this._promiseAt( i ); + if( !isPromise( promise ) ) { + fn.call( this._receiverAt( i ), progressValue, promise ); + continue; + } + var ret = progressValue; + if( fn !== void 0 ) { + this._pushContext(); + ret = tryCatch1( fn, this._receiverAt( i ), progressValue ); + this._popContext(); + if( ret === errorObj ) { + if( ret.e != null && + ret.e.name === "StopProgressPropagation" ) { + ret.e.__handled = true; + } + else { + promise._attachExtraTrace( ret.e ); + async.invoke( promise._progress, promise, ret.e ); + } + } + else if( isPromise( ret ) ) { + ret._then( promise._progress, null, null, promise, void 0, + this._progress ); + } + else { + async.invoke( promise._progress, promise, ret ); + } + } + else { + async.invoke( promise._progress, promise, ret ); + } + } +}; + +var contextStack = []; +method._peekContext = function Promise$_peekContext() { + var lastIndex = contextStack.length - 1; + if( lastIndex >= 0 ) { + return contextStack[ lastIndex ]; + } + return void 0; + +}; + +method._pushContext = function Promise$_pushContext() { + if( !longStackTraces ) return; + contextStack.push( this ); +}; + +method._popContext = function Promise$_popContext() { + if( !longStackTraces ) return; + contextStack.pop(); +}; + + +Promise._all = +function Promise$_All( promises, PromiseArray, caller ) { + ASSERT(((typeof PromiseArray) === "function"), + "typeof PromiseArray === \u0022function\u0022"); + if( isPromise( promises ) || + isArray( promises ) ) { + + return new PromiseArray( promises, + typeof caller === "function" + ? caller + : Promise$_All + ); + } + throw new TypeError("expecting an array or a promise"); +}; + + +if( !CapturedTrace.isSupported() ) { + Promise.longStackTraces = void 0; + CapturedTrace.possiblyUnhandledRejection = void 0; + Promise.onPossiblyUnhandledRejection = void 0; + longStackTraces = false; +} + +return Promise;})(); + + + +var PromiseArray = (function() { + +function nullToUndefined( val ) { + return val === null + ? void 0 + : val; +} + +var hasOwn = {}.hasOwnProperty; +var empty = []; + +function isPromise( obj ) { + if( typeof obj !== "object" ) return false; + return obj instanceof Promise; +} + +var Arr = Array; +var isArray = Arr.isArray || function( obj ) { + return obj instanceof Arr; +}; + +function PromiseArray( values, caller ) { + this._values = values; + this._resolver = Promise.pending( caller ); + this._length = 0; + this._totalResolved = 0; + this._init( void 0, empty ); +} +var method = PromiseArray.prototype; + +method.length = function PromiseArray$length() { + return this._length; +}; + +method.promise = function PromiseArray$promise() { + return this._resolver.promise; +}; + + +method._init = function PromiseArray$_init( _, fulfillValueIfEmpty ) { + var values = this._values; + if( isPromise( values ) ) { + if( values.isPending() ) { + values._then( + this._init, + this._reject, + void 0, + this, + fulfillValueIfEmpty, + this.constructor + ); + return; + } + else if( values.isRejected() ) { + this._reject( values._resolvedValue ); + return; + } + else { + values = values._resolvedValue; + if( !isArray( values ) ) { + this._fulfill( nullToUndefined( fulfillValueIfEmpty ) ); + return; + } + this._values = values; + } + + } + if( !values.length ) { + this._fulfill( nullToUndefined( fulfillValueIfEmpty ) ); + return; + } + var len = values.length; + var newLen = len; + var newValues = new Array( len ); + for( var i = 0; i < len; ++i ) { + var promise = values[i]; + + if( promise === void 0 && !hasOwn.call( values, i ) ) { + newLen--; + continue; + } + + promise = Promise.cast( promise ); + + promise._then( + this._promiseFulfilled, + this._promiseRejected, + this._promiseProgressed, + + this, Integer.get( i ), this.constructor + + + + ); + newValues[i] = promise; + } + this._values = newValues; + this._length = newLen; +}; + +method._isResolved = function PromiseArray$_isResolved() { + return this._values === null; +}; + +method._fulfill = function PromiseArray$_fulfill( value ) { + ASSERT((! this._isResolved()), + "!this._isResolved()"); + this._values = null; + this._resolver.fulfill( value ); +}; + +method._reject = function PromiseArray$_reject( reason ) { + ASSERT((! this._isResolved()), + "!this._isResolved()"); + this._values = null; + this._resolver.reject( reason ); +}; + +method._promiseProgressed = +function PromiseArray$_promiseProgressed( progressValue, index ) { + if( this._isResolved() ) return; + ASSERT(isArray(this._values), + "isArray( this._values )"); + + this._resolver.progress({ + index: index.valueOf(), + value: progressValue + }); +}; + +method._promiseFulfilled = +function PromiseArray$_promiseFulfilled( value, index ) { + if( this._isResolved() ) return; + ASSERT(isArray(this._values), + "isArray( this._values )"); + ASSERT((index instanceof Integer), + "index instanceof Integer"); + this._values[ index.valueOf() ] = value; + var totalResolved = ++this._totalResolved; + if( totalResolved >= this._length ) { + this._fulfill( this._values ); + } +}; + +method._promiseRejected = +function PromiseArray$_promiseRejected( reason ) { + if( this._isResolved() ) return; + ASSERT(isArray(this._values), + "isArray( this._values )"); + this._totalResolved++; + this._reject( reason ); +}; + +function Integer( value ) { + this._value = value; +} + +Integer.prototype.valueOf = function Integer$valueOf() { + return this._value; +}; +Integer.get = function Integer$get( i ) { + if( i < 256 ) { + return ints[i]; + } + return new Integer(i); +}; + +var ints = []; +for( var i = 0; i < 256; ++i ) { + ints.push( new Integer(i) ); +} + + + + + +return PromiseArray;})(); +var SettledPromiseArray = (function() { +function SettledPromiseArray( values, caller ) { + this.constructor$( values, caller ); +} +var method = inherits( SettledPromiseArray, PromiseArray ); + + + +method._promiseResolved = +function SettledPromiseArray$_promiseResolved( index, inspection ) { + ASSERT(((typeof index) === "number"), + "typeof index === \u0022number\u0022"); + this._values[ index ] = inspection; + var totalResolved = ++this._totalResolved; + if( totalResolved >= this._length ) { + this._fulfill( this._values ); + } +}; + +var throwawayPromise = new Promise()._setTrace(); +method._promiseFulfilled = +function SettledPromiseArray$_promiseFulfilled( value, index ) { + if( this._isResolved() ) return; + var ret = new PromiseInspection( throwawayPromise ); + ret._bitField = 268435456; + ret._resolvedValue = value; + this._promiseResolved( index.valueOf(), ret ); +}; +method._promiseRejected = +function SettledPromiseArray$_promiseRejected( reason, index ) { + if( this._isResolved() ) return; + var ret = new PromiseInspection( throwawayPromise ); + ret._bitField = 134217728; + ret._resolvedValue = reason; + this._promiseResolved( index.valueOf(), ret ); +}; + +return SettledPromiseArray;})(); +var AnyPromiseArray = (function() { +function AnyPromiseArray( values, caller ) { + this.constructor$( values, caller ); +} +var method = inherits( AnyPromiseArray, PromiseArray ); + +method._init = function AnyPromiseArray$_init() { + this._init$( void 0, null ); +}; + +method._promiseFulfilled = +function AnyPromiseArray$_promiseFulfilled( value ) { + if( this._isResolved() ) return; + ++this._totalResolved; + this._fulfill( value ); + +}; +method._promiseRejected = +function AnyPromiseArray$_promiseRejected( reason, index ) { + if( this._isResolved() ) return; + var totalResolved = ++this._totalResolved; + this._values[ index.valueOf() ] = reason; + if( totalResolved >= this._length ) { + this._reject( this._values ); + } + +}; + +return AnyPromiseArray;})(); +var SomePromiseArray = (function() { +function SomePromiseArray( values, caller ) { + this.constructor$( values, caller ); +} +var method = inherits( SomePromiseArray, PromiseArray ); + + + +method._init = function SomePromiseArray$_init() { + this._init$( void 0, [] ); + this._howMany = 0; + this._rejected = 0; + this._rejectionValues = new Array( this.length() ); + this._resolutionValues = new Array( this.length() ); + if( this._isResolved() ) return; + + if( this._howMany > this._canPossiblyFulfill() ) { + this._reject( [] ); + } +}; + +method._canPossiblyFulfill = +function SomePromiseArray$_canPossiblyFulfill() { + return this._totalResolved - this._rejected + + ( this.length() - this._totalResolved ); +}; + +method._promiseFulfilled = +function SomePromiseArray$_promiseFulfilled( value ) { + if( this._isResolved() ) return; + + var totalResolved = this._totalResolved; + this._resolutionValues[ totalResolved ] = value; + this._totalResolved = totalResolved + 1; + if( totalResolved + 1 === this._howMany ) { + this._resolutionValues.length = this._howMany; + this._fulfill( this._resolutionValues ); + this._resolutionValues = + this._rejectionValues = null; + } + +}; +method._promiseRejected = +function SomePromiseArray$_promiseRejected( reason ) { + if( this._isResolved() ) return; + + this._rejectionValues[ this._rejected ] = reason; + this._rejected++; + this._totalResolved++; + + if( this._howMany > this._canPossiblyFulfill() ) { + this._rejectionValues.length = this._rejected; + this._reject( this._rejectionValues ); + this._resolutionValues = + this._rejectionValues = null; + } +}; + +return SomePromiseArray;})(); +var PromiseInspection = (function() { + + +function PromiseInspection( promise ) { + this._bitField = promise._bitField; + this._resolvedValue = promise.isResolved() + ? promise._resolvedValue + : void 0; +} +var method = PromiseInspection.prototype; + +method.isFulfilled = function PromiseInspection$isFulfilled() { + return ( this._bitField & 268435456 ) > 0; +}; + +method.isRejected = function PromiseInspection$isRejected() { + return ( this._bitField & 134217728 ) > 0; +}; + +method.isPending = function PromiseInspection$isPending() { + return ( this._bitField & 402653184 ) === 0; +}; + +method.value = function PromiseInspection$value() { + if( !this.isFulfilled() ) { + throw new TypeError( + "cannot get fulfillment value of a non-fulfilled promise"); + } + return this._resolvedValue; +}; + +method.error = function PromiseInspection$error() { + if( !this.isRejected() ) { + throw new TypeError( + "cannot get rejection reason of a non-rejected promise"); + } + return this._resolvedValue; +}; + + + + +return PromiseInspection;})(); + +var PromiseResolver = (function() { + +function PromiseResolver( promise ) { + this.promise = promise; +} +var method = PromiseResolver.prototype; + +method.toString = function PromiseResolver$toString() { + return "[object PromiseResolver]"; +}; + +method.fulfill = function PromiseResolver$fulfill( value ) { + if( this.promise._tryAssumeStateOf( value, false ) ) { + return; + } + async.invoke( this.promise._fulfill, this.promise, value ); +}; + +method.reject = function PromiseResolver$reject( reason ) { + this.promise._attachExtraTrace( reason ); + async.invoke( this.promise._reject, this.promise, reason ); +}; + +method.progress = function PromiseResolver$progress( value ) { + async.invoke( this.promise._progress, this.promise, value ); +}; + +method.cancel = function PromiseResolver$cancel() { + async.invoke( this.promise.cancel, this.promise, void 0 ); +}; + +method.timeout = function PromiseResolver$timeout() { + this.reject( new TimeoutError( "timeout" ) ); +}; + +method.isResolved = function PromiseResolver$isResolved() { + return this.promise.isResolved(); +}; + +method.toJSON = function PromiseResolver$toJSON() { + return this.promise.toJSON(); +}; + + +return PromiseResolver;})(); +if( typeof module !== "undefined" && module.exports ) { + module.exports = Promise; +} +else if( typeof define === "function" && define.amd ) { + define( "Promise", [], function(){return Promise;}); +} +else { + global.Promise = Promise; +} + + +return Promise;})( + new Function("return this")(), + Function, + Array, + Error, + Object +); diff --git a/test/mocha/helpers/error.js b/test/mocha/helpers/error.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/test/mocha/helpers/error.js @@ -0,0 +1 @@ + diff --git a/test/mocha/helpers/reasons.js b/test/mocha/helpers/reasons.js new file mode 100644 index 0000000..9c24281 --- /dev/null +++ b/test/mocha/helpers/reasons.js @@ -0,0 +1,56 @@ +"use strict"; + +// This module exports some valid rejection reason factories, keyed by human-readable versions of their names. + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; + +var dummy = { dummy: "dummy" }; + +exports["`undefined`"] = function () { + return undefined; +}; + +exports["`null`"] = function () { + return null; +}; + +exports["`false`"] = function () { + return false; +}; + +exports["`0`"] = function () { + return 0; +}; + +exports["an error"] = function () { + return new Error(); +}; + +exports["an error without a stack"] = function () { + var error = new Error(); + delete error.stack; + + return error; +}; + +exports["a date"] = function () { + return new Date(); +}; + +exports["an object"] = function () { + return {}; +}; + +exports["an always-pending thenable"] = function () { + return { then: function () { } }; +}; + +exports["a fulfilled promise"] = function () { + return fulfilled(dummy); +}; + +exports["a rejected promise"] = function () { + return rejected(dummy); +}; diff --git a/test/mocha/helpers/testThreeCases.js b/test/mocha/helpers/testThreeCases.js new file mode 100644 index 0000000..6455157 --- /dev/null +++ b/test/mocha/helpers/testThreeCases.js @@ -0,0 +1,64 @@ +"use strict"; + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; +var pending = adapter.pending; + +function success(done) { + return function() { + done(); + }; +} + +function fail(done) { + return function(err) { + done(err); + }; +} + +function handlePromise(val, done) { + if (val && typeof val.then === "function") { + val.then(success(done), fail(done)); + } +} + +exports.testFulfilled = function (value, test) { + specify("already-fulfilled", function (done) { + handlePromise(test(fulfilled(value), done), done); + }); + + specify("immediately-fulfilled", function (done) { + var tuple = pending(); + handlePromise(test(tuple.promise, done), done); + tuple.fulfill(value); + }); + + specify("eventually-fulfilled", function (done) { + var tuple = pending(); + handlePromise(test(tuple.promise, done), done); + setTimeout(function () { + tuple.fulfill(value); + }, 1); + }); +}; + +exports.testRejected = function (reason, test) { + specify("already-rejected", function (done) { + handlePromise(test(rejected(reason), done), done); + }); + + specify("immediately-rejected", function (done) { + var tuple = pending(); + handlePromise(test(tuple.promise, done), done); + tuple.reject(reason); + }); + + specify("eventually-rejected", function (done) { + var tuple = pending(); + handlePromise(test(tuple.promise, done), done); + setTimeout(function () { + tuple.reject(reason); + }, 1); + }); +}; diff --git a/test/mocha/helpers/thenables.js b/test/mocha/helpers/thenables.js new file mode 100644 index 0000000..4028797 --- /dev/null +++ b/test/mocha/helpers/thenables.js @@ -0,0 +1,146 @@ +"use strict"; + +var adapter = global.adapter; +var fulfilled = adapter.fulfilled; +var rejected = adapter.rejected; +var pending = adapter.pending; + +var other = { other: "other" }; // a value we don't want to be strict equal to + +exports.fulfilled = { + "a synchronously-fulfilled custom thenable": function (value) { + return { + then: function (onFulfilled) { + onFulfilled(value); + } + }; + }, + + "an asynchronously-fulfilled custom thenable": function (value) { + return { + then: function (onFulfilled) { + setTimeout(function () { + onFulfilled(value); + }, 1); + } + }; + }, + + "a synchronously-fulfilled one-time thenable": function (value) { + var numberOfTimesThenRetrieved = 0; + var ret = Object.create(null, { + then: { + get: function () { + + if (numberOfTimesThenRetrieved === 0) { + ++numberOfTimesThenRetrieved; + return function (onFulfilled) { + onFulfilled(value); + }; + } + return null; + } + } + }); + return ret; + }, + + "a thenable that tries to fulfill twice": function (value) { + return { + then: function (onFulfilled) { + onFulfilled(value); + onFulfilled(other); + } + }; + }, + + "a thenable that fulfills but then throws": function (value) { + return { + then: function (onFulfilled) { + onFulfilled(value); + throw other; + } + }; + }, + + "an already-fulfilled promise": function (value) { + return fulfilled(value); + }, + + "an eventually-fulfilled promise": function (value) { + var tuple = pending(); + setTimeout(function () { + tuple.fulfill(value); + }, 1); + return tuple.promise; + } +}; + +exports.rejected = { + "a synchronously-rejected custom thenable": function (reason) { + return { + then: function (onFulfilled, onRejected) { + onRejected(reason); + } + }; + }, + + "an asynchronously-rejected custom thenable": function (reason) { + return { + then: function (onFulfilled, onRejected) { + setTimeout(function () { + onRejected(reason); + }, 1); + } + }; + }, + + "a synchronously-rejected one-time thenable": function (reason) { + var numberOfTimesThenRetrieved = 0; + return Object.create(null, { + then: { + get: function () { + if (numberOfTimesThenRetrieved === 0) { + ++numberOfTimesThenRetrieved; + return function (onFulfilled, onRejected) { + onRejected(reason); + }; + } + return null; + } + } + }); + }, + + "a thenable that immediately throws in `then`": function (reason) { + return { + then: function () { + throw reason; + } + }; + }, + + "an object with a throwing `then` accessor": function (reason) { + return Object.create(null, { + then: { + get: function () { + throw reason; + } + } + }); + }, + + "an already-rejected promise": function (reason) { + var ret = rejected(reason); + ret.caught(function(){}); + return ret; + }, + + "an eventually-rejected promise": function (reason) { + var tuple = pending(); + setTimeout(function () { + tuple.reject(reason); + }, 1); + return tuple.promise; + } +}; diff --git a/test/mocha/helpers/util.js b/test/mocha/helpers/util.js new file mode 100644 index 0000000..22ece67 --- /dev/null +++ b/test/mocha/helpers/util.js @@ -0,0 +1,280 @@ +var assert = require("assert"); +var token = {}; +module.exports = { + awaitGlobalException: function(fn) { + function replaceListeners(by) { + var single = typeof by === "function"; + if (process.title === "browser") { + var original = window.onerror; + window.onerror = single ? function(message, file, line, column, e) { + return by(e); + } : by[0]; + return [original]; + } else { + var original = process.listeners("uncaughtException"); + process.removeAllListeners("uncaughtException"); + if (single) by = [by]; + by.forEach(function(listener) { + process.on("uncaughtException", listener); + }); + return original; + } + } + return new Promise(function(resolve, reject) { + var listeners = replaceListeners(function(e) { + var err; + var ret; + try { + ret = fn(e); + } catch (e) { + err = e; + } + if (!err && ret === false) return; + replaceListeners(listeners); + Promise.delay(1).then(function() { + if (err) reject(err); + resolve(); + }); + }); + }); + }, + + awaitLateQueue: function(fn) { + return new Promise(function(res, rej) { + Promise._async.invokeLater(function() { + try { + var result = fn(); + res(result); + } catch(e) { + rej(e); + } + }, null, null); + }); + }, + + awaitProcessExit: function(fn) { + if (typeof process !== "undefined" && typeof process.execPath === "string") { + var exit; + return new Promise(function(resolve, reject) { + exit = process.exit; + process.exit = function(code) { + try { + assert(code != 0); + fn(); + resolve(); + } catch (e) { + reject(e); + } + }; + }).lastly(function() { + process.exit = exit; + }); + } else { + return Promise.delay(1); + } + }, + + addDeferred: function(Promise) { + Promise.defer = Promise.pending = function() { + var ret = {}; + ret.promise = new Promise(function(resolve, reject) { + ret.resolve = ret.fulfill = resolve; + ret.reject = reject; + }); + return ret; + }; + return Promise; + }, + + returnToken: function() { + return token; + }, + + assertToken: function(val) { + assert.strictEqual(token, val); + }, + + getSpy: function() { + var resolve, reject; + var promise = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + var ret = function(fn) { + ret.callback = fn; + return ret.node; + }; + ret.promise = promise; + ret.node = function() { + try { + ret.callback.apply(this, arguments); + resolve(); + } catch (e) { + reject(e); + } + }; + return ret; + }, + + awaitDomainException: function(onError, fn) { + var domain; + var resolve, reject; + var promise = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + domain = require('domain').create(); + domain.on("error", function(e) { + try { + onError(e); + resolve(); + } catch (err) { + reject(err); + } + }); + domain.run(fn); + return promise; + }, + + onUnhandledFail: function(testFunction) { + Promise._unhandledRejectionClear(); + return new Promise(function(resolve, reject) { + var err = new Error("Reporting handled rejection as unhandled from: " + + testFunction); + Promise.onPossiblyUnhandledRejection(function() { + reject(err); + }); + Promise.delay(150).then(function() { + Promise._unhandledRejectionCheck(); + resolve(); + }); + }).lastly(function() { + Promise.onPossiblyUnhandledRejection(null); + }); + }, + + onUnhandledSucceed: function(testAgainst, count) { + return new Promise(function(resolveTest, reject) { + var total = typeof count === "number" ? count : 1; + var cur = 0; + + function resolve(e) { + cur++; + if (cur >= total) { + resolveTest(e); + } + } + + Promise.onPossiblyUnhandledRejection(function(e){ + if (testAgainst !== undefined) { + try { + if (typeof testAgainst === "function") { + assert(testAgainst(e)); + } + else { + assert.equal(testAgainst, e); + } + resolve(e); + } + catch (e) { + reject(e); + } + } else { + resolve(e); + } + }); + Promise.delay(50).then(function() { + Promise._unhandledRejectionCheck(); + return Promise.delay(1); + }).then(function() { + var message = "Expected onPossiblyUnhandledRejection to be called " + + total + " times but it was only called " + cur + " times"; + reject(new Error(message)); + }); + }).lastly(function() { + Promise.onPossiblyUnhandledRejection(null); + }); + + }, + + //Used in expressions like: onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + //If strict mode is supported NFEs work, if it is not, NFEs don't work but arguments.callee does + isStrictModeSupported: (function() { + try { + new Function("'use strict'; with({});"); + return false; + } + catch (e) { + return true; + } + })(), + + noop: function(v) { + return v; + }, + + isSubset: function(subset, superset) { + var i, subsetLen; + + subsetLen = subset.length; + + if (subsetLen > superset.length) { + return false; + } + + for(i = 0; i -1; + }, + + fakeResolved: function(val) { + return { + then: function(callback) { + return fakeResolved(callback ? callback(val) : val); + } + }; + }, + + fakeRejected: function(reason) { + return { + then: function(callback, errback) { + return errback ? fakeResolved(errback(reason)) : fakeRejected(reason); + } + }; + }, + + assertFulfilled: function(p, v) { + assert.strictEqual(p.value(), v); + }, + + assertRejected: function(p, v) { + assert.strictEqual(p.error(), v); + }, + + ecmaScript6Collections: (typeof Set === "function" && + typeof Symbol !== "undefined" && + Symbol.iterator && + typeof ((new Set())[Symbol.iterator]().next) === "function"), + + ecmaScript5: (function() {"use strict" + return this === undefined; + })(), + isNodeJS: typeof process !== "undefined" && typeof process.execPath === "string" +}; + +if (module.exports.isNodeJS) { + var version = process.versions.node.split(".").map(Number); + module.exports.isRecentNode = version[0] > 0; + module.exports.isOldNode = !module.exports.isRecentNode; +} else { + module.exports.isOldNode = false; + module.exports.isRecentNode = false; +} diff --git a/test/mocha/is.js b/test/mocha/is.js new file mode 100644 index 0000000..3987402 --- /dev/null +++ b/test/mocha/is.js @@ -0,0 +1,15 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +describe("Promise.is", function() { + it("should return true for trusted promise", function() { + assert.strictEqual(Promise.is(new Promise(function(){})), true); + }); + it("should return false for untrusted promise", function() { + assert.strictEqual(Promise.is({ + then: function() {} + }), false); + }); +}); diff --git a/test/mocha/join.js b/test/mocha/join.js new file mode 100644 index 0000000..08ec62f --- /dev/null +++ b/test/mocha/join.js @@ -0,0 +1,119 @@ +"use strict"; +/* +Based on When.js tests + +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +describe("Promise.join-test", function () { + + + + specify("should resolve empty input", function() { + return Promise.join().then( + function(result) { + assert.deepEqual(result, []); + }, + assert.fail + ); + }); + + specify("should join values", function() { + return Promise.join(1, 2, 3).then( + function(results) { + assert.deepEqual(results, [1, 2, 3]); + }, + assert.fail + ); + }); + + specify("should join promises array", function() { + return Promise.join(Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)).then( + function(results) { + assert.deepEqual(results, [1, 2, 3]); + }, + assert.fail + ); + }); + + specify("should join mixed array", function() { + return Promise.join(Promise.resolve(1), 2, Promise.resolve(3), 4).then( + function(results) { + assert.deepEqual(results, [1, 2, 3, 4]); + }, + assert.fail + ); + }); + + specify("should reject if any input promise rejects", function() { + return Promise.join(Promise.resolve(1), Promise.reject(2), Promise.resolve(3)).then( + assert.fail, + function(err) { + assert.deepEqual(err, 2); + } + ); + }); + + specify("should call last argument as a spread function", function() { + return Promise.join(Promise.resolve(1), Promise.resolve(2), Promise.resolve(3), function(a, b, c) { + assert(a === 1); + assert(b === 2); + assert(c === 3); + }); + }); + + + specify("gh-227", function() { + function a() { + return Promise.join(Promise.resolve(1), function () { + throw new Error(); + }); + } + + return a().then(assert.fail, function(e) {}); + }); + + specify("should not pass the callback as argument, <5 arguments", function() { + return Promise.join(1, 2, 3, function() { + assert.strictEqual(arguments.length, 3); + }); + }); + + specify("should not pass the callback as argument >5 arguments", function() { + return Promise.join(1, 2, 3, 4, 5, 6, 7, function() { + assert.strictEqual(arguments.length, 7); + }); + }); + + specify("should ensure asynchronity", function() { + var sync = false; + Promise.join(Promise.resolve(1), Promise.resolve(2), function() { + sync = true; + }); + assert.strictEqual(false, sync); + }) +}); diff --git a/test/mocha/late_buffer_safety.js b/test/mocha/late_buffer_safety.js new file mode 100644 index 0000000..d197745 --- /dev/null +++ b/test/mocha/late_buffer_safety.js @@ -0,0 +1,37 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var isNodeJS = testUtils.isNodeJS; + +function async(cb){ + return Promise.resolve().nodeify(cb); +} + +if (isNodeJS) { + describe("Late buffer", function() { + specify("shouldn't stop at first error but continue consumption until everything is consumed", function(){ + var length = 10; + var l = length; + var a = 0; + while (l--){ + async(function(){ + throw (a++); + }); + } + var errs = []; + return testUtils.awaitGlobalException(function(e) { + errs.push(e); + if (errs.length === length) { + var a = []; + for (var i = 0, len = length; i < len; ++i) { + a[i] = i; + } + assert.deepEqual(a, errs); + } else { + return false; + } + }); + }); + }); +} diff --git a/test/mocha/long_stack_traces.js b/test/mocha/long_stack_traces.js new file mode 100644 index 0000000..346ce9a --- /dev/null +++ b/test/mocha/long_stack_traces.js @@ -0,0 +1,537 @@ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var assertLongTrace = require("./helpers/assert_long_trace.js"); +var nodeVersion = typeof process !== "undefined" && + typeof process.version === "string" + ? process.version.replace(/[^0-9.]/g, "").split(".").map(Number) + : [-1, -1, -1]; + +// Node's V8 captureStackTrace is completely broken - it returns different +// results on different runs and sometimes causes this test to fail +if (!Promise.hasLongStackTraces() || testUtils.isOldNode) return; + +describe(".then as context", function() { + it("1 level", function() { + return Promise.resolve().then(function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels", function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + throw new Error(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); + it("1 level using promise reject with no stack", function() { + return Promise.resolve().then(function() { + var e; + try {throw new Error()} catch (err){e = err;} + e.stack; + delete e.stack; + return Promise.reject(e); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1, 1]); + }); + }); + it("4 levels using promise reject", function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + var e; + try {throw new Error()} catch (err){e = err;} + return Promise.reject(e); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); + it("Circular 1 level", function() { + var i = 0; + return (function circle() { + if (i++ > 5) throw new Error() + return Promise.resolve().then(circle); + })().then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("Circular 4 levels", function() { + var i = 0; + return (function circle() { + if (i++ > 5) throw new Error() + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(circle); + }); + }); + }); + })().then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); + + it("followers unaffected", function() { + return Promise.resolve().then(function() { + return new Promise(function(res) { + res(Promise.delay(13).then(function() { + return new Promise(function(res) { + res(Promise.delay(13).then(function() { + throw new Error(); + })); + }); + })); + }).then(assert.fail, function(e) { + assertLongTrace(e, 5 + 1, [1, 1, 1, 1, 1]); + throw new Error(); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 2 + 1, [1, 1]); + }); + }); + + it("3 distinct episodes of circularity with unique frames in between", function() { + var i = 0; + var j = 0; + var k = 0; + + function circle1() { + if (i++ > 5) return u1_1(); + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(circle1); + }); + }); + }); + } + + function circle2() { + if (j++ > 5) return u2_1(); + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(circle2); + }); + }); + }); + } + + function circle3() { + if (k++ > 5) return u3_1(); + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(circle3); + }); + }); + }); + } + + function u1_1() { + return Promise.resolve().then(u1_2); + } + + function u1_2() { + return Promise.resolve().then(circle2); + } + + function u2_1() { + return Promise.resolve().then(u2_2); + } + + function u2_2() { + return Promise.resolve().then(circle3); + } + + function u3_1() { + return Promise.resolve().then(u3_2); + } + + function u3_2() { + return Promise.resolve().then(function() { + throw new Error("The error"); + }); + } + + return circle1().then(assert.fail, function(e) { + assertLongTrace(e, + 1 + 1 + 1 + 1, + [ + 1, 1, 2, 1, 1, + 1, 1, 2, 1, 1, + 1, 1, 2, 1, 1 + ]); + }); + }); +}); + +describe(".spread as context", function() { + it("1 level", function() { + return Promise.resolve([]).spread(function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels", function() { + return Promise.resolve([]).spread(function() { + return Promise.resolve([]).spread(function() { + return Promise.resolve([]).spread(function() { + return Promise.resolve([]).spread(function() { + throw new Error(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); +}); + +describe("constructor as context", function() { + it("0 level", function() { + return new Promise(function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1, []); + }); + }); + it("1 level", function() { + return new Promise(function(res) { + res(new Promise(function() { + throw new Error(); + })) + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [2]); + }); + }); + it("0 level, no stack property", function() { + return new Promise(function(_ ,rej) { + var e = new Error(); + e.stack; + delete e.stack; + rej(e); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1, [1]); + }); + }); + it("1 level, no stack property", function() { + return new Promise(function(res) { + res(new Promise(function(_, rej) { + var e = new Error(); + e.stack; + delete e.stack; + rej(e); + })) + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1, 1]); + }); + }); + + it("4 levels", function() { + return new Promise(function(res) { + res(new Promise(function(res) { + res(new Promise(function(res) { + res(new Promise(function(res) { + res(new Promise(function(res) { + throw new Error(); + })); + })); + })); + })); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [2, 1, 1, 1]); + }); + }); +}); + +describe(".join as context", function() { + it("0 level", function() { + var err; + try {throw new Error(); } catch(e) {err = e;}; + return Promise.join(1, 2, Promise.reject(err), function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 0 + 1, []); + }); + }); + it("1 level", function() { + return Promise.join(1, 2, 3, function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels", function() { + return Promise.join(1, 2, 3, function() { + return Promise.join(1, 2, 3, function() { + return Promise.join(1, 2, 3, function() { + return Promise.join(1, 2, 3, function() { + throw new Error(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); +}); + +describe(".map as context", function() { + it("1 level", function() { + return Promise.map([1,2,3], function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels", function() { + return Promise.map([1,2,3], function() { + return Promise.map([1,2,3], function() { + return Promise.map([1,2,3], function() { + return Promise.map([1,2,3], function() { + throw new Error(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); +}); + +describe(".reduce as context", function() { + it("1 level", function() { + return Promise.reduce([1,2,3], function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels", function() { + return Promise.reduce([1,2,3], function() { + return Promise.reduce([1,2,3], function() { + return Promise.reduce([1,2,3], function() { + return Promise.reduce([1,2,3], function() { + throw new Error(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); +}); + +describe(".method as context", function() { + it("1 level", function() { + return Promise.method(function() { + throw new Error(); + })().then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels", function() { + var second = Promise.method(function() { + return third(); + }); + var third = Promise.method(function() { + return fourth(); + }); + var fourth = Promise.method(function() { + throw new Error(); + }); + + return Promise.method(function() { + return second(); + })().then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [[1,2], 1, 1, 1]); + }); + }); +}); + +describe(".try as context", function() { + it("1 level", function() { + return Promise.attempt(function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + + it("4 levels", function() { + return Promise.attempt(function() { + return Promise.attempt(function() { + return Promise.attempt(function() { + return Promise.attempt(function() { + throw new Error(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); +}); + +describe(".using as context", function() { + it("0 level", function() { + var err; + try {throw new Error(); } catch(e) {err = e}; + return Promise.using(1, 2, Promise.reject(err), function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 0 + 1, []); + }); + }); + it("1 level", function() { + return Promise.using(1, 2, 3, function() { + throw new Error(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels", function() { + return Promise.using(1, 2, 3, function() { + return Promise.using(1, 2, 3, function() { + return Promise.using(1, 2, 3, function() { + return Promise.using(1, 2, 3, function() { + throw new Error(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); +}); + +describe("Long stack traces from thenable rejections", function() { + var es5 = (function(){"use strict"; return this})() === undefined; + // Todo, for 100% coverage thenables should be tested with every + // feature, not just then + var syncRej = function() { + return { + then: function(_, rej) { + rej(new Error()); + } + }; + }; + var asyncRej = function() { + return { + then: function(_, rej) { + setTimeout(function() { + rej(new Error()); + }, 1); + } + }; + }; + var throwRej = function() { + return { + then: function(_, rej) { + throw(new Error()); + } + }; + }; + var thenGetRej = function() { + var ret = {}; + Object.defineProperty(ret, "then", { + get: function() { + throw new Error() + } + }); + return ret; + }; + it("1 level sync reject", function() { + return Promise.resolve().then(function() { + return syncRej(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1+1, [1]); + }); + }); + it("4 levels sync reject", function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return syncRej(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); + it("1 level async reject", function() { + return Promise.resolve().then(function() { + return asyncRej(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels async reject", function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return asyncRej(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); + it("1 level throw", function() { + return Promise.resolve().then(function() { + return throwRej(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels throw", function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return throwRej(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); + it("1 level getter throw", function() { + return Promise.resolve().then(function() { + return thenGetRej(); + }).then(assert.fail, function(e) { + assertLongTrace(e, 1 + 1, [1]); + }); + }); + it("4 levels getter throw", function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return Promise.resolve().then(function() { + return thenGetRej(); + }); + }); + }); + }).then(assert.fail, function(e) { + assertLongTrace(e, 4 + 1, [1, 1, 1, 1]); + }); + }); +}); diff --git a/test/mocha/map.js b/test/mocha/map.js new file mode 100644 index 0000000..2387290 --- /dev/null +++ b/test/mocha/map.js @@ -0,0 +1,295 @@ +"use strict"; +/* +Based on When.js tests + +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +describe("Promise.map-test", function () { + + function mapper(val) { + return val * 2; + } + + function deferredMapper(val) { + return Promise.delay(1, mapper(val)); + } + + specify("should map input values array", function() { + var input = [1, 2, 3]; + return Promise.map(input, mapper).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should map input promises array", function() { + var input = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + return Promise.map(input, mapper).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should map mixed input array", function() { + var input = [1, Promise.resolve(2), 3]; + return Promise.map(input, mapper).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should map input when mapper returns a promise", function() { + var input = [1,2,3]; + return Promise.map(input, deferredMapper).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should accept a promise for an array", function() { + return Promise.map(Promise.resolve([1, Promise.resolve(2), 3]), mapper).then( + function(result) { + assert.deepEqual(result, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should throw a TypeError when input promise does not resolve to an array", function() { + return Promise.map(Promise.resolve(123), mapper).caught(TypeError, function(e){ + }); + }); + + specify("should map input promises when mapper returns a promise", function() { + var input = [Promise.resolve(1),Promise.resolve(2),Promise.resolve(3)]; + return Promise.map(input, mapper).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should reject when input contains rejection", function() { + var input = [Promise.resolve(1), Promise.reject(2), Promise.resolve(3)]; + return Promise.map(input, mapper).then( + assert.fail, + function(result) { + assert(result === 2); + } + ); + }); + + specify("should call mapper asynchronously on values array", function() { + var calls = 0; + function mapper(val) { + calls++; + } + + var input = [1, 2, 3]; + var p = Promise.map(input, mapper); + assert(calls === 0); + return p.then(function() { + assert(calls === 3); + }); + }); + + specify("should call mapper asynchronously on promises array", function() { + var calls = 0; + function mapper(val) { + calls++; + } + + var input = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + var p = Promise.map(input, mapper); + assert(calls === 0); + return p.then(function() { + assert(calls === 3); + }); + }); + + specify("should call mapper asynchronously on mixed array", function() { + var calls = 0; + function mapper(val) { + calls++; + } + + var input = [1, Promise.resolve(2), 3]; + var p = Promise.map(input, mapper); + assert(calls === 0); + return p.then(function() { + assert(calls === 3); + }); + }); +}); + +describe("Promise.map-test with concurrency", function () { + + var concurrency = {concurrency: 2}; + + function mapper(val) { + return val * 2; + } + + function deferredMapper(val) { + return Promise.delay(1, mapper(val)); + } + + specify("should map input values array with concurrency", function() { + var input = [1, 2, 3]; + return Promise.map(input, mapper, concurrency).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should map input promises array with concurrency", function() { + var input = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + return Promise.map(input, mapper, concurrency).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should map mixed input array with concurrency", function() { + var input = [1, Promise.resolve(2), 3]; + return Promise.map(input, mapper, concurrency).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should map input when mapper returns a promise with concurrency", function() { + var input = [1,2,3]; + return Promise.map(input, deferredMapper, concurrency).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should accept a promise for an array with concurrency", function() { + return Promise.map(Promise.resolve([1, Promise.resolve(2), 3]), mapper, concurrency).then( + function(result) { + assert.deepEqual(result, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should resolve to empty array when input promise does not resolve to an array with concurrency", function() { + return Promise.map(Promise.resolve(123), mapper, concurrency).caught(TypeError, function(e){ + }); + }); + + specify("should map input promises when mapper returns a promise with concurrency", function() { + var input = [Promise.resolve(1),Promise.resolve(2),Promise.resolve(3)]; + return Promise.map(input, mapper, concurrency).then( + function(results) { + assert.deepEqual(results, [2,4,6]); + }, + assert.fail + ); + }); + + specify("should reject when input contains rejection with concurrency", function() { + var input = [Promise.resolve(1), Promise.reject(2), Promise.resolve(3)]; + return Promise.map(input, mapper, concurrency).then( + assert.fail, + function(result) { + assert(result === 2); + } + ); + }); + + specify("should not have more than {concurrency} promises in flight", function() { + var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + var b = []; + var now = Date.now(); + + var immediates = []; + function immediate(index) { + var resolve; + var ret = new Promise(function(){resolve = arguments[0]}); + immediates.push([ret, resolve, index]); + return ret; + } + + var lates = []; + function late(index) { + var resolve; + var ret = new Promise(function(){resolve = arguments[0]}); + lates.push([ret, resolve, index]); + return ret; + } + + + function promiseByIndex(index) { + return index < 5 ? immediate(index) : late(index); + } + + function resolve(item) { + item[1](item[2]); + } + + var ret1 = Promise.map(array, function(value, index) { + return promiseByIndex(index).then(function() { + b.push(value); + }); + }, {concurrency: 5}); + + var ret2 = Promise.delay(100).then(function() { + assert.strictEqual(0, b.length); + immediates.forEach(resolve); + return immediates.map(function(item){return item[0]}); + }).delay(100).then(function() { + assert.deepEqual(b, [0, 1, 2, 3, 4]); + lates.forEach(resolve); + }).delay(100).then(function() { + assert.deepEqual(b, [0, 1, 2, 3, 4, 10, 9, 8, 7, 6 ]); + lates.forEach(resolve); + }).thenReturn(ret1).then(function() { + assert.deepEqual(b, [0, 1, 2, 3, 4, 10, 9, 8, 7, 6, 5]); + }); + return Promise.all([ret1, ret2]); + }); +}); diff --git a/test/mocha/method.js b/test/mocha/method.js new file mode 100644 index 0000000..0fa7fe6 --- /dev/null +++ b/test/mocha/method.js @@ -0,0 +1,134 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +var obj = {}; +var error = new Error(); +var thrower = Promise.method(function() { + throw error; +});; + +var identity = Promise.method(function(val) { + return val; +}); + +var array = Promise.method(function() { + return [].slice.call(arguments); +}); + +var receiver = Promise.method(function() { + return this; +}); + + + +describe("Promise.method", function(){ + specify("should reject when the function throws", function() { + var async = false; + var ret = thrower().then(assert.fail, function(e) { + assert(async); + assert(e === error); + }); + async = true; + return ret; + }); + specify("should throw when the function is not a function", function() { + try { + Promise.method(null); + } + catch (e) { + assert(e instanceof TypeError); + return; + } + assert.fail(); + }); + specify("should call the function with the given receiver", function(){ + var async = false; + var ret = receiver.call(obj).then(function(val) { + assert(async); + assert(val === obj); + }, assert.fail); + async = true; + return ret; + }); + specify("should call the function with the given value", function(){ + var async = false; + var ret = identity(obj).then(function(val) { + assert(async); + assert(val === obj); + }, assert.fail); + async = true; + return ret; + }); + specify("should apply the function if given value is array", function(){ + var async = false; + var ret = array(1, 2, 3).then(function(val) { + assert(async); + assert.deepEqual(val, [1,2,3]); + }, assert.fail); + async = true; + return ret; + }); + + specify("should unwrap returned promise", function(){ + var d = Promise.defer(); + + var ret = Promise.method(function(){ + return d.promise; + })().then(function(v){ + assert(v === 3); + }) + + setTimeout(function(){ + d.fulfill(3); + }, 1); + return ret; + }); + specify("should unwrap returned thenable", function(){ + + return Promise.method(function(){ + return { + then: function(f, v) { + f(3); + } + } + })().then(function(v){ + assert(v === 3); + }); + }); + + specify("should unwrap a following promise", function() { + var resolveF; + var f = new Promise(function() { + resolveF = arguments[0]; + }); + var v = new Promise(function(f) { + setTimeout(function() { + f(3); + }, 1); + }); + resolveF(v); + return Promise.method(function(){ + return f; + })().then(function(v){ + assert(v === 3); + }); + }); + + specify("zero arguments length should remain zero", function() { + return Promise.method(function(){ + assert(arguments.length === 0); + })(); + }); + specify("should retain binding from returned promise", function() { + var THIS = {}; + return Promise.method(function() { + return Promise.bind(THIS, 1); + })().then(function(value) { + assert.strictEqual(THIS, this); + assert.strictEqual(1, value); + }); + }); +}); diff --git a/test/mocha/monitoring.js b/test/mocha/monitoring.js new file mode 100644 index 0000000..0653ed8 --- /dev/null +++ b/test/mocha/monitoring.js @@ -0,0 +1,183 @@ +var assert = require("assert"); +var util = require("../../js/debug/util"); + +describe("monitoring: promise lifecycle events subscriptions", function() { + var numCreated = 0; + var numChained = 0; + var numFulfilled = 0; + var numRejected = 0; + var on = null; + var off = null; + + before(function(){ + Promise.config({monitoring: true}); + }); + + after(function(){ + Promise.config({monitoring: false}); + }); + + beforeEach(function(){ + numCreated = 0; + numChained = 0; + numFulfilled = 0; + numRejected = 0; + }); + + function nodeOn(eventName, eventHandler) { + process.on.call(process, eventName, eventHandler); + } + function nodeOff(eventName, eventHandler) { + process.removeListener.call(process, eventName, eventHandler); + } + function browserSimpleOn(eventName, eventHandler) { + eventName = "on" + eventName.toLowerCase(); + self[eventName] = eventHandler; + } + function browserSimpleOff(eventName, eventHandler) { + eventName = "on" + eventName.toLowerCase(); + assert(self[eventName] === eventHandler); + delete self[eventName]; + } + function browserDomOn (eventName, eventHandler) { + self.addEventListener.call(self, eventName.toLowerCase(), + eventHandler); + } + function browserDomOff (eventName, eventHandler) { + self.removeEventListener.call(self, eventName.toLowerCase(), + eventHandler); + } + + function testCreated(onCreated) { + on("promiseCreated", onCreated); + var promise = new Promise(function(resolve){resolve()}); + assert(numCreated === 1); + promise = promise.then(function(){}); + assert(numCreated === 2); + off("promiseCreated", onCreated); + promise.then(function(){}); + assert(numCreated === 2); + } + + function testChained(onChained) { + on("promiseChained", onChained); + var promise = new Promise(function(resolve){resolve()}); + assert(numChained === 0); + promise = promise.then(function(){}); + assert(numChained === 1); + off("promiseChained", onChained); + promise.then(function(){}); + assert(numChained === 1); + } + + function testRejected(onRejected) { + on("promiseRejected", onRejected); + assert(numRejected === 0); + var promise = new Promise(function(resolve,reject){ + reject(); + }); + assert(numRejected === 1); + promise = promise.caught(function(resolve,reject){ + assert(numRejected === 1); + reject(); + }); + off("promiseRejected", onRejected); + return promise.caught(function(){ + assert(numRejected === 1); + }); + + } + + function testFulfilled(onFulfilled) { + on("promiseFulfilled", onFulfilled); + assert(numFulfilled === 0); + var promise = new Promise(function(resolve){resolve()}); + assert(numFulfilled === 1); + promise = promise.then(function(){}); + assert(numFulfilled === 1); + off("promiseFulfilled", onFulfilled); + promise.then(function(){}); + assert(numFulfilled === 1); + } + + describe("simple events API", function() { + + before(function() { + if (util.isNode) { + on = nodeOn; + off = nodeOff; + } else if (typeof self !== "undefined") { + on = browserSimpleOn; + off = browserSimpleOff; + } else { + assert(1===0); + } + }); + + it("promiseCreated", function () { + return testCreated(function (promise) { + assert(Promise.is(promise)); + numCreated++ + }); + }); + it("promiseChained", function () { + return testChained(function (promise, child) { + assert(Promise.is(promise)); + assert(Promise.is(child)); + numChained++; + }); + }); + it("promiseRejected", function () { + return testRejected(function (promise) { + assert(Promise.is(promise)); + numRejected++ + }); + }); + it("promiseFulfilled", function () { + return testFulfilled(function (promise) { + assert(Promise.is(promise)); + numFulfilled++ + }); + }); + }); + + if (!util.isNode) { + describe("events API", function() { + + before(function() { + on = browserDomOn; + off = browserDomOff; + }); + + it("promiseCreated", function () { + return testCreated(function (event) { + assert(event.type === "promisecreated"); + assert(Promise.is(event.detail.promise)); + numCreated++; + }); + }); + it("promiseChained", function () { + return testChained(function (event) { + assert(event.type === "promisechained"); + assert(Promise.is(event.detail.promise)); + assert(Promise.is(event.detail.child)); + numChained++; + }); + }); + it("promiseRejected", function () { + return testRejected(function (event) { + numRejected++; + assert(event.type === "promiserejected"); + assert(Promise.is(event.detail.promise)); + }); + }); + it("promiseFulfilled", function () { + return testFulfilled(function (event) { + numFulfilled++; + assert(event.type === "promisefulfilled"); + assert(Promise.is(event.detail.promise)); + }); + }); + }); + } +}); diff --git a/test/mocha/multiple-copies.js b/test/mocha/multiple-copies.js new file mode 100644 index 0000000..77ab84b --- /dev/null +++ b/test/mocha/multiple-copies.js @@ -0,0 +1,18 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +if (testUtils.isNodeJS) { + describe("multiple copies", function() { + specify("are being loaded", function() { + var a = require("../../js/debug/bluebird.js"); + Object.keys(require.cache).forEach(function(key) { + if (/debug/.test(key)) + delete require.cache[key]; + }); + var b = require("../../js/debug/bluebird.js"); + assert.notEqual(a, b); + }); + }); +} diff --git a/test/mocha/no_conflict.js b/test/mocha/no_conflict.js new file mode 100644 index 0000000..9c82c10 --- /dev/null +++ b/test/mocha/no_conflict.js @@ -0,0 +1,21 @@ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +describe("Promise.noConflict", function() { + specify("should work", function() { + var glob = typeof window !== "undefined" ? window : global; + var bluebird = Promise.noConflict(); + assert(bluebird !== Promise); + glob.Promise = null; + assert.strictEqual(bluebird, bluebird.noConflict()); + try { + delete glob.Promise; + assert.strictEqual(bluebird, bluebird.noConflict()); + } catch (e) { + assert.strictEqual(bluebird, bluebird.noConflict()); + } + glob.Promise = bluebird; + assert.strictEqual(bluebird, Promise.noConflict()); + glob.Promise = bluebird; + }) +}); diff --git a/test/mocha/nodeify.js b/test/mocha/nodeify.js new file mode 100644 index 0000000..7883209 --- /dev/null +++ b/test/mocha/nodeify.js @@ -0,0 +1,200 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var awaitGlobalException = testUtils.awaitGlobalException; +var sinon = require("sinon"); +var isNodeJS = testUtils.isNodeJS; +/* +Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ + +describe("nodeify", function () { + + it("calls back with a resolution", function () { + var spy = sinon.spy(); + Promise.resolve(10).nodeify(spy); + setTimeout(function(){ + sinon.assert.calledOnce(spy); + sinon.assert.calledWith(spy, null, 10); + }, 1); + }); + + it("calls back with an undefined resolution", function() { + var spy = sinon.spy(); + Promise.resolve().nodeify(spy); + setTimeout(function(){ + sinon.assert.calledOnce(spy); + sinon.assert.calledWithExactly(spy, null); + }, 1); + }); + + it("calls back with an error", function () { + var spy = sinon.spy(); + Promise.reject(10).nodeify(spy); + setTimeout(function(){ + sinon.assert.calledOnce(spy); + sinon.assert.calledWith(spy, 10); + }, 1); + }); + + it("forwards a promise", function () { + return Promise.resolve(10).nodeify().then(function (ten) { + assert(10 === ten); + }); + }); + + it("returns undefined when a callback is passed", function () { + return 'undefined' === typeof Promise.resolve(10).nodeify(function () {}); + }); + +}); +var getSpy = testUtils.getSpy; +if (isNodeJS) { + describe("nodeify", function () { + var h = []; + var e = new Error(); + function thrower() { + throw e; + } + + it("throws normally in the node process if the function throws", function() { + var promise = Promise.resolve(10); + var turns = 0; + process.nextTick(function(){ + turns++; + }); + promise.nodeify(thrower); + return awaitGlobalException(function(err) { + assert(err === e); + assert(turns === 1); + }); + }); + + it("always returns promise for now", function() { + return Promise.resolve(3).nodeify().then(function() { + var a = 0; + Promise.resolve(3).nodeify(function(){ + a++; + }).then(function() { + assert(1 == 1); + }); + }); + }); + + it("should spread arguments with spread option", function() { + var spy = getSpy(); + Promise.resolve([1,2,3]).nodeify(spy(function(err, a, b, c) { + assert(err === null); + assert(a === 1); + assert(b === 2); + assert(c === 3); + }), {spread: true}); + return spy.promise; + }); + + describe("promise rejected with falsy values", function() { + specify("no reason", function() { + var spy = getSpy(); + Promise.reject().nodeify(spy(function(err) { + assert.strictEqual(arguments.length, 1); + assert.strictEqual(err.cause, undefined); + })); + return spy.promise; + }); + specify("null reason", function() { + var spy = getSpy(); + Promise.reject(null).nodeify(spy(function(err) { + assert.strictEqual(arguments.length, 1); + assert.strictEqual(err.cause, null); + })); + return spy.promise; + }); + specify("nodefying a follewer promise", function() { + var spy = getSpy(); + new Promise(function(resolve, reject) { + resolve(new Promise(function(_, reject) { + setTimeout(function() { + reject(); + }, 1); + })) + }).nodeify(spy(function(err) { + assert.strictEqual(arguments.length, 1); + assert.strictEqual(err.cause, undefined); + })); + return spy.promise; + }); + specify("nodefier promise becomes follower", function() { + var spy = getSpy(); + Promise.resolve(1).then(function() { + return new Promise(function(_, reject) { + setTimeout(function() { + reject(); + }, 1); + }); + }).nodeify(spy(function(err) { + assert.strictEqual(arguments.length, 1); + assert.strictEqual(err.cause, undefined); + })); + return spy.promise; + }); + }); + it("should wrap arguments with spread option", function() { + var spy = getSpy(); + Promise.resolve([1,2,3]).nodeify(spy(function(err, a, b, c) { + assert(err === null); + assert(a === 1); + assert(b === 2); + assert(c === 3); + }), {spread: true}); + return spy.promise; + }); + + it("should work then result is not an array", function() { + var spy = getSpy(); + Promise.resolve(3).nodeify(spy(function(err, a) { + assert(err === null); + assert(a === 3); + }), {spread: true}); + return spy.promise; + }); + + it("should work if the callback throws when spread", function() { + var err = new Error(); + Promise.resolve([1,2,3]).nodeify(function(_, a) { + throw err; + }, {spread: true}); + + return awaitGlobalException(function(e) { + assert.strictEqual(err, e); + }); + }); + + it("should work if the callback throws when rejected", function() { + var err = new Error(); + Promise.reject(new Error()).nodeify(function(_, a) { + throw err; + }); + + return awaitGlobalException(function(e) { + assert.strictEqual(err, e); + }); + }); + }); +} diff --git a/test/mocha/promise_array.js b/test/mocha/promise_array.js new file mode 100644 index 0000000..39ba5fb --- /dev/null +++ b/test/mocha/promise_array.js @@ -0,0 +1,178 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +/*! + * +Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ +describe("all", function () { + it("fulfills when passed an empty array", function () { + return Promise.all([]); + }); + + if (testUtils.ecmaScript6Collections) { + it("supports iterables", function () { + return Promise.all(new Set([1, 2, 3])).then(function(v) { + assert.deepEqual([1,2,3].sort(), v.sort()); + }); + }); + } + + it("rejects after any constituent promise is rejected", function () { + var toResolve = Promise.defer(); // never resolve + var toReject = Promise.defer(); + var promises = [toResolve.promise, toReject.promise]; + var promise = Promise.all(promises); + + toReject.reject(new Error("Rejected")); + + promise.then(assert.fail, function(e){ + //Unhandled rejection + }); + + return Promise.delay(1) + .then(function () { + assert.equal(promise.isRejected(), true); + }) + .timeout(1000); + + + }); + + it("resolves foreign thenables", function () { + var normal = Promise.resolve(1); + var foreign = { then: function (f) { f(2); } }; + + return Promise.all([normal, foreign]) + .then(function (result) { + assert.deepEqual(result,[1, 2]); + }); + }); + + + it("fulfills when passed an sparse array", function () { + var toResolve = Promise.defer(); + var promises = []; + promises[0] = Promise.resolve(0); + promises[2] = toResolve.promise; + var promise = Promise.all(promises); + + toResolve.resolve(2); + + return promise.then(function (result) { + assert.deepEqual(result, [0, void 0, 2]); + }); + }); +}); + +/* +Based on When.js tests + +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ +describe("Promise.all-test", function () { + + specify("should resolve empty input", function() { + return Promise.all([]).then( + function(result) { + assert.deepEqual(result, []); + }, assert.fail + ); + }); + + specify("should resolve values array", function() { + var input = [1, 2, 3]; + return Promise.all(input).then( + function(results) { + assert.deepEqual(results, input); + }, assert.fail + ); + }); + + specify("should resolve promises array", function() { + var input = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + return Promise.all(input).then( + function(results) { + assert.deepEqual(results, [1, 2, 3]); + }, assert.fail + ); + }); + + specify("should not resolve sparse array input", function() { + var input = [, 1, , 1, 1 ]; + return Promise.all(input).then( + function(results) { + assert.deepEqual(results, [void 0, 1, void 0, 1, 1]); + }, assert.fail + ); + }); + + specify("should reject if any input promise rejects", function() { + var input = [Promise.resolve(1), Promise.reject(2), Promise.resolve(3)]; + return Promise.all(input).then( + assert.fail, + function(err) { + assert.deepEqual(err, 2); + } + ); + }); + + specify("should accept a promise for an array", function() { + var expected, input; + + expected = [1, 2, 3]; + input = Promise.resolve(expected); + + return Promise.all(input).then( + function(results) { + assert.deepEqual(results, expected); + }, assert.fail + ); + }); + + specify("should reject when input promise does not resolve to array", function() { + return Promise.all(Promise.resolve(1)).caught(TypeError, function(e){ + }); + }); + +}); diff --git a/test/mocha/promisify.js b/test/mocha/promisify.js new file mode 100644 index 0000000..87b1b76 --- /dev/null +++ b/test/mocha/promisify.js @@ -0,0 +1,1116 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +var OperationalError = Promise.OperationalError; + +var erroneusNode = function(a, b, c, cb) { + setTimeout(function(){ + cb(sentinelError); + }, 1); +}; + +var sentinel = {}; +var sentinelError = new OperationalError(); + +var successNode = function(a, b, c, cb) { + setTimeout(function(){ + cb(null, a); + }, 1); +}; + +var successNodeMultipleValues = function(a, b, c, cb) { + setTimeout(function(){ + cb(null, a, b, c); + }, 1); +}; + +var syncErroneusNode = function(a, b, c, cb) { + cb(sentinelError); +}; + +var syncSuccessNode = function(a, b, c, cb) { + cb(null, a); +}; + +var syncSuccessNodeMultipleValues = function(a, b, c, cb) { + cb(null, a, b, c); +}; + +var errToThrow; +var thrower = Promise.promisify(function(a, b, c, cb) { + errToThrow = new OperationalError(); + throw errToThrow; +}); + +var tprimitive = "Where is your stack now?"; +var throwsStrings = Promise.promisify(function(cb){ + throw tprimitive; +}); + +var errbacksStrings = Promise.promisify(function(cb){ + cb(tprimitive); +}); + +var errbacksStringsAsync = Promise.promisify(function(cb){ + setTimeout(function(){ + cb(tprimitive); + }, 1); +}); +var THIS = {}; + +var error = Promise.promisify(erroneusNode); +var syncError = Promise.promisify(syncErroneusNode); +var success = Promise.promisify(successNode); +var syncSuccess = Promise.promisify(syncSuccessNode); +var successMultiArgsSingleValue = Promise.promisify(successNode, {multiArgs: true}); +var successMultiOptDisabledNoReceiver = Promise.promisify(successNodeMultipleValues); +var syncSuccessMultiOptDisabledNoReceiver = Promise.promisify(syncSuccessNodeMultipleValues); +var successMultiOptEnabledNoReceiver = Promise.promisify(successNodeMultipleValues, {multiArgs: true}); +var syncSuccessMultiOptEnabledNoReceiver = Promise.promisify(syncSuccessNodeMultipleValues, {multiArgs: true}); +var successMultiOptEnabledWithReceiver = Promise.promisify(successNodeMultipleValues, {multiArgs: true, context: THIS}); +var syncSccessMultiOptEnabledWithReceiver = Promise.promisify(syncSuccessNodeMultipleValues, {multiArgs: true, context: THIS}); +var successMultiOptDisabledWithReceiver = Promise.promisify(successNodeMultipleValues, {context: THIS}); +var syncSccessMultiOptDisabledWithReceiver = Promise.promisify(syncSuccessNodeMultipleValues, {context: THIS}); +var successMulti = successMultiOptDisabledNoReceiver; +var syncSuccessMulti = syncSuccessMultiOptDisabledNoReceiver; +describe("when calling promisified function it should ", function(){ + specify("return a promise that is pending", function() { + var a = error(1,2,3); + var b = success(1,2,3); + var c = successMulti(1,2,3); + + var calls = 0; + assert.equal(a.isPending(), true); + assert.equal(b.isPending(), true); + assert.equal(c.isPending(), true); + return a.caught(testUtils.noop); + }); + + specify("should use this if no receiver was given", function(){ + var o = {}; + var fn = Promise.promisify(function(cb){ + + cb(null, this === o); + }); + + o.fn = fn; + + return o.fn().then(function(val){ + assert(val); + }); + }); + + specify("do nothing when called more than 1 times", function() { + var err = new Error(); + var stack = err.stack; + + var fn = Promise.promisify(function(cb) { + cb(null); + cb(err); + }); + + return fn().then(function() { + return Promise.delay(1).then(function() { + assert.strictEqual(stack, err.stack); + }) + }); + }); + + specify("undefined as receiver", function() { + return Promise.promisify(function(cb) { + assert.strictEqual(this, (function(){return this;})()); + cb(null, 1); + }, {context: undefined})().then(function(result) { + assert.strictEqual(1, result); + }); + }); + + specify("double promisification returns same function back", function() { + var c = function(){}; + var a = Promise.promisify(function(){}); + var b = Promise.promisify(a); + assert.notEqual(c, a); + assert.strictEqual(a, b); + }); + + specify("call future attached handlers later", function() { + var a = error(1,2,3).then(0, testUtils.noop); + var b = success(1,2,3); + var c = successMulti(1,2,3); + var d = syncError(1,2,3).then(0, testUtils.noop); + var e = syncSuccess(1,2,3).then(0, testUtils.noop); + var f = syncSuccessMulti(1,2,3).then(0, testUtils.noop); + var calls = 0; + return Promise.all([a, b, c, d, e, f]); + }); + + specify("Reject with the synchronously caught reason", function(){ + thrower(1, 2, 3).then(assert.fail).then(assert.fail, function(e){ + assert(e === errToThrow); + }); + }); + + specify("reject with the proper reason", function() { + var a = error(1,2,3); + var b = syncError(1,2,3); + + return Promise.all([ + a.then(assert.fail, function(e){ + assert.equal(sentinelError, e); + }), + b.then(assert.fail, function(e){ + assert.equal(sentinelError, e); + }) + ]); + }); + + describe("multi-args behaviors", function() { + specify("successMultiArgsSingleValue", function() { + var a = successMultiArgsSingleValue(1, 2, 3); + return a.then(function(value) { + assert.deepEqual([1], value); + }) + }); + specify("successMultiOptDisabledNoReceiver", function() { + var a = successMultiOptDisabledNoReceiver(1, 2, 3); + return a.then(function(value) { + assert.strictEqual(value, 1); + }) + }); + specify("syncSuccessMultiOptDisabledNoReceiver", function() { + var a = syncSuccessMultiOptDisabledNoReceiver(1, 2, 3); + return a.then(function(value) { + assert.strictEqual(value, 1); + }) + }); + specify("successMultiOptEnabledNoReceiver", function() { + var a = successMultiOptEnabledNoReceiver(1, 2, 3); + return a.then(function(value) { + assert.deepEqual([1,2,3], value); + }) + }); + specify("syncSuccessMultiOptEnabledNoReceiver", function() { + var a = syncSuccessMultiOptEnabledNoReceiver(1, 2, 3); + return a.then(function(value) { + assert.deepEqual([1,2,3], value); + }) + }); + specify("successMultiOptEnabledWithReceiver", function() { + var a = successMultiOptEnabledWithReceiver(1, 2, 3); + return a.then(function(value) { + assert.deepEqual([1,2,3], value); + }) + }); + specify("syncSccessMultiOptEnabledWithReceiver", function() { + var a = syncSccessMultiOptEnabledWithReceiver(1, 2, 3); + return a.then(function(value) { + assert.deepEqual([1,2,3], value); + }) + }); + specify("successMultiOptDisabledWithReceiver", function() { + var a = successMultiOptDisabledWithReceiver(1, 2, 3); + return a.then(function(value) { + assert.strictEqual(value, 1); + }) + }); + specify("syncSccessMultiOptDisabledWithReceiver", function() { + var a = syncSccessMultiOptDisabledWithReceiver(1, 2, 3); + return a.then(function(value) { + assert.strictEqual(value, 1); + }) + }); + }); +}); + +describe("with more than 5 arguments", function(){ + + var o = { + value: 15, + + f: function(a,b,c,d,e,f,g, cb) { + cb(null, [a,b,c,d,e,f,g, this.value]) + } + + } + + + var prom = Promise.promisify(o.f, {context: o}); + + specify("receiver should still work", function() { + return prom(1,2,3,4,5,6,7).then(function(val){ + assert.deepEqual( + val, + [1,2,3,4,5,6,7, 15] + ); + }); + + }); + +}); + +describe("promisify on objects", function(){ + + var o = { + value: 15, + + f: function(a,b,c,d,e,f,g, cb) { + cb(null, [a,b,c,d,e,f,g, this.value]) + } + + }; + + var objf = function(){}; + + objf.value = 15; + objf.f = function(a,b,c,d,e,f,g, cb) { + cb(null, [a,b,c,d,e,f,g, this.value]) + }; + + function Test(data) { + this.data = data; + } + + Test.prototype.get = function(a, b, c, cb) { + cb(null, a, b, c, this.data); + }; + + Test.prototype.getMany = function(a, b, c, d, e, f, g, cb) { + cb(null, a, b, c, d, e, f, g, this.data); + }; + + Promise.promisifyAll(o); + Promise.promisifyAll(objf); + Promise.promisifyAll(Test.prototype); + + specify("should not repromisify", function() { + var f = o.f; + var fAsync = o.fAsync; + var getOwnPropertyNames = Object.getOwnPropertyNames(o); + var ret = Promise.promisifyAll(o); + assert.equal(f, o.f); + assert.equal(fAsync, o.fAsync); + assert.deepEqual(getOwnPropertyNames, Object.getOwnPropertyNames(o)); + assert.equal(ret, o); + }); + + specify("should not repromisify function object", function() { + var f = objf.f; + var fAsync = objf.fAsync; + var getOwnPropertyNames = Object.getOwnPropertyNames(objf); + var ret = Promise.promisifyAll(objf); + assert.equal(f, objf.f); + assert.equal(fAsync, objf.fAsync); + assert.deepEqual(getOwnPropertyNames, Object.getOwnPropertyNames(objf)); + assert.equal(ret, objf); + }); + + specify("should work on function objects too", function() { + objf.fAsync(1, 2, 3, 4, 5, 6, 7).then(function(result){ + assert.deepEqual(result, [1, 2, 3, 4, 5, 6, 7, 15]); + }); + }); + + specify("should work on prototypes and not mix-up the instances", function() { + var a = new Test(15); + var b = new Test(30); + var c = new Test(45); + return Promise.all([ + a.getAsync(1, 2, 3).then(function(result){ + assert.strictEqual(result, 1); + }), + + b.getAsync(4, 5, 6).then(function(result){ + assert.strictEqual(result, 4); + }), + + c.getAsync(7, 8, 9).then(function(result){ + assert.strictEqual(result, 7); + }) + ]); + }); + + specify("should work on prototypes and not mix-up the instances with more than 5 arguments", function() { + var a = new Test(15); + var b = new Test(30); + var c = new Test(45); + + return Promise.all([ + a.getManyAsync(1, 2, 3, 4, 5, 6, 7).then(function(result){ + assert.strictEqual(result, 1); + }), + + b.getManyAsync(4, 5, 6, 7, 8, 9, 10).then(function(result){ + assert.strictEqual(result, 4); + }), + + c.getManyAsync(7, 8, 9, 10, 11, 12, 13).then(function(result){ + assert.strictEqual(result, 7); + }) + ]); + }); + + specify("Fails to promisify Async suffixed methods", function() { + var o = { + x: function(cb){ + cb(null, 13); + }, + xAsync: function(cb) { + cb(null, 13); + }, + + xAsyncAsync: function(cb) { + cb(null, 13) + } + }; + try { + Promise.promisifyAll(o); + } + catch (e) { + assert(e instanceof Promise.TypeError); + } + }); + + specify("Calls overridden methods", function() { + function Model() { + this.save = function() {}; + } + Model.prototype.save = function() { + throw new Error(""); + }; + + Promise.promisifyAll(Model.prototype); + var model = new Model(); + model.saveAsync(); + }); + + specify("gh-232", function() { + function f() { + var args = [].slice.call(arguments, 0, -1); + assert.deepEqual(args, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + var cb = [].slice.call(arguments, -1)[0]; + cb(null, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + var fAsync = Promise.promisify(f); + return fAsync(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).then(function(result) { + assert.strictEqual(result, 1); + }); + }); + + specify("Should lookup method dynamically if 'this' is given", function() { + var obj = { + fn: function(cb) { + cb(null, 1); + } + }; + Promise.promisifyAll(obj); + obj.fn = function(cb) { + cb(null, 2); + }; + return obj.fnAsync().then(function(val) { + assert.strictEqual(2, val); + }); + }); + + specify("gh335", function() { + function HasArgs() { } + HasArgs.prototype.args = function(cb) { + return cb(null, "ok"); + }; + + Promise.promisifyAll(HasArgs.prototype); + var a = new HasArgs(); + return a.argsAsync().then(function(res) { + assert.equal(res, "ok"); + }); + }); + specify("Should not promisify Object.prototype methods", function() { + var o = {}; + var keys = Object.keys(o); + Promise.promisifyAll(o); + assert.deepEqual(keys.sort(), Object.keys(o).sort()); + }); + + specify("Should not promisify Object.prototype methods", function() { + var o = {method: function(){}}; + Promise.promisifyAll(o); + assert.deepEqual(["method", "methodAsync"].sort(), Object.keys(o).sort()); + }); + + if (testUtils.ecmaScript5) { + specify("Should promisify non-enumerable methods", function() { + var o = {}; + Object.defineProperty(o, "method", { + value: function() {}, + enumerable: false + }); + Promise.promisifyAll(o); + assert.deepEqual(["method", "methodAsync"].sort(), + Object.getOwnPropertyNames(o).sort()); + }); + } +}); + +describe("Promisify with custom suffix", function() { + it("should define methods with the custom suffix", function() { + function Test() { + + } + + Test.prototype.method = function method() {}; + + Promise.promisifyAll(Test.prototype, {suffix: "$P"}); + assert(typeof Test.prototype.method$P == "function"); + }); + + it("should throw on invalid suffix", function() { + try { + Promise.promisifyAll({}, {suffix: ""}); + } + catch (e) { + return; + } + assert.fail(); + }); +}) + +describe("Module promisification", function() { + it("should promisify module with direct property classes", function() { + function RedisClient() {} + RedisClient.prototype.query = function() {}; + function Multi() {} + Multi.prototype.exec = function() {}; + Multi.staticMethod = function() {} + + var redis = { + RedisClient: RedisClient, + Multi: Multi, + moduleMethod: function() {} + }; + redis.Multi.staticMethod.tooDeep = function() {}; + + Promise.promisifyAll(redis); + + assert(typeof redis.moduleMethodAsync === "function"); + assert(typeof redis.Multi.staticMethodAsync === "function"); + assert(typeof redis.Multi.prototype.execAsync === "function"); + assert(typeof redis.RedisClient.prototype.queryAsync === "function"); + assert(typeof redis.Multi.staticMethod.tooDeepAsync === "undefined"); + }) + + it("should promisify module with inherited property classes", function() { + function Mongoose() {} + var Model = Mongoose.prototype.Model = function() {}; + Model.prototype.find = function() {}; + var Document = Mongoose.prototype.Document = function() {}; + Document.prototype.create = function() {}; + Document.staticMethod = function() {}; + var mongoose = new Mongoose(); + + Promise.promisifyAll(mongoose); + + assert(typeof mongoose.Model.prototype.findAsync === "function"); + assert(typeof mongoose.Document.prototype.createAsync === "function"); + assert(typeof mongoose.Document.staticMethodAsync === "function") + }) + + it("should promisify classes that have static methods", function() { + function MongoClient() {this.connect = 3;} + MongoClient.connect = function() {}; + var module = {}; + module.MongoClient = MongoClient; + Promise.promisifyAll(module); + + assert(typeof MongoClient.connectAsync === "function"); + }); +}) + +describe("Promisify from prototype to object", function() { + var getterCalled = 0; + + function makeClass() { + var Test = (function() { + + function Test() { + + } + var method = Test.prototype; + + method.test = function() { + + }; + + method["---invalid---"] = function(){}; + + if (testUtils.ecmaScript5) { + Object.defineProperty(method, "thrower", { + enumerable: true, + configurable: true, + get: function() { + throw new Error("getter called"); + }, + set: function() { + throw new Error("setter called"); + } + }); + Object.defineProperty(method, "counter", { + enumerable: true, + configurable: true, + get: function() { + getterCalled++; + }, + set: function() { + throw new Error("setter called"); + } + }); + } + + return Test;})(); + + return Test; + } + + specify("Shouldn't touch the prototype when promisifying instance", function() { + var Test = makeClass(); + + var origKeys = Object.getOwnPropertyNames(Test.prototype).sort(); + var a = new Test(); + Promise.promisifyAll(a); + + assert(typeof a.testAsync === "function"); + assert(a.hasOwnProperty("testAsync")); + assert.deepEqual(Object.getOwnPropertyNames(Test.prototype).sort(), origKeys); + assert(getterCalled === 0); + }); + + specify("Shouldn't touch the method", function() { + var Test = makeClass(); + + var origKeys = Object.getOwnPropertyNames(Test.prototype.test).sort(); + var a = new Test(); + Promise.promisifyAll(a); + + + assert(typeof a.testAsync === "function"); + assert.deepEqual(Object.getOwnPropertyNames(Test.prototype.test).sort(), origKeys); + assert(Promise.promisify(a.test) !== a.testAsync); + assert(getterCalled === 0); + }); + + specify("Should promisify own method even if a promisified method of same name already exists somewhere in proto chain", function(){ + var Test = makeClass(); + var instance = new Test(); + Promise.promisifyAll(instance); + var origKeys = Object.getOwnPropertyNames(Test.prototype).sort(); + var origInstanceKeys = Object.getOwnPropertyNames(instance).sort(); + instance.test = function() {}; + Promise.promisifyAll(instance); + assert.deepEqual(origKeys, Object.getOwnPropertyNames(Test.prototype).sort()); + assert.notDeepEqual(origInstanceKeys, Object.getOwnPropertyNames(instance).sort()); + assert(getterCalled === 0); + }); + + specify("Shouldn promisify the method closest to the object if method of same name already exists somewhere in proto chain", function(){ + //IF the implementation is for-in, this pretty much tests spec compliance + var Test = makeClass(); + var origKeys = Object.getOwnPropertyNames(Test.prototype).sort(); + var instance = new Test(); + instance.test = function() {}; + Promise.promisifyAll(instance); + + assert.deepEqual(Object.getOwnPropertyNames(Test.prototype).sort(), origKeys); + assert(instance.test === instance.test); + assert(getterCalled === 0); + }); + +}); + + +function assertLongStackTraces(e) { + assert(e.stack.indexOf("From previous event:") > -1); +} +if (Promise.hasLongStackTraces()) { + describe("Primitive errors wrapping", function() { + specify("when the node function throws it", function(){ + return throwsStrings().then(assert.fail, function(e){ + assert(e instanceof Error); + assert(e.message == tprimitive); + }); + }); + + specify("when the node function throws it inside then", function(){ + return Promise.resolve().then(function() { + throwsStrings().then(assert.fail, function(e) { + assert(e instanceof Error); + assert(e.message == tprimitive); + assertLongStackTraces(e); + }); + }); + }); + + + specify("when the node function errbacks it synchronously", function(){ + return errbacksStrings().then(assert.fail, function(e){ + assert(e instanceof Error); + assert(e.message == tprimitive); + }); + }); + + specify("when the node function errbacks it synchronously inside then", function(){ + return Promise.resolve().then(function(){ + errbacksStrings().then(assert.fail, function(e){ + assert(e instanceof Error); + assert(e.message == tprimitive); + assertLongStackTraces(e); + }); + }); + }); + + specify("when the node function errbacks it asynchronously", function(){ + return errbacksStringsAsync().then(assert.fail, function(e){ + assert(e instanceof Error); + assert(e.message == tprimitive); + assertLongStackTraces(e); + }); + }); + + specify("when the node function errbacks it asynchronously inside then", function(){ + return Promise.resolve().then(function(){ + errbacksStringsAsync().then(assert.fail, function(e){ + assert(e instanceof Error); + assert(e.message == tprimitive); + assertLongStackTraces(e); + }); + }); + }); + }); +} + +describe("Custom promisifier", function() { + var dummy = {}; + var err = new Error(); + var chrome = { + getTab: function(tabId, callback) { + setTimeout(function() { + callback(dummy); + }, 1); + }, + getTabErroneous: function(tabId, callback, errback) { + setTimeout(function() { + errback(err); + }, 1); + } + }; + + Promise.promisifyAll(chrome, { + promisifier: function(originalMethod) { + return function() { + var self = this; + var args = [].slice.call(arguments); + return new Promise(function(f, r) { + args.push(f, r); + originalMethod.apply(self, args); + }); + }; + } + }); + + specify("getTab", function() { + return chrome.getTabAsync(1).then(function(result) { + assert.equal(dummy, result); + }); + }); + + specify("getTabErroneous", function() { + return chrome.getTabErroneousAsync(2).then(assert.fail, function(e) { + assert.equal(e, err); + }); + }); + + specify("Copies custom props promisifyFirst", function() { + var request = function(cb){ + cb(null, 1); + }; + request.zero = 0; + request.get = function(cb) { + cb(null, 2 + this.zero); + }; + request.post = function(cb) { + cb(null, 3); + }; + + request = Promise.promisifyAll(Promise.promisify(request)); + return Promise.all([ + request(), + request.getAsync(), + request.postAsync() + ]).then(function(a) { + assert.deepEqual([1,2,3], a); + }); + }); + + specify("Copies custom props promisifyAll first", function() { + var request = function(cb){ + cb(null, 1); + }; + request.zero = 0; + request.get = function(cb) { + cb(null, 2 + this.zero); + }; + request.post = function(cb) { + cb(null, 3); + }; + + request = Promise.promisify(Promise.promisifyAll(request)); + return Promise.all([ + request(), + request.getAsync(), + request.postAsync() + ]).then(function(a) { + assert.deepEqual([1,2,3], a); + }); + }); + + specify("Copies custom props no this", function() { + var request = function(cb){ + cb(null, 1); + }; + request.zero = 0; + request.get = function(cb) { + cb(null, 2); + }; + request.post = function(cb) { + cb(null, 3); + }; + + request = Promise.promisify(Promise.promisifyAll(request)); + var getAsync = request.getAsync; + var postAsync = request.postAsync; + return Promise.all([ + request(), + getAsync(), + postAsync() + ]).then(function(a) { + assert.deepEqual([1,2,3], a); + }); + }); + + specify("custom promisifier enhancing default promisification", function() { + var obj = { + a: function(cb) { + setTimeout(function() { + cb(null, 1); + }, 1); + }, + + b: function(val, cb) { + setTimeout(function() { + cb(null, val); + }, 1); + } + }; + obj = Promise.promisifyAll(obj, { + promisifier: function(originalFunction, defaultPromisifier) { + var promisified = defaultPromisifier(originalFunction); + + return function() { + var args = [].slice.call(arguments); + var self = this; + return Promise.all(args).then(function(awaitedArgs) { + return promisified.apply(self, awaitedArgs); + }); + }; + } + }); + + return obj.bAsync(obj.aAsync()).then(function(val) { + assert.strictEqual(val, 1); + }); + + }); + + specify("multiArgs option enabled single value", function() { + var o = { + get: function(cb) { + cb(null, 1) + } + }; + Promise.promisifyAll(o, {multiArgs: true}); + return o.getAsync().then(function(value) { + assert.deepEqual([1], value); + }); + }); + specify("multiArgs option enabled multi value", function() { + var o = { + get: function(cb) { + cb(null, 1, 2, 3) + } + }; + Promise.promisifyAll(o, {multiArgs: true}); + return o.getAsync().then(function(value) { + assert.deepEqual([1,2,3], value); + }); + }); + specify("multiArgs option disabled single value", function() { + var o = { + get: function(cb) { + cb(null, 1) + } + }; + Promise.promisifyAll(o); + return o.getAsync().then(function(value) { + assert.strictEqual(value, 1); + }); + }); + specify("multiArgs option disabled multi value", function() { + var o = { + get: function(cb) { + cb(null, 1) + } + }; + Promise.promisifyAll(o); + return o.getAsync().then(function(value) { + assert.strictEqual(value, 1); + }); + }); +}); + +describe("OperationalError wrapping", function() { + + var CustomError = function(){ + } + CustomError.prototype = new Error(); + CustomError.prototype.constructor = CustomError; + + function isUntypedError(obj) { + return obj instanceof Error && + Object.getPrototypeOf(obj) === Error.prototype; + } + + + if (!isUntypedError(new Error())) { + console.log("error must be untyped"); + } + + if (isUntypedError(new CustomError())) { + console.log("customerror must be typed"); + } + + function stringback(cb) { + cb("Primitive as error"); + } + + function errback(cb) { + cb(new Error("error as error")); + } + + function typeback(cb) { + cb(new CustomError()); + } + + function stringthrow(cb) { + throw("Primitive as error"); + } + + function errthrow(cb) { + throw(new Error("error as error")); + } + + function typethrow(cb) { + throw(new CustomError()); + } + + stringback = Promise.promisify(stringback); + errback = Promise.promisify(errback); + typeback = Promise.promisify(typeback); + stringthrow = Promise.promisify(stringthrow); + errthrow = Promise.promisify(errthrow); + typethrow = Promise.promisify(typethrow); + + specify("should wrap stringback", function() { + return stringback().error(function(e) { + assert(e instanceof OperationalError); + }); + }); + + specify("should wrap errback", function() { + return errback().error(function(e) { + assert(e instanceof OperationalError); + }); + }); + + specify("should not wrap typeback", function() { + return typeback().caught(CustomError, function(e){ + }); + }); + + specify("should not wrap stringthrow", function() { + return stringthrow().error(assert.fail).then(assert.fail, function(e){ + assert(e instanceof Error); + }); + }); + + specify("should not wrap errthrow", function() { + return errthrow().error(assert.fail).then(assert.fail, function(e) { + assert(e instanceof Error); + }); + }); + + specify("should not wrap typethrow", function() { + return typethrow().error(assert.fail) + .caught(CustomError, function(e){ + }); + }); +}); +describe("nodeback with multiple arguments", function() { + specify("spreaded with immediate values", function() { + var promise = Promise.promisify(function(cb) { + cb(null, 1, 2, 3); + }, {multiArgs: true})(); + + return promise.spread(function(a, b, c) { + assert.equal(a, 1); + assert.equal(b, 2); + assert.equal(c, 3); + }); + }); + + specify("spreaded with thenable values should be unwrapped", function() { + var a = {then: function(a){a(1)}}; + var b = a; + var c = a; + var promise = Promise.promisify(function(cb) { + cb(null, a, b, c); + }, {multiArgs: true})(); + + return promise.spread(function(a_, b_, c_) { + assert.equal(a_, 1); + assert.equal(b_, 1); + assert.equal(c_, 1); + }); + }); + + specify("spreaded with promise values should be unwrapped", function() { + var a = Promise.resolve(1); + var b = Promise.resolve(2); + var c = Promise.resolve(3); + var promise = Promise.promisify(function(cb) { + cb(null, a, b, c); + }, {multiArgs: true})(); + + return promise.spread(function(a_, b_, c_) { + assert.strictEqual(a_, 1); + assert.strictEqual(b_, 2); + assert.strictEqual(c_, 3); + }); + }); +}); +describe("filter", function() { + specify("gets an argument whether default filter was passed", function() { + Promise.promisifyAll({ + abc: function() {} + }, { + filter: function(_, __, ___, passesDefaultFilter) { + assert.strictEqual(passesDefaultFilter, true); + } + }) + }); + + specify("doesn't fail when allowing non-identifier methods", function() { + var a = Promise.promisifyAll({ + " a s d ": function(cb) { + cb(null, 1); + } + }, { + filter: function() { + return true; + } + }); + + a[" a s d Async"]().then(function(val) { + assert.strictEqual(1, val); + }); + }); +}); + +var global = new Function("return this")(); +var canEvaluate = (function() { + if (typeof window !== "undefined" && window !== null && + typeof window.document !== "undefined" && + typeof navigator !== "undefined" && navigator !== null && + typeof navigator.appName === "string" && + window === global) { + return false; + } + return true; +})(); +var canTestArity = (function(a, b, c) {}).length === 3 && canEvaluate; + +if (canTestArity) { + describe("arity", function() { + specify("should be original - 1", function() { + var fn = function(a, b, c, callback) {}; + assert.equal(Promise.promisify(fn).length, 3); + + var o = { + fn: function(a, b, c, callback) { + + } + }; + assert.equal(Promise.promisifyAll(o).fnAsync.length, 3); + }) + }) +} + +describe("github 680", function() { + before(function() { + Function.prototype.method = function() {}; + }); + + after(function() { + delete Function.prototype.method; + }); + + specify("should not try to promisify methods from Function.prototype, native or otherwise", function() { + var a = function() {}; + a.fn = function() {}; + Promise.promisifyAll(a); + assert.strictEqual(undefined, a.methodAsync); + assert.strictEqual(undefined, a.applyAsync); + assert(typeof a.fnAsync === "function"); + }); +}); + +describe("github 1063", function() { + specify("should not cause error when called with no arguments", function() { + return Promise.promisify(function(cb) { + cb(); + }, { multiArgs: true})().then(function(values) { + assert(Array.isArray(values)); + assert.strictEqual(values.length, 0); + }); + }) +}); + +describe("github 1023", function() { + specify("promisify triggers custom schedulers", function() { + var triggered = false; + var defaultScheduler = Promise.setScheduler(function(fn) { + triggered = true; + setTimeout(fn, 0); + }); + var fnAsync = Promise.promisify(function(cb) { + setTimeout(function() { + cb(null, true); + }, 0); + }); + + return fnAsync().then(function(result) { + assert(result); + assert(triggered); + }).lastly(function() { + Promise.setScheduler(defaultScheduler); + }); + }); +}) diff --git a/test/mocha/props.js b/test/mocha/props.js new file mode 100644 index 0000000..585ef58 --- /dev/null +++ b/test/mocha/props.js @@ -0,0 +1,220 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +describe("Promise.props", function () { + + specify("should reject undefined", function() { + return Promise.props().caught(TypeError, function(){ + }) + }); + + specify("should reject primitive", function() { + return Promise.props("str").caught(TypeError, function(){ + }) + }); + + specify("should resolve to new object", function() { + var o = {}; + return Promise.props(o).then(function(v){ + assert(v !== o); + assert.deepEqual(o, v); + }); + }); + + specify("should resolve value properties", function() { + var o = { + one: 1, + two: 2, + three: 3 + }; + return Promise.props(o).then(function(v){ + assert.deepEqual({ + one: 1, + two: 2, + three: 3 + }, v); + }); + }); + + specify("should resolve immediate properties", function() { + var o = { + one: Promise.resolve(1), + two: Promise.resolve(2), + three: Promise.resolve(3) + }; + return Promise.props(o).then(function(v){ + assert.deepEqual({ + one: 1, + two: 2, + three: 3 + }, v); + }); + }); + + specify("should resolve eventual properties", function() { + var d1 = Promise.defer(), + d2 = Promise.defer(), + d3 = Promise.defer(); + var o = { + one: d1.promise, + two: d2.promise, + three: d3.promise + }; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.props(o).then(function(v){ + assert.deepEqual({ + one: 1, + two: 2, + three: 3 + }, v); + }); + + + }); + + specify("should reject if any input promise rejects", function() { + var o = { + one: Promise.resolve(1), + two: Promise.reject(2), + three: Promise.resolve(3) + }; + return Promise.props(o).then(assert.fail, function(v){ + assert(v === 2); + }); + }); + + specify("should accept a promise for an object", function() { + var o = { + one: Promise.resolve(1), + two: Promise.resolve(2), + three: Promise.resolve(3) + }; + var d1 = Promise.defer(); + setTimeout(function(){ + d1.fulfill(o); + }, 1); + return Promise.props(d1.promise).then(function(v){ + assert.deepEqual({ + one: 1, + two: 2, + three: 3 + }, v); + }); + + }); + + specify("should reject a promise for a primitive", function() { + var d1 = Promise.defer(); + setTimeout(function(){ + d1.fulfill("text"); + }, 1); + return Promise.props(d1.promise).caught(TypeError, function(){ + }); + + }); + + specify("should accept thenables in properties", function() { + var t1 = {then: function(cb){cb(1);}}; + var t2 = {then: function(cb){cb(2);}}; + var t3 = {then: function(cb){cb(3);}}; + var o = { + one: t1, + two: t2, + three: t3 + }; + return Promise.props(o).then(function(v){ + assert.deepEqual({ + one: 1, + two: 2, + three: 3 + }, v); + }); + }); + + specify("should accept a thenable for thenables in properties", function() { + var o = { + then: function (f) { + f({ + one: { + then: function (cb) { + cb(1); + } + }, + two: { + then: function (cb) { + cb(2); + } + }, + three: { + then: function (cb) { + cb(3); + } + } + }); + } + }; + return Promise.props(o).then(function(v){ + assert.deepEqual({ + one: 1, + two: 2, + three: 3 + }, v); + }); + }); + + specify("treats arrays for their properties", function() { + var o = [1,2,3]; + + return Promise.props(o).then(function(v){ + assert.deepEqual({ + 0: 1, + 1: 2, + 2: 3 + }, v); + }); + }); + + + if (typeof Map !== "undefined") { + specify("works with es6 maps", function() { + return Promise.props(new Map([ + ["a", Promise.resolve(1)], + ["b", Promise.resolve(2)], + ["c", Promise.resolve(3)] + ])).then(function(result) { + assert.strictEqual(result.get("a"), 1); + assert.strictEqual(result.get("b"), 2); + assert.strictEqual(result.get("c"), 3); + }); + }); + + specify("doesn't await promise keys in es6 maps", function() { + var a = new Promise(function() {}); + var b = new Promise(function() {}); + var c = new Promise(function() {}); + + return Promise.props(new Map([ + [a, Promise.resolve(1)], + [b, Promise.resolve(2)], + [c, Promise.resolve(3)] + ])).then(function(result) { + assert.strictEqual(result.get(a), 1); + assert.strictEqual(result.get(b), 2); + assert.strictEqual(result.get(c), 3); + }); + }); + + specify("empty map should resolve to empty map", function() { + return Promise.props(new Map()).then(function(result) { + assert(result instanceof Map); + }); + }); + } + +}); diff --git a/test/mocha/race.js b/test/mocha/race.js new file mode 100644 index 0000000..77eeeac --- /dev/null +++ b/test/mocha/race.js @@ -0,0 +1,121 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + + + +describe("Promise.race", function(){ + it("remains forever pending when passed an empty array", function() { + var p = Promise.race([]); + return Promise.delay(1).then(function() { + assert(p.isPending()); + }); + }); + + it("remains forever pending when passed an empty sparse array", function() { + var p = Promise.race([,,,,,]); + return Promise.delay(1).then(function() { + assert(p.isPending()); + }); + }); + + it("fulfills when passed an immediate value", function() { + return Promise.race([1,2,3]).then(function(v){ + assert.deepEqual(v, 1); + }); + }); + + it("fulfills when passed an immediately fulfilled value", function() { + var d1 = Promise.defer(); + d1.fulfill(1); + var p1 = d1.promise; + + var d2 = Promise.defer(); + d2.fulfill(2); + var p2 = d2.promise; + + var d3 = Promise.defer(); + d3.fulfill(3); + var p3 = d3.promise; + + return Promise.race([p1, p2, p3]).then(function(v){ + assert.deepEqual(v, 1); + }); + }); + + it("fulfills when passed an eventually fulfilled value", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.fulfill(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.race([p1, p2, p3]).then(function(v){ + assert.deepEqual(v, 1); + }); + }); + + it("rejects when passed an immediate value", function() { + return Promise.race([Promise.reject(1), 2, 3]).then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }); + + it("rejects when passed an immediately rejected value", function() { + var d1 = Promise.defer(); + d1.reject(1); + var p1 = d1.promise; + + var d2 = Promise.defer(); + d2.fulfill(2); + var p2 = d2.promise; + + var d3 = Promise.defer(); + d3.fulfill(3); + var p3 = d3.promise; + + return Promise.race([, p1, , p2, , , p3]).then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }); + }); + + it("rejects when passed an eventually rejected value", function() { + var d1 = Promise.defer(); + var p1 = d1.promise; + + var d2 = Promise.defer(); + var p2 = d2.promise; + + var d3 = Promise.defer(); + var p3 = d3.promise; + + setTimeout(function(){ + d1.reject(1); + d2.fulfill(2); + d3.fulfill(3); + }, 1); + + return Promise.race([p1, p2, p3]).then(assert.fail, function(v){ + assert.deepEqual(v, 1); + }) + }); + + it("propagates bound value", function() { + var o = {}; + return Promise.resolve([1]).bind(o).race().then(function(v){ + assert(v === 1); + assert(this === o); + }); + }); +}); diff --git a/test/mocha/reduce.js b/test/mocha/reduce.js new file mode 100644 index 0000000..b29a07d --- /dev/null +++ b/test/mocha/reduce.js @@ -0,0 +1,705 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +function promised(val) { + return new Promise(function(f) { + setTimeout(function() { + f(val); + }, 1); + }); +} +function promising(val) { + return function() { + return promised(val); + } +} +function promisingThen(val) { + return function() { + return promised(val).then(function(resolved) { + return resolved; + }); + } +} + +function thenabled(val) { + return { + then: function(f){ + setTimeout(function() { + f(val); + }, 1); + } + }; +} +function thenabling(val) { + return function() { return thenabled(val); } +} + +function evaluate(val) { + if (typeof val === 'function') { + val = val(); + } + if (Array.isArray(val)) { + val = val.map(function(member) { + return evaluate(member); + }); + } + return val; +} + + +var ACCUM_CRITERIA = [ + { value: 0, desc: "that is resolved" }, + { value: promising(0), desc: "as a Promise" }, + { value: promisingThen(0), desc: "as a deferred Promise" }, + { value: thenabling(0), desc: "as a thenable" }, +]; + +var VALUES_CRITERIA = [ + { value: [], total: 0, desc: "and no values" }, + { value: [ 1 ], total: 1, desc: "and a single resolved value" }, + { value: [ 1, 2, 3 ], total: 6, desc: "and multiple resolved values" }, + { value: [ promising(1) ], total: 1, desc: "and a single Promise" }, + { value: [ + promising(1), + promising(2), + promising(3) + ], total: 6, desc: "and multiple Promises" }, + { value: [ + promisingThen(1) + ], total: 1, desc: "and a single deferred Promise" }, + { value: [ + promisingThen(1), + promisingThen(2), + promisingThen(3) + ], total: 6, desc: "and multiple deferred Promises" }, + { value: [ + thenabling(1) + ], total: 1, desc: "and a single thenable" }, + { value: [ + thenabling(1), + thenabling(2), + thenabling(3) + ], total: 6, desc: "and multiple thenables" }, + { value: [ + thenabling(1), + promisingThen(2), + promising(3), + 4 + ], total: 10, desc: "and a blend of values" }, +]; + +var ERROR = new Error("BOOM"); + + +describe("Promise.prototype.reduce", function() { + it("works with no values", function() { + return Promise.resolve([]).reduce(function(total, value) { + return total + value + 5; + }).then(function(total) { + assert.strictEqual(total, undefined); + }); + }); + + it("works with a single value", function() { + return Promise.resolve([ 1 ]).reduce(function(total, value) { + return total + value + 5; + }).then(function(total) { + assert.strictEqual(total, 1); + }); + }); + + it("works when the iterator returns a value", function() { + return Promise.resolve([ 1, 2, 3 ]).reduce(function(total, value) { + return total + value + 5; + }).then(function(total) { + assert.strictEqual(total, (1 + 2+5 + 3+5)); + }); + }); + + it("works when the iterator returns a Promise", function() { + return Promise.resolve([ 1, 2, 3 ]).reduce(function(total, value) { + return promised(5).then(function(bonus) { + return total + value + bonus; + }); + }).then(function(total) { + assert.strictEqual(total, (1 + 2+5 + 3+5)); + }); + }); + + it("works when the iterator returns a thenable", function() { + return Promise.resolve([ 1, 2, 3 ]).reduce(function(total, value) { + return thenabled(total + value + 5); + }).then(function(total) { + assert.strictEqual(total, (1 + 2+5 + 3+5)); + }); + }); +}); + + +describe("Promise.reduce", function() { + + it("should allow returning values", function() { + var a = [promised(1), promised(2), promised(3)]; + + return Promise.reduce(a, function(total, a) { + return total + a + 5; + }, 0).then(function(total){ + assert.equal(total, 1+5 + 2+5 + 3+5); + }); + }); + + it("should allow returning promises", function() { + var a = [promised(1), promised(2), promised(3)]; + + return Promise.reduce(a, function(total, a) { + return promised(5).then(function(b) { + return total + a + b; + }); + }, 0).then(function(total){ + assert.equal(total, 1+5 + 2+5 + 3+5); + }); + }); + + it("should allow returning thenables", function() { + var b = [1,2,3]; + var a = []; + + return Promise.reduce(b, function(total, cur) { + a.push(cur); + return thenabled(3); + }, 0).then(function(total) { + assert.equal(total, 3); + assert.deepEqual(a, b); + }); + }); + + it("propagates error", function() { + var a = [promised(1), promised(2), promised(3)]; + var e = new Error("asd"); + return Promise.reduce(a, function(total, a) { + if (a > 2) { + throw e; + } + return total + a + 5; + }, 0).then(assert.fail, function(err) { + assert.equal(err, e); + }); + }); + + describe("with no initial accumulator or values", function() { + it("works when the iterator returns a value", function() { + return Promise.reduce([], function(total, value) { + return total + value + 5; + }).then(function(total){ + assert.strictEqual(total, undefined); + }); + }); + + it("works when the iterator returns a Promise", function() { + return Promise.reduce([], function(total, value) { + return promised(5).then(function(bonus) { + return total + value + bonus; + }); + }).then(function(total){ + assert.strictEqual(total, undefined); + }); + }); + + it("works when the iterator returns a thenable", function() { + return Promise.reduce([], function(total, value) { + return thenabled(total + value + 5); + }).then(function(total){ + assert.strictEqual(total, undefined); + }); + }); + }); + + describe("with an initial accumulator value", function() { + ACCUM_CRITERIA.forEach(function(criteria) { + var initial = criteria.value; + + describe(criteria.desc, function() { + VALUES_CRITERIA.forEach(function(criteria) { + var values = criteria.value; + var valueTotal = criteria.total; + + describe(criteria.desc, function() { + it("works when the iterator returns a value", function() { + return Promise.reduce(evaluate(values), function(total, value) { + return total + value + 5; + }, evaluate(initial)).then(function(total){ + assert.strictEqual(total, valueTotal + (values.length * 5)); + }); + }); + + it("works when the iterator returns a Promise", function() { + return Promise.reduce(evaluate(values), function(total, value) { + return promised(5).then(function(bonus) { + return total + value + bonus; + }); + }, evaluate(initial)).then(function(total){ + assert.strictEqual(total, valueTotal + (values.length * 5)); + }); + }); + + it("works when the iterator returns a thenable", function() { + return Promise.reduce(evaluate(values), function(total, value) { + return thenabled(total + value + 5); + }, evaluate(initial)).then(function(total){ + assert.strictEqual(total, valueTotal + (values.length * 5)); + }); + }); + }); + }); + }); + }); + + it("propagates an initial Error", function() { + var initial = Promise.reject(ERROR); + var values = [ + thenabling(1), + promisingThen(2)(), + promised(3), + 4 + ]; + + return Promise.reduce(values, function(total, value) { + return value; + }, initial).then(assert.fail, function(err) { + assert.equal(err, ERROR); + }); + }); + + it("propagates a value's Error", function() { + var initial = 0; + var values = [ + thenabling(1), + promisingThen(2)(), + Promise.reject(ERROR), + promised(3), + 4 + ]; + + return Promise.reduce(values, function(total, value) { + return value; + }, initial).then(assert.fail, function(err) { + assert.equal(err, ERROR); + }); + }); + + it("propagates an Error from the iterator", function() { + var initial = 0; + var values = [ + thenabling(1), + promisingThen(2)(), + promised(3), + 4 + ]; + + return Promise.reduce(values, function(total, value) { + if (value === 2) { + throw ERROR; + } + return value; + }, initial).then(assert.fail, function(err) { + assert.equal(err, ERROR); + }); + }); + }); + + describe("with a 0th value acting as an accumulator", function() { + it("acts this way when an accumulator value is provided yet `undefined`", function() { + return Promise.reduce([ 1, 2, 3 ], function(total, value) { + return ((total === void 0) ? 0 : total) + value + 5; + }, undefined).then(function(total){ + assert.strictEqual(total, (1 + 2+5 + 3+5)); + }); + }); + + it("survives an `undefined` 0th value", function() { + return Promise.reduce([ undefined, 1, 2, 3 ], function(total, value) { + return ((total === void 0) ? 0 : total) + value + 5; + }).then(function(total){ + assert.strictEqual(total, (1+5 + 2+5 + 3+5)); + }); + }); + + ACCUM_CRITERIA.forEach(function(criteria) { + var zeroth = criteria.value; + + describe(criteria.desc, function() { + VALUES_CRITERIA.forEach(function(criteria) { + var values = criteria.value; + var zerothAndValues = [ zeroth ].concat(values); + var valueTotal = criteria.total; + + describe(criteria.desc, function() { + it("works when the iterator returns a value", function() { + return Promise.reduce(evaluate(zerothAndValues), function(total, value) { + return total + value + 5; + }).then(function(total){ + assert.strictEqual(total, valueTotal + (values.length * 5)); + }); + }); + + it("works when the iterator returns a Promise", function() { + return Promise.reduce(evaluate(zerothAndValues), function(total, value) { + return promised(5).then(function(bonus) { + return total + value + bonus; + }); + }).then(function(total){ + assert.strictEqual(total, valueTotal + (values.length * 5)); + }); + }); + + it("works when the iterator returns a thenable", function() { + return Promise.reduce(evaluate(zerothAndValues), function(total, value) { + return thenabled(total + value + 5); + }).then(function(total){ + assert.strictEqual(total, valueTotal + (values.length * 5)); + }); + }); + }); + }); + }); + }); + + it("propagates an initial Error", function() { + var values = [ + Promise.reject(ERROR), + thenabling(1), + promisingThen(2)(), + promised(3), + 4 + ]; + + return Promise.reduce(values, function(total, value) { + return value; + }).then(assert.fail, function(err) { + assert.equal(err, ERROR); + }); + }); + + it("propagates a value's Error", function() { + var values = [ + 0, + thenabling(1), + promisingThen(2)(), + Promise.reject(ERROR), + promised(3), + 4 + ]; + + return Promise.reduce(values, function(total, value) { + return value; + }).then(assert.fail, function(err) { + assert.equal(err, ERROR); + }); + }); + + it("propagates an Error from the iterator", function() { + var values = [ + 0, + thenabling(1), + promisingThen(2)(), + promised(3), + 4 + ]; + + return Promise.reduce(values, function(total, value) { + if (value === 2) { + throw ERROR; + } + return value; + }).then(assert.fail, function(err) { + assert.equal(err, ERROR); + }); + }); + }); +}); + +/* +Based on When.js tests + +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var sentinel = {}; +var other = {}; +describe("Promise.reduce-test", function () { + + function plus(sum, val) { + return sum + val; + } + + function later(val) { + return Promise.delay(1, val); + } + + + specify("should reduce values without initial value", function() { + return Promise.reduce([1,2,3], plus).then( + function(result) { + assert.deepEqual(result, 6); + }, + assert.fail + ); + }); + + specify("should reduce values with initial value", function() { + return Promise.reduce([1,2,3], plus, 1).then( + function(result) { + assert.deepEqual(result, 7); + }, + assert.fail + ); + }); + + specify("should reduce values with initial promise", function() { + return Promise.reduce([1,2,3], plus, Promise.resolve(1)).then( + function(result) { + assert.deepEqual(result, 7); + }, + assert.fail + ); + }); + + specify("should reduce promised values without initial value", function() { + var input = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + return Promise.reduce(input, plus).then( + function(result) { + assert.deepEqual(result, 6); + }, + assert.fail + ); + }); + + specify("should reduce promised values with initial value", function() { + var input = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + return Promise.reduce(input, plus, 1).then( + function(result) { + assert.deepEqual(result, 7); + }, + assert.fail + ); + }); + + specify("should reduce promised values with initial promise", function() { + var input = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + return Promise.reduce(input, plus, Promise.resolve(1)).then( + function(result) { + assert.deepEqual(result, 7); + }, + assert.fail + ); + }); + + specify("should reduce empty input with initial value", function() { + var input = []; + return Promise.reduce(input, plus, 1).then( + function(result) { + assert.deepEqual(result, 1); + }, + assert.fail + ); + }); + + specify("should reduce empty input with eventual promise", function() { + return Promise.reduce([], plus, Promise.delay(1, 1)).then( + function(result) { + assert.deepEqual(result, 1); + }, + assert.fail + ); + }); + + specify("should reduce empty input with initial promise", function() { + return Promise.reduce([], plus, Promise.resolve(1)).then( + function(result) { + assert.deepEqual(result, 1); + }, + assert.fail + ); + }); + + specify("should reject Promise input contains rejection", function() { + var input = [Promise.resolve(1), Promise.reject(2), Promise.resolve(3)]; + return Promise.reduce(input, plus, Promise.resolve(1)).then( + assert.fail, + function(result) { + assert.deepEqual(result, 2); + } + ); + }); + + specify("should reduce to undefined with empty array", function() { + return Promise.reduce([], plus).then(function(r){ + assert(r === void 0); + }); + }); + + specify("should reduce to initial value with empty array", function() { + return Promise.reduce([], plus, sentinel).then(function(r){ + assert(r === sentinel); + }); + }); + + specify("should reduce in input order", function() { + return Promise.reduce([later(1), later(2), later(3)], plus, '').then( + function(result) { + assert.deepEqual(result, '123'); + }, + assert.fail + ); + }); + + specify("should accept a promise for an array", function() { + return Promise.reduce(Promise.resolve([1, 2, 3]), plus, '').then( + function(result) { + assert.deepEqual(result, '123'); + }, + assert.fail + ); + }); + + specify("should resolve to initialValue Promise input promise does not resolve to an array", function() { + return Promise.reduce(Promise.resolve(123), plus, 1).caught(TypeError, function(e){ + }); + }); + + specify("should provide correct basis value", function() { + function insertIntoArray(arr, val, i) { + arr[i] = val; + return arr; + } + + return Promise.reduce([later(1), later(2), later(3)], insertIntoArray, []).then( + function(result) { + assert.deepEqual(result, [1,2,3]); + }, + assert.fail + ); + }); + + describe("checks", function() { + function later(val, ms) { + return Promise.delay(ms, val); + } + + function plus(sum, val) { + return sum + val; + } + + function plusDelayed(sum, val) { + return Promise.delay(0).then(function() { + return sum + val; + }); + } + + function check(delay1, delay2, delay3) { + return Promise.reduce([ + later(1, delay1), + later(2, delay2), + later(3, delay3) + ], plus, '').then(function(result) { + assert.deepEqual(result, '123'); + }) + } + + function checkDelayed(delay1, delay2, delay3) { + return Promise.reduce([ + later(1, delay1), + later(2, delay2), + later(3, delay3) + ], plusDelayed, '').then(function(result) { + assert.deepEqual(result, '123'); + }) + } + + specify("16, 16, 16", function() { + return check(16, 16, 16); + }); + + specify("16, 16, 4", function() { + return check(16, 16, 4); + }); + specify("4, 16, 16", function() { + return check(4, 16, 16); + }); + specify("16, 4, 16", function() { + return check(16, 4, 16); + }); + specify("16, 16, 4", function() { + return check(16, 16, 4); + }); + specify("4, 4, 16", function() { + return check(4, 4, 16); + }); + specify("16, 4, 4", function() { + return check(16, 4, 4); + }); + specify("4, 16, 4", function() { + return check(4, 16, 4); + }); + specify("4, 4, 4", function() { + return check(4, 4, 4); + }); + + + specify("16, 16, 16", function() { + return checkDelayed(16, 16, 16); + }); + + specify("16, 16, 4", function() { + return checkDelayed(16, 16, 4); + }); + specify("4, 16, 16", function() { + return checkDelayed(4, 16, 16); + }); + specify("16, 4, 16", function() { + return checkDelayed(16, 4, 16); + }); + specify("16, 16, 4", function() { + return checkDelayed(16, 16, 4); + }); + specify("4, 4, 16", function() { + return checkDelayed(4, 4, 16); + }); + specify("16, 4, 4", function() { + return checkDelayed(16, 4, 4); + }); + specify("4, 16, 4", function() { + return checkDelayed(4, 16, 4); + }); + specify("4, 4, 4", function() { + return checkDelayed(4, 4, 4); + }); + + }) +}); diff --git a/test/mocha/reflect.js b/test/mocha/reflect.js new file mode 100644 index 0000000..7adc222 --- /dev/null +++ b/test/mocha/reflect.js @@ -0,0 +1,23 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var testFulfilled = require("./helpers/testThreeCases").testFulfilled; +var testRejected = require("./helpers/testThreeCases").testRejected; + +describe(".reflect()", function() { + testFulfilled(1, function(promise) { + return promise.reflect().then(function(inspection) { + assert(inspection instanceof Promise.PromiseInspection); + assert(inspection.isFulfilled()); + assert(inspection.value() === 1); + }); + }); + testRejected(2, function(promise) { + return promise.reflect().then(function(inspection) { + assert(inspection instanceof Promise.PromiseInspection); + assert(inspection.isRejected()); + assert(inspection.reason() === 2); + }); + }); +}); diff --git a/test/mocha/regress.js b/test/mocha/regress.js new file mode 100644 index 0000000..28de3ca --- /dev/null +++ b/test/mocha/regress.js @@ -0,0 +1,169 @@ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +describe("regressions", function() { + specify("should be able to call .then more than once inside that promise's handler", function() { + var called = 0; + var resolve; + var promise = new Promise(function() { + resolve = arguments[0]; + }); + return new Promise(function(resolve) { + promise.then(function() { + called++; + promise.then(function(){ + called++; + }); + promise.then(function(){ + called++; + assert.equal(4, called); + resolve(); + }); + }); + + promise.then(function() { + called++; + }); + + setTimeout(resolve, 1); + }); + + }); + + specify("should be able to nest arbitrary amount of then handlers on already resolved promises", function() { + var called = 0; + var resolve; + var promise = Promise.resolve(); + return new Promise(function(resolve) { + promise.then(function() { + called++; + promise.then(function(){ + called++; + promise.then(function(){ + called++; + }); + promise.then(function(){ + called++; + }); + }); + promise.then(function(){ + promise.then(function(){ + called++; + }); + promise.then(function(){ + called++; + assert.equal(8, called); + resolve(); + }); + called++; + }); + }); + + promise.then(function() { + called++; + }); + }); + }); + + specify("github-682", function() { + var o = { + then: function(f) { + setTimeout(function() { + delete o.then; + f(o); + }, 1); + } + }; + + return Promise.resolve(o).then(function(value) { + assert.equal(o, value); + }); + }); + + specify("gh-1006", function() { + return Promise.resolve().then(function() { + new Promise(function() {}).tap(function() {}).cancel(); + }); + }); + + if (testUtils.isNodeJS) { + describe("github-689", function() { + var originalProperty = Object.getOwnPropertyDescriptor(process, "domain"); + var bindCalls = 0; + + beforeEach(function() { + bindCalls = 0; + }); + + before(function() { + Object.defineProperty(process, "domain", { + writable: true, + enumerable: true, + configurable: true, + value: { + emit: function() {}, + bind: function(fn) { + bindCalls++; + // Ensure non-strict mode. + return new Function("fn", "return function() {return fn.apply(this, arguments);}")(fn); + }, + enter: function() {}, + exit: function() {} + } + }); + }); + + after(function() { + Object.defineProperty(process, "domain", originalProperty); + }); + + specify(".return", function() { + return Promise.resolve().thenReturn(true).then(function(val) { + assert.strictEqual(val, true); + assert.strictEqual(bindCalls, 4); + }); + }); + + specify(".throw", function() { + return Promise.resolve().thenThrow(true).then(assert.fail, function(err) { + assert.strictEqual(err, true); + assert.strictEqual(bindCalls, 5); + }); + }); + + specify(".finally", function() { + return Promise.resolve(true).lastly(function() { + return Promise.delay(1); + }).then(function(val) { + assert.strictEqual(val, true); + assert.strictEqual(bindCalls, 6); + }); + }); + }); + + describe("long promise chain stack overflow", function() { + specify("mapSeries", function() { + var array = new Array(5000); + for (var i = 0; i < array.length; ++i) { + array[i] = null; + } + + var theError = new Error(); + + var queryAsync = Promise.promisify(function(cb) { + process.nextTick(function() { + cb(theError); + }, 1); + }); + + return Promise.mapSeries(array, function() { + return queryAsync(); + }).caught(function(e) { + assert.strictEqual(e.cause, theError); + }); + }); + }); + } + + +}); diff --git a/test/mocha/rejections.js b/test/mocha/rejections.js new file mode 100644 index 0000000..a33ec3d --- /dev/null +++ b/test/mocha/rejections.js @@ -0,0 +1,220 @@ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +describe("Using as a rejection reason", function() { + var nullObject = (function() { + var es5 = (function(){"use strict"; + return this; + })() === undefined; + if (es5) { + return function() { + return Object.create(null); + }; + } else { + return function() { + return {}; + }; + } + })(); + describe("Object.create(null)", function() { + specify("directly", function() { + var o = nullObject(); + return Promise.reject(o).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through constructor by throw", function() { + var o = nullObject(); + return new Promise(function() { + throw o; + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + + specify("through constructor immediately", function() { + var o = nullObject(); + return new Promise(function() { + arguments[1](o); + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through constructor eventually", function() { + var o = nullObject(); + return new Promise(function(_, r) { + setTimeout(function() { + r(o); + }, 1); + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through defer immediately", function() { + var o = nullObject(); + var d = Promise.defer(); + var ret = d.promise.then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + d.reject(o); + return ret; + }); + + specify("through defer eventually", function() { + var o = nullObject(); + var d = Promise.defer(); + var ret = d.promise.then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + setTimeout(function() { + d.reject(o); + }, 1); + return ret; + }); + + specify("through thenThrow immediately", function() { + var o = nullObject(); + return Promise.resolve().thenThrow(o).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through handler throw", function() { + var o = nullObject(); + return Promise.resolve().then(function() { + throw o; + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through handler-returned-promise immediately", function() { + var o = nullObject(); + return Promise.resolve().then(function() { + return Promise.reject(o); + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through handler-returned-promise eventually", function() { + var o = nullObject(); + return Promise.resolve().then(function() { + return new Promise(function(_, r) { + setTimeout(function() { + r(o); + }, 1); + }); + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through handler-returned-thenable throw", function() { + var o = nullObject(); + return Promise.resolve().then(function() { + return { + then: function(_, r) { + throw o; + } + }; + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through handler-returned-thenable immediately", function() { + var o = nullObject(); + return Promise.resolve().then(function() { + return { + then: function(_, r) { + r(o); + } + }; + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through handler-returned-thenable eventually", function() { + var o = nullObject(); + return Promise.resolve().then(function() { + return { + then: function(_, r) { + setTimeout(function() { + r(o); + }, 1); + } + }; + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + var BluebirdThenable = require("../../js/debug/promise.js")(); + specify("through handler-returned-bluebird-thenable immediately", function() { + var o = nullObject(); + return Promise.resolve().then(function() { + return BluebirdThenable.reject(o); + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through handler-returned-bluebird-thenable eventually", function() { + var o = nullObject(); + return Promise.resolve().then(function() { + return new BluebirdThenable(function(_, r) { + setTimeout(function() { + r(o); + }, 1); + }); + }).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through promisified callback immediately", function() { + var o = nullObject(); + return Promise.promisify(function(cb) { + cb(o); + })().then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through immediate PromiseArray promise", function() { + var o = nullObject(); + return Promise.all([Promise.reject(o)]).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through eventual PromiseArray promise", function() { + var o = nullObject(); + return Promise.all([new Promise(function(_, r) { + setTimeout(function() { + r(o); + }, 1); + })]).then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + specify("through promisified callback eventually", function() { + var o = nullObject(); + return Promise.promisify(function(cb) { + setTimeout(function() { + cb(o); + }, 1); + })().then(assert.fail, function(e) { + assert.strictEqual(e, o); + }); + }); + + }); +}); diff --git a/test/mocha/resolution.js b/test/mocha/resolution.js new file mode 100644 index 0000000..1b70668 --- /dev/null +++ b/test/mocha/resolution.js @@ -0,0 +1,434 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +var getValues = function() { + var d = Promise.defer(); + var f = Promise.resolve(3); + var r = Promise.reject(3); + + setTimeout(function(){ + d.resolve(3); + }, 1); + + return { + value: 3, + thenableFulfill: {then: function(fn){setTimeout(function(){fn(3)}, 1);}}, + thenableReject: {then: function(_, fn){setTimeout(function(){fn(3)}, 1);}}, + promiseFulfilled: f, + promiseRejected: r, + promiseEventual: d.promise + }; +}; + +function expect(count, callback) { + var cur = 0; + return new Promise(function() { + + }); +} + +function expect(count, done) { + var total = 0; + return function() { + total++; + if (total >= count) { + } + } +} + +describe("Promise.resolve", function() { + specify("follows thenables and promises", function() { + var values = getValues(); + var async = false; + var ret = Promise.all([ + Promise.resolve(values.value).then(testUtils.noop), + Promise.resolve(values.thenableFulfill).then(testUtils.noop), + Promise.resolve(values.thenableReject).then(assert.fail, testUtils.noop), + Promise.resolve(values.promiseFulfilled).then(testUtils.noop), + Promise.resolve(values.promiseRejected).then(assert.fail, testUtils.noop), + Promise.resolve(values.promiseEventual).then(testUtils.noop) + ]).then(function(v) { + assert.deepEqual(v, [3, 3, 3, 3, 3, 3]); + assert(async); + }); + async = true; + return ret; + }); +}); + +describe("Cast thenable", function() { + var b = { + then: function(f, fn){ + fn(b); + } + }; + + specify("rejects with itself", function() { + var promise = Promise.cast(b); + + return promise.then(assert.fail, function(v){ + assert(v === b); + }); + }); +}); + +describe("Implicitly cast thenable", function() { + var b = { + then: function(f, fn){ + fn(b); + } + }; + + specify("rejects with itself", function() { + return Promise.resolve().then(function(){ + return b; + }).then(assert.fail, function(v){ + assert(v === b); + }); + }); +}); + + + + +/*! + * +Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ +describe("propagation", function () { + + it("propagate through then with no callback", function () { + return Promise.resolve(10) + .then() + .then(function (ten) { + assert.equal(ten,10); + }); + }); + + it("propagate through then with modifying callback", function () { + return Promise.resolve(10) + .then(function (ten) { + return ten + 10; + }) + .then(function (twen) { + assert.equal(twen,20); + }); + }); + + it("errback recovers from exception", function () { + var error = new Error("Bah!"); + return Promise.reject(error) + .then(null, function (_error) { + assert.equal(_error,error); + return 10; + }) + .then(function (value) { + assert.equal(value,10); + }); + }); + + it("rejection propagates through then with no errback", function () { + var error = new Error("Foolish mortals!"); + return Promise.reject(error) + .then() + .then(null, function (_error) { + assert.equal(_error,error); + }); + }); + + it("rejection intercepted and rethrown", function () { + var error = new Error("Foolish mortals!"); + var nextError = new Error("Silly humans!"); + return Promise.reject(error) + .caught(function () { + throw nextError; + }) + .then(null, function (_error) { + assert.equal(_error,nextError); + }); + }); + + it("resolution is forwarded through deferred promise", function () { + var a = Promise.defer(); + var b = Promise.defer(); + a.resolve(b.promise); + b.resolve(10); + return a.promise.then(function (eh) { + assert.equal(eh, 10); + }); + }); +}); + +/* +Based on When.js tests + +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var sentinel = {}; +var other = {}; +describe("Promise.defer-test", function () { + + + specify("should fulfill with an immediate value", function() { + var d = Promise.defer(); + d.resolve(sentinel); + return d.promise.then( + function(val) { + assert.equal(val, sentinel); + }, + assert.fail + ); + }); + + specify("should return a promise for the resolution value", function() { + var d = Promise.defer(); + + d.resolve(sentinel); + return d.promise.then( + function(returnedPromiseVal) { + assert.deepEqual(returnedPromiseVal, sentinel); + }, + assert.fail + ); + }); + + specify("should return a promise for a promised resolution value", function() { + var d = Promise.defer(); + + d.resolve(Promise.resolve(sentinel)) + return d.promise.then( + function(returnedPromiseVal) { + assert.deepEqual(returnedPromiseVal, sentinel); + }, + assert.fail + ); + }); + + specify("should return a promise for a promised rejection value", function() { + var d = Promise.defer(); + + // Both the returned promise, and the deferred's own promise should + // be rejected with the same value + d.resolve(Promise.reject(sentinel)) + return d.promise.then( + assert.fail, + function(returnedPromiseVal) { + assert.deepEqual(returnedPromiseVal, sentinel); + } + ); + }); + + specify("should invoke newly added callback when already resolved", function() { + var d = Promise.defer(); + + d.resolve(sentinel); + + return d.promise.then( + function(val) { + assert.equal(val, sentinel); + }, + assert.fail + ); + }); + + + + specify("should reject with an immediate value", function() { + var d = Promise.defer(); + d.reject(sentinel); + return d.promise.then( + assert.fail, + function(val) { + assert.equal(val, sentinel); + } + ); + }); + + specify("should reject with fulfilled promised", function() { + var d, expected; + + d = Promise.defer(); + expected = testUtils.fakeResolved(sentinel); + + var ret = d.promise.then( + assert.fail, + function(val) { + assert.equal(val, expected); + } + ); + + d.reject(expected); + return ret; + }); + + specify("should reject with rejected promise", function() { + var d, expected; + + d = Promise.defer(); + expected = testUtils.fakeRejected(sentinel); + + var ret = d.promise.then( + assert.fail, + function(val) { + assert.equal(val, expected); + } + ); + + d.reject(expected); + return ret; + }); + + + specify("should return a promise for the rejection value", function() { + var d = Promise.defer(); + + // Both the returned promise, and the deferred's own promise should + // be rejected with the same value + d.reject(sentinel); + return d.promise.then( + assert.fail, + function(returnedPromiseVal) { + assert.deepEqual(returnedPromiseVal, sentinel); + } + ); + }); + + specify("should invoke newly added errback when already rejected", function() { + var d = Promise.defer(); + + d.reject(sentinel); + + return d.promise.then( + assert.fail, + function (val) { + assert.deepEqual(val, sentinel); + } + ); + }); +}); + +describe("Promise.fromNode", function() { + specify("rejects thrown errors from resolver", function() { + var err = new Error(); + return Promise.fromNode(function(callback) { + throw err; + }).then(assert.fail, function(e) { + assert.strictEqual(err, e); + }); + }); + specify("rejects rejections as operational errors", function() { + var err = new Error(); + return Promise.fromNode(function(callback) { + callback(err); + }).caught(Promise.OperationalError, function(e) { + assert.strictEqual(err, e.cause); + }); + }); + specify("resolves normally", function() { + var result = {}; + return Promise.fromNode(function(callback) { + callback(null, result); + }).then(function(res) { + assert.strictEqual(result, res); + }); + }); + specify("resolves with bound thunk", function() { + var nodeFn = function(param, cb) { + setTimeout(function() { + cb(null, param); + }, 1); + }; + + return Promise.fromNode(nodeFn.bind(null, 1)).then(function(res) { + assert.strictEqual(1, res); + }); + }); + specify("multiArgs option enabled single value", function() { + var nodeFn = function(cb) { + cb(null, 1); + }; + return Promise.fromNode(function(callback) { + nodeFn(callback); + }, {multiArgs: true}).then(function(value) { + assert.deepEqual([1], value); + }); + + }); + specify("multiArgs option enabled multi value", function() { + var nodeFn = function(cb) { + cb(null, 1, 2, 3); + }; + return Promise.fromNode(function(callback) { + nodeFn(callback); + }, {multiArgs: true}).then(function(value) { + assert.deepEqual([1,2,3], value); + }); + + }); + specify("multiArgs option disabled single value", function() { + var nodeFn = function(cb) { + cb(null, 1); + }; + return Promise.fromNode(function(callback) { + nodeFn(callback); + }).then(function(value) { + assert.strictEqual(1, value); + }); + + }); + specify("multiArgs option disabled multi value", function() { + var nodeFn = function(cb) { + cb(null, 1, 2, 3); + }; + return Promise.fromNode(function(callback) { + nodeFn(callback); + }).then(function(value) { + assert.strictEqual(1, value); + }); + + }); +}); diff --git a/test/mocha/schedule.js b/test/mocha/schedule.js new file mode 100644 index 0000000..7af8b12 --- /dev/null +++ b/test/mocha/schedule.js @@ -0,0 +1,48 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var schedule = require("../../js/debug/schedule"); +var isNodeJS = testUtils.isNodeJS; + +describe("schedule", function () { + if (isNodeJS) { + describe("for Node.js", function () { + it("should preserve the active domain", function() { + var domain = require("domain"); + var activeDomain = domain.create(); + return new Promise(function(resolve) { + activeDomain.run(function () { + schedule(function () { + assert(domain.active); + assert.equal(domain.active, activeDomain); + resolve(); + }); + }); + }); + + }); + }); + + describe("Promise.setScheduler", function() { + it("should work with synchronous scheduler", function() { + var prev = Promise.setScheduler(function(task) { + task(); + }); + var success = false; + Promise.resolve().then(function() { + success = true; + }); + assert(success); + Promise.setScheduler(prev); + }); + it("should throw for non function", function() { + try { + Promise.setScheduler({}); + } catch (e) { + return Promise.resolve(); + } + assert.fail(); + }); + }); + } +}); diff --git a/test/mocha/settle.js b/test/mocha/settle.js new file mode 100644 index 0000000..6ffb0f4 --- /dev/null +++ b/test/mocha/settle.js @@ -0,0 +1,172 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +/*! + * +Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ + +describe("allSettled", function () { + it("works on an empty array", function () { + return Promise.settle([]) + .then(function (snapshots) { + assert.deepEqual(snapshots, []); + }); + }); + + it("deals with a mix of non-promises and promises", function () { + return Promise.settle([1, Promise.resolve(2), Promise.reject(3)]) + .then(function (snapshots) { + assert.equal(snapshots[0].value(), 1); + assert.equal(snapshots[1].value(), 2); + assert.equal(snapshots[2].error(), 3); + }); + }); + + it("is settled after every constituent promise is settled", function () { + var toFulfill = Promise.defer(); + var toReject = Promise.defer(); + var promises = [toFulfill.promise, toReject.promise]; + var fulfilled; + var rejected; + + Promise.attempt(function () { + toReject.reject(); + rejected = true; + }) + .delay(1) + .then(function () { + toFulfill.resolve(); + fulfilled = true; + }); + + return Promise.settle(promises) + .then(function () { + assert.equal(fulfilled, true); + assert.equal(rejected, true); + }); + }); + + it("does not modify the input array", function () { + var input = [1, Promise.resolve(2), Promise.reject(3)]; + + return Promise.settle(input) + .then(function (snapshots) { + assert.notEqual(snapshots, input); + assert.equal(snapshots[0].value(), 1); + assert.equal(snapshots[1].value(), 2); + assert.equal(snapshots[2].error(), 3); + }); + }); + +}); + +/* +Based on When.js tests + +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var sentinel = {}; +var other = {}; +describe("Promise.settle-test", function () { + + + Promise.promise = function(rs){ + var a = Promise.defer(); + rs(a); + return a.promise; + }; + + + specify("should settle empty array", function() { + return Promise.settle([]).then(function(settled) { + assert.deepEqual(settled.length, 0); + }); + }); + + specify("should reject if promise for input array rejects", function() { + return Promise.settle(Promise.reject(sentinel)).then( + assert.fail, + function(reason) { + assert.equal(reason, sentinel); + } + ); + }); + + specify("should settle values", function() { + var array = [0, 1, sentinel]; + return Promise.settle(array).then(function(settled) { + testUtils.assertFulfilled(settled[0], 0); + testUtils.assertFulfilled(settled[1], 1); + testUtils.assertFulfilled(settled[2], sentinel); + }); + }); + + specify("should settle promises", function() { + var array = [0, Promise.resolve(sentinel), Promise.reject(sentinel)]; + return Promise.settle(array).then(function(settled) { + testUtils.assertFulfilled(settled[0], 0); + testUtils.assertFulfilled(settled[1], sentinel); + testUtils.assertRejected(settled[2], sentinel); + }); + }); + + specify("returned promise should fulfill once all inputs settle", function() { + var array, p1, p2, resolve, reject; + + p1 = Promise.promise(function(r) { resolve = function(a){r.fulfill(a);}; }); + p2 = Promise.promise(function(r) { reject = function(a){r.reject(a);}; }); + + array = [0, p1, p2]; + + setTimeout(function() { resolve(sentinel); }, 0); + setTimeout(function() { reject(sentinel); }, 0); + + return Promise.settle(array).then(function(settled) { + testUtils.assertFulfilled(settled[0], 0); + testUtils.assertFulfilled(settled[1], sentinel); + testUtils.assertRejected(settled[2], sentinel); + }); + }); +}); diff --git a/test/mocha/some.js b/test/mocha/some.js new file mode 100644 index 0000000..ee91c0c --- /dev/null +++ b/test/mocha/some.js @@ -0,0 +1,174 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +describe("Promise.some", function(){ + it("should reject on negative number", function(){ + return Promise.some([1,2,3], -1) + .then(assert.fail) + .caught(Promise.TypeError, function(){ + }); + }); + + it("should reject on NaN", function(){ + return Promise.some([1,2,3], -0/0) + .then(assert.fail) + .caught(Promise.TypeError, function(){ + }); + }); + + it("should reject on non-array", function(){ + return Promise.some({}, 2) + .then(assert.fail) + .caught(Promise.TypeError, function(){ + }); + }); + + it("should reject with rangeerror when impossible to fulfill", function(){ + return Promise.some([1,2,3], 4) + .then(assert.fail) + .caught(Promise.RangeError, function(e){ + }); + }); + + it("should fulfill with empty array with 0", function(){ + return Promise.some([1,2,3], 0).then(function(result){ + assert.deepEqual(result, []); + }); + }); +}); + +/* +Based on When.js tests + +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ + +var RangeError = Promise.RangeError; + +describe("Promise.some-test", function () { + + specify("should reject empty input", function() { + return Promise.some([], 1).caught(RangeError, function() { + }); + }); + + specify("should resolve values array", function() { + var input = [1, 2, 3]; + return Promise.some(input, 2).then( + function(results) { + assert(testUtils.isSubset(results, input)); + }, + assert.fail + ) + }); + + specify("should resolve promises array", function() { + var input = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + return Promise.some(input, 2).then( + function(results) { + assert(testUtils.isSubset(results, [1, 2, 3])); + }, + assert.fail + ) + }); + + specify("should not resolve sparse array input", function() { + var input = [, 1, , 2, 3 ]; + return Promise.some(input, 2).then( + function(results) { + assert.deepEqual(results, [void 0, 1]); + }, + function() { + console.error(arguments); + assert.fail(); + } + ) + }); + + specify("should reject with all rejected input values if resolving howMany becomes impossible", function() { + var input = [Promise.resolve(1), Promise.reject(2), Promise.reject(3)]; + return Promise.some(input, 2).then( + assert.fail, + function(err) { + //Cannot use deep equality in IE8 because non-enumerable properties are not + //supported + assert(err[0] === 2); + assert(err[1] === 3); + } + ) + }); + + specify("should reject with aggregateError", function() { + var input = [Promise.resolve(1), Promise.reject(2), Promise.reject(3)]; + var AggregateError = Promise.AggregateError; + return Promise.some(input, 2) + .then(assert.fail) + .caught(AggregateError, function(e) { + assert(e[0] === 2); + assert(e[1] === 3); + assert(e.length === 2); + }); + }); + + specify("aggregate error should be caught in .error", function() { + var input = [Promise.resolve(1), Promise.reject(2), Promise.reject(3)]; + var AggregateError = Promise.AggregateError; + return Promise.some(input, 2) + .then(assert.fail) + .error(function(e) { + assert(e[0] === 2); + assert(e[1] === 3); + assert(e.length === 2); + }); + }); + + specify("should accept a promise for an array", function() { + var expected, input; + + expected = [1, 2, 3]; + input = Promise.resolve(expected); + + return Promise.some(input, 2).then( + function(results) { + assert.deepEqual(results.length, 2); + }, + assert.fail + ) + }); + + specify("should reject when input promise does not resolve to array", function() { + return Promise.some(Promise.resolve(1), 1).caught(TypeError, function(e){ + }); + }); + + specify("should reject when given immediately rejected promise", function() { + var err = new Error(); + return Promise.some(Promise.reject(err), 1).then(assert.fail, function(e) { + assert.strictEqual(err, e); + }); + }); +}); diff --git a/test/mocha/spread.js b/test/mocha/spread.js new file mode 100644 index 0000000..8c23efd --- /dev/null +++ b/test/mocha/spread.js @@ -0,0 +1,304 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +/*! + * +Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ + +describe("spread", function () { + it("spreads values across arguments", function () { + return Promise.resolve([1, 2, 3]).spread(function (a, b) { + assert.equal(b,2); + }); + }); + + it("spreads promises for arrays across arguments", function () { + return Promise.resolve([Promise.resolve(10)]) + .all() + .spread(function (value) { + assert.equal(value,10); + }); + }); + + it("spreads arrays of promises across arguments", function () { + var deferredA = Promise.defer(); + var deferredB = Promise.defer(); + + var promise = Promise.resolve([deferredA.promise, deferredB.promise]).all().spread( + function (a, b) { + assert.equal(a,10); + assert.equal(b,20); + }); + + Promise.delay(1).then(function () { + deferredA.resolve(10); + }); + Promise.delay(1).then(function () { + deferredB.resolve(20); + }); + + return promise; + }); + + it("spreads arrays of thenables across arguments", function () { + var p1 = { + then: function(v) { + v(10); + } + }; + var p2 = { + then: function(v) { + v(20); + } + }; + + var promise = Promise.resolve([p1, p2]).all().spread(function (a, b) { + assert.equal(a,10); + assert.equal(b,20); + }); + return promise; + }); + + it("should wait for promises in the returned array even when not calling .all", function() { + var d1 = Promise.defer(); + var d2 = Promise.defer(); + var d3 = Promise.defer(); + setTimeout(function(){ + d1.resolve(1); + d2.resolve(2); + d3.resolve(3); + }, 1); + return Promise.resolve().then(function(){ + return [d1.promise, d2.promise, d3.promise]; + }).all().spread(function(a, b, c){ + assert(a === 1); + assert(b === 2); + assert(c === 3); + }); + }); + + it("should wait for thenables in the returned array even when not calling .all", function() { + var t1 = { + then: function(fn) { + setTimeout(function(){ + fn(1); + }, 1); + } + }; + var t2 = { + then: function(fn) { + setTimeout(function(){ + fn(2); + }, 1); + } + }; + var t3 = { + then: function(fn) { + setTimeout(function(){ + fn(3); + }, 1); + } + }; + return Promise.resolve().then(function(){ + return [t1, t2, t3]; + }).all().spread(function(a, b, c){ + assert(a === 1); + assert(b === 2); + assert(c === 3); + }); + }); + + it("should wait for promises in an array that a returned promise resolves to even when not calling .all", function() { + var d1 = Promise.defer(); + var d2 = Promise.defer(); + var d3 = Promise.defer(); + var defer = Promise.defer(); + + setTimeout(function(){ + defer.resolve([d1.promise, d2.promise, d3.promise]); + setTimeout(function(){ + d1.resolve(1); + d2.resolve(2); + d3.resolve(3); + }, 1); + }, 1); + + return Promise.resolve().then(function(){ + return defer.promise; + }).all().spread(function(a, b, c){ + assert(a === 1); + assert(b === 2); + assert(c === 3); + }); + }); + + it("should wait for thenables in an array that a returned thenable resolves to even when not calling .all", function() { + var t1 = { + then: function(fn) { + setTimeout(function(){ + fn(1); + }, 1); + } + }; + var t2 = { + then: function(fn) { + setTimeout(function(){ + fn(2); + }, 1); + } + }; + var t3 = { + then: function(fn) { + setTimeout(function(){ + fn(3); + }, 1); + } + }; + + var thenable = { + then: function(fn) { + setTimeout(function(){ + fn([t1, t2, t3]) + }, 1); + } + }; + + return Promise.resolve().then(function(){ + return thenable; + }).all().spread(function(a, b, c){ + assert(a === 1); + assert(b === 2); + assert(c === 3); + }); + }); + + it("should reject with error when non array is the ultimate value to be spread", function(){ + return Promise.resolve().then(function(){ + return 3 + }).spread(function(a, b, c){ + assert.fail(); + }).then(assert.fail, function(e){ + }) + }); + + specify("gh-235", function() { + var P = Promise; + return P.resolve(1).then(function(x) { + return [x, P.resolve(2)] + }).spread(function(x, y) { + return P.all([P.resolve(3), P.resolve(4)]); + }).then(function(a) { + assert.deepEqual([3, 4], a); + }); + }) + + specify("error when passed non-function", function() { + return Promise.resolve(3) + .spread() + .then(assert.fail) + .caught(Promise.TypeError, function() {}); + }); + + specify("error when resolution is non-spredable", function() { + return Promise.resolve(3) + .spread(function(){}) + .then(assert.fail) + .caught(Promise.TypeError, function() {}); + }); +}); + +/* +Based on When.js tests + +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ + +describe("Promise.spread-test", function () { + var slice = [].slice; + + specify("should return a promise", function() { + assert(typeof (Promise.defer().promise.spread(function(){}).then) === "function"); + }); + + specify("should apply onFulfilled with array as argument list", function() { + var expected = [1, 2, 3]; + return Promise.resolve(expected).spread(function() { + assert.deepEqual(slice.call(arguments), expected); + }); + }); + + specify("should resolve array contents", function() { + var expected = [Promise.resolve(1), 2, Promise.resolve(3)]; + return Promise.resolve(expected).all().spread(function() { + assert.deepEqual(slice.call(arguments), [1, 2, 3]); + }); + }); + + specify("should reject if any item in array rejects", function() { + var expected = [Promise.resolve(1), 2, Promise.reject(3)]; + return Promise.resolve(expected).all() + .spread(assert.fail) + .then(assert.fail, function() {}); + }); + + specify("should apply onFulfilled with array as argument list", function() { + var expected = [1, 2, 3]; + return Promise.resolve(Promise.resolve(expected)).spread(function() { + assert.deepEqual(slice.call(arguments), expected); + }); + }); + + specify("should resolve array contents", function() { + var expected = [Promise.resolve(1), 2, Promise.resolve(3)]; + return Promise.resolve(Promise.resolve(expected)).all().spread(function() { + assert.deepEqual(slice.call(arguments), [1, 2, 3]); + }); + }); + + specify("should reject if input is a rejected promise", function() { + var expected = Promise.reject([1, 2, 3]); + return Promise.resolve(expected) + .spread(assert.fail) + .then(assert.fail, function() {}); + }); +}); diff --git a/test/mocha/synchronous_inspection.js b/test/mocha/synchronous_inspection.js new file mode 100644 index 0000000..d02705b --- /dev/null +++ b/test/mocha/synchronous_inspection.js @@ -0,0 +1,144 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +describe("Promise.prototype.toJSON", function() { + it("should match pending state", function() { + var a = new Promise(function(){}).toJSON(); + assert.strictEqual(a.isFulfilled, false); + assert.strictEqual(a.isRejected, false); + assert.strictEqual(a.rejectionReason, undefined); + assert.strictEqual(a.fulfillmentValue, undefined); + }); + it("should match rejected state", function() { + var a = Promise.reject(3).toJSON(); + assert.strictEqual(a.isFulfilled, false); + assert.strictEqual(a.isRejected, true); + assert.strictEqual(a.rejectionReason, 3); + assert.strictEqual(a.fulfillmentValue, undefined); + }); + it("should match fulfilled state", function() { + var a = Promise.resolve(3).toJSON(); + assert.strictEqual(a.isFulfilled, true); + assert.strictEqual(a.isRejected, false); + assert.strictEqual(a.rejectionReason, undefined); + assert.strictEqual(a.fulfillmentValue, 3); + }); +}); +/*! + * +Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ +// In browsers that support strict mode, it'll be `undefined`; otherwise, the global. +var calledAsFunctionThis = (function () { return this; }()); +describe("inspect", function () { + + it("for a fulfilled promise", function () { + var ret = Promise.resolve(10); + assert.equal(ret.value(), 10); + assert.equal(ret.isFulfilled(), true); + }); + + it("for a rejected promise", function () { + var e = new Error("In your face."); + var ret = Promise.reject(e); + assert.equal(ret.reason(), e); + assert.equal(ret.isRejected(), true); + return ret.then(assert.fail, function(){}); + }); + + it("for a pending, unresolved promise", function () { + var pending = Promise.defer().promise; + assert.equal(pending.isPending(), true); + }); + + it("for a promise resolved to a rejected promise", function () { + var deferred = Promise.defer(); + var error = new Error("Rejected!"); + var reject = Promise.reject(error); + deferred.resolve(reject); + + assert.equal(deferred.promise.isRejected(), true); + assert.equal(deferred.promise.reason(), error); + return deferred.promise.then(assert.fail, function(){}); + }); + + it("for a promise resolved to a fulfilled promise", function () { + var deferred = Promise.defer(); + var fulfilled = Promise.resolve(10); + deferred.resolve(fulfilled); + + assert.equal(deferred.promise.isFulfilled(), true); + assert.equal(deferred.promise.value(), 10); + }); + + it("for a promise resolved to a pending promise", function () { + var a = Promise.defer(); + var b = Promise.defer(); + a.resolve(b.promise); + + assert.equal(a.promise.isPending(), true); + }); + + describe(".value()", function() { + specify("of unfulfilled inspection should throw", function() { + Promise.reject(1).reflect().then(function(inspection) { + try { + inspection.value(); + } catch (e) { + return Promise.resolve(); + } + assert.fail(); + }); + }); + specify("of unfulfilled promise should throw", function() { + var r = Promise.reject(1); + r.reason(); + try { + r.value(); + } catch (e) { + return Promise.resolve(); + } + assert.fail(); + }); + }); + + describe(".reason()", function() { + specify("of unrejected inspection should throw", function() { + Promise.resolve(1).reflect().then(function(inspection) { + try { + inspection.reason(); + } catch (e) { + return Promise.resolve(); + } + assert.fail(); + }); + }); + + specify("of unrejected promise should throw", function() { + try { + Promise.resolve(1).reason(); + } catch (e) { + return Promise.resolve(); + } + assert.fail(); + }); + }); +}); diff --git a/test/mocha/tap.js b/test/mocha/tap.js new file mode 100644 index 0000000..d4f70a7 --- /dev/null +++ b/test/mocha/tap.js @@ -0,0 +1,66 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + + +describe("tap", function () { + specify("passes through value", function() { + return Promise.resolve("test").tap(function() { + return 3; + }).then(function(value){ + assert.equal(value, "test"); + }); + }); + + specify("passes through value after returned promise is fulfilled", function() { + var async = false; + return Promise.resolve("test").tap(function() { + return new Promise(function(r) { + setTimeout(function(){ + async = true; + r(3); + }, 1); + }); + }).then(function(value){ + assert(async); + assert.equal(value, "test"); + }); + }); + + specify("is not called on rejected promise", function() { + var called = false; + return Promise.reject("test").tap(function() { + called = true; + }).then(assert.fail, function(value){ + assert(!called); + }); + }); + + specify("passes immediate rejection", function() { + var err = new Error(); + return Promise.resolve("test").tap(function() { + throw err; + }).tap(assert.fail).then(assert.fail, function(e){ + assert(err === e); + }); + }); + + specify("passes eventual rejection", function() { + var err = new Error(); + return Promise.resolve("test").tap(function() { + return new Promise(function(_, rej) { + setTimeout(function(){ + rej(err); + }, 1) + }); + }).tap(assert.fail).then(assert.fail, function(e) { + assert(err === e); + }); + }); + + specify("passes value", function() { + return Promise.resolve(123).tap(function(a) { + assert(a === 123); + }); + }); +}); diff --git a/test/mocha/tapCatch.js b/test/mocha/tapCatch.js new file mode 100644 index 0000000..e141ea4 --- /dev/null +++ b/test/mocha/tapCatch.js @@ -0,0 +1,130 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +function rejection() { + var error = new Error("test"); + var rejection = Promise.reject(error); + rejection.err = error; + return rejection; +} + +describe("tapCatch", function () { + + specify("passes through rejection reason", function() { + return rejection().tapCatch(function() { + return 3; + }).caught(function(value) { + assert.equal(value.message, "test"); + }); + }); + + specify("passes through reason after returned promise is fulfilled", function() { + var async = false; + return rejection().tapCatch(function() { + return new Promise(function(r) { + setTimeout(function(){ + async = true; + r(3); + }, 1); + }); + }).caught(function(value) { + assert(async); + assert.equal(value.message, "test"); + }); + }); + + specify("is not called on fulfilled promise", function() { + var called = false; + return Promise.resolve("test").tapCatch(function() { + called = true; + }).then(function(value){ + assert(!called); + }, assert.fail); + }); + + specify("passes immediate rejection", function() { + var err = new Error(); + return rejection().tapCatch(function() { + throw err; + }).tap(assert.fail).then(assert.fail, function(e) { + assert(err === e); + }); + }); + + specify("passes eventual rejection", function() { + var err = new Error(); + return rejection().tapCatch(function() { + return new Promise(function(_, rej) { + setTimeout(function(){ + rej(err); + }, 1) + }); + }).tap(assert.fail).then(assert.fail, function(e) { + assert(err === e); + }); + }); + + specify("passes reason", function() { + return rejection().tapCatch(function(a) { + assert(a === rejection); + }).then(assert.fail, function() {}); + }); + + specify("Works with predicates", function() { + var called = false; + return Promise.reject(new TypeError).tapCatch(TypeError, function(a) { + called = true; + assert(err instanceof TypeError) + }).then(assert.fail, function(err) { + assert(called === true); + assert(err instanceof TypeError); + }); + }); + specify("Does not get called on predicates that don't match", function() { + var called = false; + return Promise.reject(new TypeError).tapCatch(ReferenceError, function(a) { + called = true; + }).then(assert.fail, function(err) { + assert(called === false); + assert(err instanceof TypeError); + }); + }); + + specify("Supports multiple predicates", function() { + var calledA = false; + var calledB = false; + var calledC = false; + + var promiseA = Promise.reject(new ReferenceError).tapCatch( + ReferenceError, + TypeError, + function (e) { + assert(e instanceof ReferenceError); + calledA = true; + } + ).catch(function () {}); + + var promiseB = Promise.reject(new TypeError).tapCatch( + ReferenceError, + TypeError, + function (e) { + assert(e instanceof TypeError); + calledB = true; + } + ).catch(function () {}); + + var promiseC = Promise.reject(new SyntaxError).tapCatch( + ReferenceError, + TypeError, + function (e) { + calledC = true; + } + ).catch(function () {}); + + return Promise.join(promiseA, promiseB, promiseC, function () { + assert(calledA === true); + assert(calledB === true); + assert(calledC === false); + }); + }) +}); diff --git a/test/mocha/timers.js b/test/mocha/timers.js new file mode 100644 index 0000000..17bbc87 --- /dev/null +++ b/test/mocha/timers.js @@ -0,0 +1,192 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var Q = Promise; +Promise.config({cancellation: true}) +var globalObject = typeof window !== "undefined" ? window : new Function("return this;")(); +/* +Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ + +describe("timeout", function () { + it("should do nothing if the promise fulfills quickly", function() { + Promise.delay(1).timeout(200).then(function(){ + }); + }); + + it("should do nothing if the promise rejects quickly", function() { + var goodError = new Error("haha!"); + return Promise.delay(1) + .then(function () { + throw goodError; + }) + .timeout(200) + .then(undefined, function (error) { + assert(error === goodError); + }); + }); + + it("should reject with a timeout error if the promise is too slow", function() { + return Promise.delay(1) + .timeout(10) + .caught(Promise.TimeoutError, function(){ + }) + }); + + it("should reject with a custom timeout error if the promise is too slow and msg was provided", function() { + return Promise.delay(1) + .timeout(10, "custom") + .caught(Promise.TimeoutError, function(e){ + assert(/custom/i.test(e.message)); + }); + }); + + it("should cancel the parent promise once the timeout expires", function() { + var didNotExecute = true; + var wasRejectedWithTimeout = false; + var p = Promise.delay(22).then(function() { + didNotExecute = false; + }) + p.timeout(11).thenReturn(10).caught(Promise.TimeoutError, function(e) { + wasRejectedWithTimeout = true; + }) + return Promise.delay(33).then(function() { + assert(didNotExecute, "parent promise was not cancelled"); + assert(wasRejectedWithTimeout, "promise was not rejected with timeout"); + }) + }); + + it("should not cancel the parent promise if there are multiple consumers", function() { + var derivedNotCancelled = false; + var p = Promise.delay(22); + var derived = p.then(function() { + derivedNotCancelled = true; + }) + p.timeout(11).thenReturn(10) + return Promise.delay(33).then(function() { + assert(derivedNotCancelled, "derived promise was cancelled") + }) + }) + + var globalsAreReflectedInGlobalObject = (function(window) { + var fn = function(id){return clearTimeout(id);}; + var old = window.clearTimeout; + window.clearTimeout = fn; + var ret = clearTimeout === fn; + window.clearTimeout = old; + return ret; + })(globalObject); + + if (globalsAreReflectedInGlobalObject) { + describe("timer handle clearouts", function() { + var fakeSetTimeout, fakeClearTimeout; + var expectedHandleType; + + before(function() { + fakeSetTimeout = globalObject.setTimeout; + fakeClearTimeout = globalObject.clearTimeout; + globalObject.setTimeout = globalObject.oldSetTimeout; + globalObject.clearTimeout = globalObject.oldClearTimeout; + expectedHandleType = typeof (globalObject.setTimeout(function(){}, 1)); + }); + + after(function() { + globalObject.setTimeout = fakeSetTimeout; + globalObject.clearTimeout = fakeClearTimeout; + }); + + it("should clear timeouts with proper handle type when fulfilled", function() { + var old = globalObject.clearTimeout; + var handleType = "empty"; + globalObject.clearTimeout = function(handle) { + handleType = typeof handle; + globalObject.clearTimeout = old; + }; + + return Promise.delay(1).timeout(10000).then(function() { + assert.strictEqual(expectedHandleType, handleType); + }); + }); + + it("should clear timeouts with proper handle type when rejected", function() { + var old = globalObject.clearTimeout; + var handleType = "empty"; + globalObject.clearTimeout = function(handle) { + handleType = typeof handle; + globalObject.clearTimeout = old; + }; + + return new Promise(function(_, reject) { + setTimeout(reject, 10); + }).timeout(10000).then(null, function() { + assert.strictEqual(expectedHandleType, handleType); + }); + }); + }) + + } +}); + +describe("delay", function () { + it("should not delay rejection", function() { + var promise = Promise.reject(5).delay(1); + + promise.then(assert.fail, function(){}); + + return Promise.delay(1).then(function () { + assert(!promise.isPending()); + }); + }); + + it("should delay after resolution", function () { + var promise1 = Promise.delay(1, "what"); + var promise2 = promise1.delay(1); + + return promise2.then(function (value) { + assert(value === "what"); + }); + }); + + it("should resolve follower promise's value", function() { + var resolveF; + var f = new Promise(function() { + resolveF = arguments[0]; + }); + var v = new Promise(function(f) { + setTimeout(function() { + f(3); + }, 1); + }); + resolveF(v); + return Promise.delay(1, f).then(function(value) { + assert.equal(value, 3); + }); + }); + + it("should reject with a custom error if an error was provided as a parameter", function() { + var err = Error("Testing Errors") + return Promise.delay(1) + .timeout(10, err) + .caught(function(e){ + assert(e === err); + }); + }); + +}); diff --git a/test/mocha/try.js b/test/mocha/try.js new file mode 100644 index 0000000..93087db --- /dev/null +++ b/test/mocha/try.js @@ -0,0 +1,73 @@ +"use strict"; + +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +var obj = {}; +var error = new Error(); +var thrower = function() { + throw error; +}; + +var identity = function(val) { + return val; +}; + +var array = function() { + return [].slice.call(arguments); +}; + +var receiver = function() { + return this; +}; + +var tryy = Promise["try"]; + +describe("Promise.attempt", function(){ + specify("should reject when the function throws", function() { + var async = false; + var ret = tryy(thrower).then(assert.fail, function(e) { + assert(async); + assert(e === error); + }); + async = true; + return ret; + }); + + specify("should reject when the function is not a function", function() { + var async = false; + var ret = tryy(null).then(assert.fail, function(e) { + assert(async); + assert(e instanceof Promise.TypeError); + }); + async = true; + return ret; + }); + + specify("should unwrap returned promise", function(){ + var d = Promise.defer(); + + var ret = tryy(function(){ + return d.promise; + }).then(function(v){ + assert(v === 3); + }) + + setTimeout(function(){ + d.fulfill(3); + }, 1); + return ret; + }); + specify("should unwrap returned thenable", function(){ + return tryy(function(){ + return { + then: function(f, v) { + f(3); + } + } + }).then(function(v){ + assert(v === 3); + }); + + }); +}); diff --git a/test/mocha/unhandled_rejections.js b/test/mocha/unhandled_rejections.js new file mode 100644 index 0000000..0745b5b --- /dev/null +++ b/test/mocha/unhandled_rejections.js @@ -0,0 +1,875 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); +var noop = testUtils.noop; +var isStrictModeSupported = testUtils.isStrictModeSupported; +var onUnhandledFail = testUtils.onUnhandledFail; +var onUnhandledSucceed = testUtils.onUnhandledSucceed; + +function yesE() { + return new Error(); +} + +function notE() { + var rets = [{}, []]; + return rets[Math.random()*rets.length|0]; +} + +function cleanUp() { + Promise.onPossiblyUnhandledRejection(null); + Promise.onUnhandledRejectionHandled(null); +} + +function setupCleanUps() { + beforeEach(cleanUp); + afterEach(cleanUp); +} + +describe("Will report rejections that are not handled in time", function() { + setupCleanUps(); + + specify("Immediately rejected not handled at all", function testFunction() { + var promise = Promise.defer(); + promise.reject(yesE()); + return onUnhandledSucceed(); + }); + + specify("Eventually rejected not handled at all", function testFunction() { + var promise = Promise.defer(); + setTimeout(function(){ + promise.reject(yesE()); + }, 1); + return onUnhandledSucceed(); + }); + + specify("Immediately rejected handled too late", function testFunction() { + var promise = Promise.defer(); + promise.reject(yesE()); + setTimeout(function() { + promise.promise.then(assert.fail, function(){}); + }, 150); + return onUnhandledSucceed(); + }); + specify("Eventually rejected handled too late", function testFunction() { + var promise = Promise.defer(); + setTimeout(function(){ + promise.reject(yesE()); + setTimeout(function() { + promise.promise.then(assert.fail, function(){}); + }, 150); + }, 1); + + return onUnhandledSucceed(); + }); +}); + +describe("Will report rejections that are code errors", function() { + setupCleanUps(); + + specify("Immediately fulfilled handled with erroneous code", function testFunction() { + var deferred = Promise.defer(); + var promise = deferred.promise; + deferred.fulfill(null); + promise.then(function(itsNull){ + itsNull.will.fail.four.sure(); + }); + return onUnhandledSucceed(); + }); + specify("Eventually fulfilled handled with erroneous code", function testFunction() { + var deferred = Promise.defer(); + var promise = deferred.promise; + setTimeout(function(){ + deferred.fulfill(null); + }, 1); + promise.then(function(itsNull){ + itsNull.will.fail.four.sure(); + }); + return onUnhandledSucceed(); + }); + + specify("Already fulfilled handled with erroneous code but then recovered and failDeferred again", function testFunction() { + var err = yesE(); + var promise = Promise.resolve(null); + promise.then(function(itsNull){ + itsNull.will.fail.four.sure(); + }).then(assert.fail, function(e){ + assert.ok(e instanceof Promise.TypeError); + }).then(function(){ + //then assert.failing again + //this error should be reported + throw err; + }); + return onUnhandledSucceed(err); + }); + + specify("Immediately fulfilled handled with erroneous code but then recovered and failDeferred again", function testFunction() { + var err = yesE(); + var deferred = Promise.defer(); + var promise = deferred.promise; + deferred.fulfill(null); + promise.then(function(itsNull){ + itsNull.will.fail.four.sure(); + }).then(assert.fail, function(e){ + assert.ok(e instanceof Promise.TypeError) + //Handling the type error here + }).then(function(){ + //then assert.failing again + //this error should be reported + throw err; + }); + return onUnhandledSucceed(err); + }); + + specify("Eventually fulfilled handled with erroneous code but then recovered and failDeferred again", function testFunction() { + var err = yesE(); + var deferred = Promise.defer(); + var promise = deferred.promise; + + promise.then(function(itsNull){ + itsNull.will.fail.four.sure(); + }).then(assert.fail, function(e){ + assert.ok(e instanceof Promise.TypeError) + //Handling the type error here + }).then(function(){ + //then assert.failing again + //this error should be reported + throw err; + }); + + setTimeout(function(){ + deferred.fulfill(null); + }, 1); + return onUnhandledSucceed(err); + }); + + specify("Already fulfilled handled with erroneous code but then recovered in a parallel handler and failDeferred again", function testFunction() { + var err = yesE(); + var promise = Promise.resolve(null); + promise.then(function(itsNull){ + itsNull.will.fail.four.sure(); + }).then(assert.fail, function(e){ + assert.ok(e instanceof Promise.TypeError) + }); + + promise.then(function(){ + //then assert.failing again + //this error should be reported + throw err; + }); + return onUnhandledSucceed(err); + }); +}); + +describe("Will report rejections that are not instanceof Error", function() { + setupCleanUps(); + + specify("Immediately rejected with non instanceof Error", function testFunction() { + var failDeferred = Promise.defer(); + failDeferred.reject(notE()); + return onUnhandledSucceed(); + }); + + specify("Eventually rejected with non instanceof Error", function testFunction() { + var failDeferred = Promise.defer(); + setTimeout(function(){ + failDeferred.reject(notE()); + }, 1); + return onUnhandledSucceed(); + }); +}); + +describe("Will handle hostile rejection reasons like frozen objects", function() { + setupCleanUps(); + + specify("Immediately rejected with non instanceof Error", function testFunction() { + var failDeferred = Promise.defer(); + failDeferred.reject(Object.freeze({})); + return onUnhandledSucceed(function(e) { + return true; + }); + }); + + + specify("Eventually rejected with non instanceof Error", function testFunction() { + var failDeferred = Promise.defer(); + var obj = {}; + setTimeout(function(){ + failDeferred.reject(Object.freeze(obj)); + }, 1); + return onUnhandledSucceed(function(e) { + return e === obj; + }); + }); +}); + + +describe("Will not report rejections that are handled in time", function() { + setupCleanUps(); + + specify("Already rejected handled", function testFunction() { + var failDeferred = Promise.reject(yesE()).caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("Immediately rejected handled", function testFunction() { + var failDeferred = Promise.defer(); + failDeferred.promise.caught(noop); + failDeferred.reject(yesE()); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + + specify("Eventually rejected handled", function testFunction() { + var failDeferred = Promise.defer(); + setTimeout(function() { + failDeferred.reject(yesE()); + }, 1); + failDeferred.promise.caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("Already rejected handled in a deep sequence", function testFunction() { + var failDeferred = Promise.reject(yesE()); + + failDeferred + .then(function(){}) + .then(function(){}, null, function(){}) + .then() + .then(function(){}) + .caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("Immediately rejected handled in a deep sequence", function testFunction() { + var failDeferred = Promise.defer(); + + failDeferred.promise.then(function(){}) + .then(function(){}, null, function(){}) + .then() + .then(function(){}) + .caught(noop); + + + failDeferred.reject(yesE()); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + + specify("Eventually handled in a deep sequence", function testFunction() { + var failDeferred = Promise.defer(); + setTimeout(function() { + failDeferred.reject(yesE()); + }, 1); + failDeferred.promise.then(function(){}) + .then(function(){}, null, function(){}) + .then() + .then(function(){}) + .caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + + specify("Already rejected handled in a middle parallel deep sequence", function testFunction() { + var failDeferred = Promise.reject(yesE()); + + failDeferred + .then(function(){}) + .then(function(){}, null, function(){}) + .then() + .then(function(){}); + + + failDeferred + .then(function(){}) + .then(function(){}, null, function(){}) + .then(assert.fail, function(){ + }); + + failDeferred + .then(function(){}) + .then(function(){}, null, function(){}) + .then() + .then(function(){}); + + return onUnhandledSucceed(undefined, 2); + }); + + + specify("Immediately rejected handled in a middle parallel deep sequence", function testFunction() { + var failDeferred = Promise.defer(); + + failDeferred.promise + .then(function(){}) + .then(function(){}, null, function(){}) + .then() + .then(function(){}); + + failDeferred.promise + .then(function(){}) + .then(function(){}, null, function(){}) + .then(assert.fail, function(){ + }); + + failDeferred.promise + .then(function(){}) + .then(function(){}, null, function(){}) + .then() + .then(function(){}); + + failDeferred.reject(yesE()); + return onUnhandledSucceed(undefined, 2); + }); + + + specify("Eventually handled in a middle parallel deep sequence", function testFunction() { + var failDeferred = Promise.defer(); + + failDeferred.promise + .then(function(){}) + .then(function(){}, null, function(){}) + .then() + .then(function(){}); + + failDeferred.promise + .then(function(){}) + .then(function(){}, null, function(){}) + .then(assert.fail, function(){ + }); + + failDeferred.promise + .then(function(){}) + .then(function(){}, null, function(){}) + .then() + .then(function(){}); + + + setTimeout(function(){ + failDeferred.reject(yesE()); + }, 1); + return onUnhandledSucceed(undefined, 2); + }); +}); + +describe("immediate assert.failures without .then", function testFunction() { + setupCleanUps(); + var err = new Error(''); + specify("Promise.reject", function testFunction() { + Promise.reject(err); + return onUnhandledSucceed(function(e) { + return e === err; + }); + }); + + specify("new Promise throw", function testFunction() { + new Promise(function() { + throw err; + }); + return onUnhandledSucceed(function(e) { + return e === err; + }); + }); + + specify("new Promise reject", function testFunction() { + new Promise(function(_, r) { + r(err); + }); + return onUnhandledSucceed(function(e) { + return e === err; + }); + }); + + specify("Promise.method", function testFunction() { + Promise.method(function() { + throw err; + })(); + return onUnhandledSucceed(function(e) { + return e === err; + }); + }); + + specify("Promise.all", function testFunction() { + Promise.all([Promise.reject(err)]); + return onUnhandledSucceed(function(e) { + return e === err; + }); + }); +}); + + +describe("immediate assert.failures with .then", function testFunction() { + setupCleanUps(); + var err = new Error(''); + specify("Promise.reject", function testFunction() { + Promise.reject(err).caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("new Promise throw", function testFunction() { + new Promise(function() { + throw err; + }).caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("new Promise reject", function testFunction() { + new Promise(function(_, r) { + r(err); + }).caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("Promise.method", function testFunction() { + Promise.method(function() { + throw err; + })().caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("Promise.all", function testFunction() { + Promise.all([Promise.reject("err")]) + .caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("Promise.all many", function testFunction() { + Promise.all([Promise.reject("err"), Promise.reject("err2")]) + .caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("Promise.all many, latter async", function testFunction() { + Promise.all([Promise.reject("err"), Promise.delay(1).thenThrow(new Error())]) + .caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("Promise.all many pending", function testFunction() { + var a = new Promise(function(v, w){ + setTimeout(function(){w("err");}, 1); + }); + var b = new Promise(function(v, w){ + setTimeout(function(){w("err2");}, 1); + }); + + Promise.all([a, b]) + .caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("Already rejected promise for a collection", function testFunction(){ + Promise.settle(Promise.reject(err)) + .caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); +}); + +describe("gh-118", function() { + setupCleanUps(); + specify("eventually rejected promise", function testFunction() { + Promise.resolve().then(function() { + return new Promise(function(_, reject) { + setTimeout(function() { + reject(13); + }, 1); + }); + }).caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("already rejected promise", function testFunction() { + Promise.resolve().then(function() { + return Promise.reject(13); + }).caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); + + specify("immediately rejected promise", function testFunction() { + Promise.resolve().then(function() { + return new Promise(function(_, reject) { + reject(13); + }); + }).caught(noop); + return onUnhandledFail(isStrictModeSupported ? testFunction : arguments.callee); + }); +}); + +describe("Promise.onUnhandledRejectionHandled", function() { + specify("should be called when unhandled promise is later handled", function() { + var unhandledPromises = []; + var spy1 = testUtils.getSpy(); + var spy2 = testUtils.getSpy(); + + Promise.onPossiblyUnhandledRejection(spy1(function(reason, promise) { + unhandledPromises.push({ + reason: reason, + promise: promise + }); + })); + + Promise.onUnhandledRejectionHandled(spy2(function(promise) { + assert.equal(unhandledPromises.length, 1); + assert(unhandledPromises[0].promise === promise); + assert(promise === a); + assert(unhandledPromises[0].reason === reason); + })); + + var reason = new Error("error"); + var a = new Promise(function(){ + throw reason; + }); + + setTimeout(function() { + Promise._unhandledRejectionCheck(); + a.then(assert.fail, function(){}); + }, 1); + + return Promise.all([spy1.promise, spy2.promise]); + }); +}); + +describe("global events", function() { + var attachGlobalHandler, detachGlobalHandlers; + if (typeof process !== "undefined" && + typeof process.version === "string" && + typeof window === "undefined") { + attachGlobalHandler = function(name, fn) { + process.on(name, fn); + }; + detachGlobalHandlers = function() { + process.removeAllListeners("unhandledRejection"); + process.removeAllListeners("rejectionHandled"); + }; + } else { + attachGlobalHandler = function(name, fn) { + window[("on" + name).toLowerCase()] = fn; + }; + detachGlobalHandlers = function() { + window.onunhandledrejection = null; + window.onrejectionhandled = null; + }; + } + setupCleanUps(); + beforeEach(detachGlobalHandlers); + afterEach(detachGlobalHandlers); + specify("are fired", function() { + return new Promise(function(resolve, reject) { + var err = new Error(); + var receivedPromise; + attachGlobalHandler("unhandledRejection", function(reason, promise) { + assert.strictEqual(reason, err); + receivedPromise = promise; + }); + attachGlobalHandler("rejectionHandled", function(promise) { + assert.strictEqual(receivedPromise, promise); + resolve(); + }); + + var promise = new Promise(function() {throw err;}); + setTimeout(function() { + Promise._unhandledRejectionCheck(); + promise.then(assert.fail, function(){}); + }, 1); + }); + }); + + specify("are fired with local events", function() { + return new Promise(function(resolve, reject) { + var expectedOrder = [1, 2, 3, 4]; + var order = []; + var err = new Error(); + var receivedPromises = []; + + Promise.onPossiblyUnhandledRejection(function(reason, promise) { + assert.strictEqual(reason, err); + receivedPromises.push(promise); + order.push(1); + }); + + Promise.onUnhandledRejectionHandled(function(promise) { + assert.strictEqual(receivedPromises[0], promise); + order.push(3); + }); + + attachGlobalHandler("unhandledRejection", function(reason, promise) { + assert.strictEqual(reason, err); + receivedPromises.push(promise); + order.push(2); + }); + + attachGlobalHandler("rejectionHandled", function(promise) { + assert.strictEqual(receivedPromises[1], promise); + order.push(4); + assert.deepEqual(expectedOrder, order); + assert.strictEqual(receivedPromises.length, 2); + resolve(); + }); + + var promise = new Promise(function() {throw err;}); + setTimeout(function() { + Promise._unhandledRejectionCheck(); + promise.then(assert.fail, function(){}); + }, 1); + }); + + }); +}); +var windowDomEventSupported = true; +try { + var event = document.createEvent("CustomEvent"); + event.initCustomEvent("testingtheevent", false, true, {}); + self.dispatchEvent(event); +} catch (e) { + windowDomEventSupported = false; +} +if (windowDomEventSupported) { + describe("dom events", function() { + var events = []; + + beforeEach(detachEvents); + afterEach(detachEvents); + function detachEvents() { + events.forEach(function(e) { + self.removeEventListener(e.type, e.fn, false); + }); + events = []; + } + + function attachEvent(type, fn) { + events.push({type: type, fn: fn}); + self.addEventListener(type, fn, false); + } + + specify("are fired", function() { + return new Promise(function(resolve, reject) { + var order = []; + var err = new Error(); + var promise = Promise.reject(err); + attachEvent("unhandledrejection", function(e) { + e.preventDefault(); + assert.strictEqual(e.detail.promise, promise); + assert.strictEqual(e.detail.reason, err); + assert.strictEqual(e.promise, promise); + assert.strictEqual(e.reason, err); + order.push(1); + }); + attachEvent("unhandledrejection", function(e) { + assert.strictEqual(e.detail.promise, promise); + assert.strictEqual(e.detail.reason, err); + assert.strictEqual(e.promise, promise); + assert.strictEqual(e.reason, err); + assert.strictEqual(e.defaultPrevented, true); + order.push(2); + }); + attachEvent("rejectionhandled", function(e) { + e.preventDefault(); + assert.strictEqual(e.detail.promise, promise); + assert.strictEqual(e.detail.reason, undefined); + assert.strictEqual(e.promise, promise); + assert.strictEqual(e.reason, undefined); + order.push(3); + }); + attachEvent("rejectionhandled", function(e) { + assert.strictEqual(e.detail.promise, promise); + assert.strictEqual(e.detail.reason, undefined); + assert.strictEqual(e.promise, promise); + assert.strictEqual(e.reason, undefined); + assert.strictEqual(e.defaultPrevented, true); + order.push(4); + resolve(); + }); + + setTimeout(function() { + Promise._unhandledRejectionCheck(); + promise.then(assert.fail, function(r) { + order.push(5); + assert.strictEqual(r, err); + assert.deepEqual(order, [1,2,3,4,5]); + }); + }, 1); + }); + + }) + }); + + if (typeof Worker !== "undefined") { + describe("dom events in a worker", function() { + var worker; + beforeEach(function () { + worker = new Worker("./worker.js"); + }); + + afterEach(function () { + worker.terminate(); + }); + + specify("are fired", function() { + var order = []; + return new Promise(function(resolve, reject) { + worker.onmessage = function (message) { + try { + switch(message.data) { + case "unhandledrejection": + order.push(1); + break; + case "rejectionhandled": + order.push(2); + resolve(); + break; + default: + throw new Error("unexpected message: " + message); + } + } + catch (e) { + reject(e); + } + }; + + worker.postMessage("reject"); + }).then(function () { + assert.deepEqual(order, [1, 2]); + }); + }); + }); + } + +} + +describe("Unhandled rejection when joining chains with common rejected parent", function testFunction() { + specify("GH 645", function() { + var aError = new Error('Something went wrong'); + var a = Promise.try(function(){ + throw aError; + }); + + var b = Promise.try(function(){ + throw new Error('Something went wrong here as well'); + }); + + var c = Promise + .join(a, b) + .spread(function( a, b ){ + return a+b; + }); + + var test1 = Promise + .join(a, c) + .spread(function( a, product ){ + // ... + }) + .caught(Error, function(e) { + assert.strictEqual(aError, e); + }); + + var test2 = onUnhandledFail(testFunction); + + return Promise.all([test1, test2]); + }); +}); + +var asyncAwaitSupported = (function() { + try { + new Function("async function abc() {}"); + return true; + } catch (e) { + return false; + } +})(); + +if (asyncAwaitSupported) { + describe("No unhandled rejection from async await", function () { + setupCleanUps(); + specify("gh-1404", function testFunction() { + var ret = onUnhandledFail(testFunction); + Promise.using(Promise.resolve(), + (new Function("Bluebird", "return async function() { await Bluebird.reject(new Error('foo')); }"))(Promise)) + .caught(function() {}); + return ret; + }); + }); +} + +describe("issues", function () { + setupCleanUps(); + + specify("GH-1501-1", function testFunction() { + var ret = onUnhandledFail(testFunction); + Promise.reduce([Promise.resolve("foo"), Promise.reject(new Error("reason"), Promise.resolve("bar"))], + function() {}, + {}).caught(function() {}); + return ret; + }); + + specify("GH-1501-2", function testFunction() { + var ret = onUnhandledFail(testFunction); + Promise.reduce([Promise.delay(1), Promise.reject(new Error("reason"))], + function() {}, + {}).caught(function() {}); + return ret; + }); + + specify("GH-1501-3", function testFunction() { + var ret = onUnhandledFail(testFunction); + Promise.reduce([Promise.reject(new Error("reason"))], + function() {}, + Promise.reject(new Error("reason2"))).caught(function() {}); + return ret; + }); + + specify("GH-1487-1", function testFunction() { + var ret = onUnhandledFail(testFunction); + var p = Promise.reject( new Error('foo') ); + Promise.map( p, function() {} ).caught( function() {} ); + return ret; + }); + + specify("GH-1487-2", function testFunction() { + var ret = onUnhandledFail(testFunction); + var arr = [ Promise.reject( new Error('foo') ) ]; + Promise.map( arr, function() {} ).caught( function() {} ); + return ret; + }); + + specify("GH-1487-3", function testFunction() { + var ret = onUnhandledFail(testFunction); + var p = Promise.reject( new Error('foo') ); + p.map( function() {} ).caught( function() {} ); + return ret; + }); + + specify("GH-1487-4", function testFunction() { + var ret = onUnhandledFail(testFunction); + var arr = [ Promise.reject( new Error('foo') ) ]; + var p = Promise.resolve( arr ); + p.map( function() {} ).caught( function() {} ); + return ret; + }); + + specify("GH-1487-5", function testFunction() { + var ret = onUnhandledFail(testFunction); + var p = Promise.reject( new Error('foo') ); + Promise.filter( p, function() {} ).caught( function() {} ); + return ret; + }); + + specify("GH-1487-6", function testFunction() { + var ret = onUnhandledFail(testFunction); + var arr = [ Promise.reject( new Error('foo') ) ]; + Promise.filter( arr, function() {} ).caught( function() {} ); + return ret; + }); + + specify("GH-1487-7", function testFunction() { + var ret = onUnhandledFail(testFunction); + var p = Promise.reject( new Error('foo') ); + p.filter( function() {} ).caught( function() {} ); + return ret; + }); + + specify("GH-1487-8", function testFunction() { + var ret = onUnhandledFail(testFunction); + var arr = [ Promise.reject( new Error('foo') ) ]; + var p = Promise.resolve( arr ); + p.filter( function() {} ).caught( function() {} ); + return ret; + }); +}) diff --git a/test/mocha/using.js b/test/mocha/using.js new file mode 100644 index 0000000..85b428d --- /dev/null +++ b/test/mocha/using.js @@ -0,0 +1,450 @@ +"use strict"; +var assert = require("assert"); +var testUtils = require("./helpers/util.js"); + +var Promise2 = require("../../js/debug/promise.js")(); + +var using = Promise.using; +var error = new Error(""); +var id = 0; +function Resource() { + this.isClosed = false; + this.closesCalled = 0; + this.commited = false; + this.rollbacked = false; + this.id = id++; + +} + +Resource.prototype.commit = function () { + if (this.commited || this.rollbacked) { + throw new Error("was already commited or rolled back") + } + this.commited = true; +}; + +Resource.prototype.commitAsync = function () { + return Promise.delay(1).bind(this).then(this.commit) +} + +Resource.prototype.rollback = function () { + if (this.commited || this.rollbacked) { + throw new Error("was already commited or rolled back") + } + this.rollbacked = true; +}; + +Resource.prototype.rollbackAsync = function () { + return Promise.delay(1).bind(this).then(this.rollback) +} + +Resource.prototype.closeAsync = function() { + return Promise.delay(1).bind(this).then(this.close); +}; + +Resource.prototype.close = function() { + this.closesCalled++; + if (this.isClosed) { + return; + } + this.isClosed = true; +}; + +Resource.prototype.closeError = function() { + throw error; +}; + +Resource.prototype.query = function(value) { + return Promise.delay(1, value); +}; + +function _connect() { + return new Promise(function(resolve) { + setTimeout(function(){ + resolve(new Resource()) + }, 1); + }); +} + +function _connect2() { + return new Promise2(function(resolve) { + setTimeout(function(){ + resolve(new Resource()) + }, 1); + }); +} + +function connectCloseAsync(arr, value) { + return _connect().disposer(function(resource){ + return resource.closeAsync().then(function() { + arr.push(value); + }); + }); +} + +function promiseForConnectCloseAsync(arr, value) { + return Promise.delay(1).then(function() { + return connectCloseAsync(arr, value); + }); +} + +function connect() { + return _connect().disposer(Resource.prototype.close); +} + +function connect2() { + return _connect2().disposer(Resource.prototype.close); +} + + +function connectError() { + return new Promise(function(resolve, reject) { + setTimeout(function(){ + reject(error); + }, 1); + }); +} + +function transactionDisposer(tx, outcome) { + outcome.isFulfilled() ? tx.commit() : tx.rollback(); +} + +function transactionDisposerAsync(tx, outcome) { + return outcome.isFulfilled() ? tx.commitAsync() : tx.rollbackAsync(); +} + +function transaction() { + return _connect().disposer(transactionDisposer); +} + +function transactionWithImmediatePromiseAfterConnect() { + return _connect().then(function (connection) { + return Promise.resolve(connection).then(function(c) { return c; }) + }).disposer(transactionDisposer); +} + +function transactionWithEventualPromiseAfterConnect() { + return _connect().then(function (connection) { + return Promise.delay(1).thenReturn(connection); + }).disposer(transactionDisposer); +} + +function transactionAsync() { + return _connect().disposer(transactionDisposerAsync); +} + +describe("Promise.using", function() { + specify("simple happy case", function() { + var res; + + return using(connect(), function(connection){ + res = connection; + }).then(function() { + assert(res.isClosed); + assert.equal(res.closesCalled, 1); + }); + }); + + specify("simple async happy case", function() { + var res; + var async = false; + + return using(connect(), function(connection) { + res = connection; + return Promise.delay(1).then(function() { + async = true; + }); + }).then(function() { + assert(async); + assert(res.isClosed); + assert.equal(res.closesCalled, 1); + }); + }); + + specify("simple unhappy case", function() { + var a = connect(); + var promise = a._promise; + var b = connectError(); + return using(b, a, function(a, b) { + assert(false); + }).then(assert.fail, function() { + assert(promise.value().isClosed); + assert.equal(promise.value().closesCalled, 1); + }) + }); + + specify("calls async disposers sequentially", function() { + var a = []; + var _res3 = connectCloseAsync(a, 3); + var _res2 = connectCloseAsync(a, 2); + var _res1 = connectCloseAsync(a, 1); + return using(_res1, _res2, _res3, function(res1, res2, res3) { + _res1 = res1; + _res2 = res2; + _res3 = res3; + }).then(function() { + assert.deepEqual(a, [1,2,3]); + assert(_res1.isClosed); + assert(_res2.isClosed); + assert(_res3.isClosed); + assert(_res1.closesCalled == 1); + assert(_res2.closesCalled == 1); + assert(_res3.closesCalled == 1); + }); + }); + + specify("calls async disposers sequentially when assert.failing", function() { + var a = []; + var _res3 = connectCloseAsync(a, 3); + var _res2 = connectCloseAsync(a, 2); + var _res1 = connectCloseAsync(a, 1); + var p1 = _res1.promise(); + var p2 = _res2.promise(); + var p3 = _res3.promise(); + var e = new Error(); + var promise = Promise.delay(1).thenThrow(e); + return using(_res1, _res2, _res3, promise, function() { + + }).then(assert.fail, function(err) { + assert.deepEqual(a, [1,2,3]); + assert(p1.value().isClosed); + assert(p2.value().isClosed); + assert(p3.value().isClosed); + assert(p1.value().closesCalled == 1); + assert(p2.value().closesCalled == 1); + assert(p3.value().closesCalled == 1); + assert(e === err) + }); + }); + + specify("calls promised async disposers sequentially", function() { + var a = []; + var _res3 = promiseForConnectCloseAsync(a, 3); + var _res2 = promiseForConnectCloseAsync(a, 2); + var _res1 = promiseForConnectCloseAsync(a, 1); + return using(_res1, _res2, _res3, function(res1, res2, res3) { + assert(res1 instanceof Resource) + assert(res2 instanceof Resource) + assert(res3 instanceof Resource) + _res1 = res1; + _res2 = res2; + _res3 = res3; + }).then(function() { + assert.deepEqual(a, [1,2,3]); + assert(_res1.isClosed); + assert(_res2.isClosed); + assert(_res3.isClosed); + assert(_res1.closesCalled == 1); + assert(_res2.closesCalled == 1); + assert(_res3.closesCalled == 1); + }); + }); + + specify("calls promised async disposers sequentially when assert.failing", function() { + var a = []; + var _res3 = promiseForConnectCloseAsync(a, 3); + var _res2 = promiseForConnectCloseAsync(a, 2); + var _res1 = promiseForConnectCloseAsync(a, 1); + var e = new Error(); + var promise = Promise.delay(1).thenThrow(e); + return using(_res1, _res2, _res3, promise, function() { + + }).then(assert.fail, function(err) { + assert.deepEqual(a, [1,2,3]); + assert(_res1.value().promise().value().isClosed); + assert(_res2.value().promise().value().isClosed); + assert(_res3.value().promise().value().isClosed); + assert(_res1.value().promise().value().closesCalled == 1); + assert(_res2.value().promise().value().closesCalled == 1); + assert(_res3.value().promise().value().closesCalled == 1); + assert(e === err) + }); + }); + + specify("mixed promise, promise-for-disposer and disposer", function() { + var a = []; + var _res3 = promiseForConnectCloseAsync(a, 3); + var _res2 = connectCloseAsync(a, 2); + var _res1 = Promise.delay(1, 10); + return using(_res1, _res2, _res3, function(res1, res2, res3) { + assert(res1 === 10); + assert(res2 instanceof Resource); + assert(res3 instanceof Resource); + _res1 = res1; + _res2 = res2; + _res3 = res3; + }).then(function() { + assert.deepEqual(a, [2,3]); + assert(_res2.isClosed); + assert(_res3.isClosed); + assert(_res2.closesCalled == 1); + assert(_res3.closesCalled == 1); + }); + }); + + specify("successful transaction", function() { + var _tx; + return using(transaction(), function(tx) { + _tx = tx; + return tx.query(1).then(function() { + return tx.query(2); + }) + }).then(function(){ + assert(_tx.commited); + assert(!_tx.rollbacked); + }); + }); + + specify("successful async transaction", function() { + var _tx; + return using(transactionAsync(), function(tx) { + _tx = tx; + return tx.query(1).then(function() { + return tx.query(3); + }) + }).then(function(){ + assert(_tx.commited); + assert(!_tx.rollbacked); + }) + }) + + specify("successful transaction with an immediate promise before disposer creation", function() { + var _tx; + return using(transactionWithImmediatePromiseAfterConnect(), function(tx) { + _tx = tx; + return tx.query(1); + }).then(function(){ + assert(_tx.commited); + assert(!_tx.rollbacked); + }); + }); + + specify("successful transaction with an eventual promise before disposer creation", function() { + var _tx; + return using(transactionWithEventualPromiseAfterConnect(), function(tx) { + _tx = tx; + return tx.query(1); + }).then(function(){ + assert(_tx.commited); + assert(!_tx.rollbacked); + }); + }); + + specify("assert.fail transaction", function() { + var _tx; + return using(transaction(), function(tx) { + _tx = tx; + return tx.query(1).then(function() { + throw new Error(); + }) + }).then(assert.fail, function(){ + assert(!_tx.commited); + assert(_tx.rollbacked); + }); + }); + + specify("assert.fail async transaction", function() { + var _tx; + return using(transactionAsync(), function(tx) { + _tx = tx; + return tx.query(1).then(function() { + throw new Error(); + }) + }).then(assert.fail, function(){ + assert(!_tx.commited); + assert(_tx.rollbacked); + }); + }); + + specify("with using coming from another Promise instance", function() { + var res; + return Promise2.using(connect(), function(connection){ + res = connection; + }).then(function() { + assert(res.isClosed); + assert.equal(res.closesCalled, 1); + }); + }); + + specify("with using coming from another Promise instance other way around", function() { + var res; + return Promise.using(connect2(), function(connection){ + res = connection; + }).then(function() { + assert(res.isClosed); + assert.equal(res.closesCalled, 1); + }); + }); + + if (testUtils.isNodeJS) { + specify("disposer throwing should throw in node process", function() { + var err = new Error(); + var disposer = Promise.resolve().disposer(function() { + throw err; + }); + using(disposer, function(){}); + return testUtils.awaitGlobalException(function(e) { + assert.strictEqual(e, err); + }); + }); + } + + specify("Return rejected promise with less than 2 arguments", function() { + return Promise.using(1).caught(Promise.TypeError, function(e) {}); + }); + + specify("Throw if disposer is not passed a function", function() { + try { + Promise.resolve().disposer({}); + } catch (e) { + return Promise.resolve(); + } + assert.fail(); + }); + + specify("Mixed rejected disposers are not called", function() { + var err = new Error("rejected should not be called"); + var a = Promise.delay(1).thenThrow(err).disposer(function() { + done(err); + }); + var b = Promise.delay(1).thenReturn(connect()); + return Promise.using(a, b, function() { + done(new Error("should not be here")); + }).then(assert.fail, function(e) { + assert.strictEqual(b.value()._promise.value().isClosed, true); + assert.strictEqual(b.value()._promise.value().closesCalled, 1); + }); + }); + + specify("Return rejected promise when last argument is not function", function() { + return Promise.using({}, {}, {}, {}).caught(Promise.TypeError, function(e) {}); + }); + + + specify("Supports an array of 2 items", function() { + return Promise.using([Promise.resolve(1), Promise.resolve(2)], function(results) { + assert.deepEqual(results, [1,2]); + }); + }); + + specify("Supports an empty array", function() { + return Promise.using([], function(results) { + assert.deepEqual(results, []); + }); + }) + + specify("null disposer is called", function() { + var calls = 0; + var getNull = function() { + return Promise.resolve(null).disposer(function() { + calls++; + }); + }; + + return Promise.using(getNull(), function() { + calls++; + }).then(function() { + assert.equal(2, calls); + }) + }) +}) diff --git a/tests b/tests new file mode 100755 index 0000000..083e2bc --- /dev/null +++ b/tests @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +node tools/test.js "$@" diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..e210e89 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,19 @@ +## Tools + +Tools that can be run from command line: + +### test.js + +For running tests. See [Testing](../#testing). + +### build.js + +For building the library. See [Custom builds](../#custom-builds). + +### jshintrc_generator.js + +Generates a .jshintrc file in the project root. + +Example: + + node tools/jshintrc_generator diff --git a/tools/ast_passes.js b/tools/ast_passes.js new file mode 100644 index 0000000..833b7a8 --- /dev/null +++ b/tools/ast_passes.js @@ -0,0 +1,746 @@ +//All kinds of conversion passes over the source code +var jsp = require("acorn"); +var walk = require("acorn-walk"); +var rnonIdentMember = /[.\-_$a-zA-Z0-9]/g; +var global = new Function("return this")(); + +function equals( a, b ) { + if( a.type === b.type ) { + if( a.type === "MemberExpression" ) { + return equals( a.object, b.object ) && + equals( a.property, b.property ); + } + else if( a.type === "Identifier" ) { + return a.name === b.name; + } + else if( a.type === "ThisExpression" ) { + return true; + } + else { + console.log("equals", a, b); + unhandled(); + } + } + return false; +} + +function getReceiver( expr ) { + if( expr.type === "MemberExpression" ) { + return expr.object; + } + return null; +} + +function nodeToString( expr ) { + if( expr == null || typeof expr !== "object" ) { + if( expr === void 0 ) { + return "void 0"; + } + else if( typeof expr === "string" ) { + return '"' + safeToEmbedString(expr) + '"'; + } + return ("" + expr); + } + if( expr.type === "Identifier" ) { + return expr.name; + } + else if( expr.type === "MemberExpression" ) { + if( expr.computed ) + return nodeToString( expr.object ) + "[" + nodeToString( expr.property ) + "]"; + else + return nodeToString( expr.object ) + "." + nodeToString( expr.property ); + } + else if( expr.type === "UnaryExpression" ) { + if( expr.operator === "~" || + expr.operator === "-" || + expr.operator === "+" ) { + return expr.operator + nodeToString( expr.argument ); + } + return "(" + expr.operator + " " + nodeToString( expr.argument ) + ")"; + } + else if( expr.type === "Literal" ) { + return expr.raw; + } + else if( expr.type === "BinaryExpression" || expr.type === "LogicalExpression" ) { + return "("+nodeToString(expr.left) + " " + + expr.operator + " " + + nodeToString(expr.right) + ")"; + } + else if( expr.type === "ThisExpression" ) { + return "this"; + } + else if( expr.type === "ObjectExpression") { + var props = expr.properties; + var ret = []; + for( var i = 0, len = props.length; i < len; ++i ) { + var prop = props[i]; + ret.push( nodeToString(prop.key) + ": " + nodeToString(prop.value)); + } + return "({"+ret.join(",\n")+"})"; + } + else if( expr.type === "NewExpression" ) { + return "new " + nodeToString(expr.callee) + "(" + nodeToString(expr.arguments) +")"; + } + //assuming it is arguments + else if( Array.isArray( expr ) ) { + var tmp = []; + for( var i = 0, len = expr.length; i < len; ++i ) { + tmp.push( nodeToString(expr[i]) ); + } + return tmp.join(", "); + } + else if( expr.type === "FunctionExpression" ) { + var params = []; + for( var i = 0, len = expr.params.length; i < len; ++i ) { + params.push( nodeToString(expr.params[i]) ); + } + } + else if( expr.type === "BlockStatement" ) { + var tmp = []; + for( var i = 0, len = expr.body.length; i < len; ++i ) { + tmp.push( nodeToString(expr.body[i]) ); + } + return tmp.join(";\n"); + } + else if( expr.type === "CallExpression" ) { + var args = []; + for( var i = 0, len = expr.arguments.length; i < len; ++i ) { + args.push( nodeToString(expr.arguments[i]) ); + } + return nodeToString( expr.callee ) + "("+args.join(",")+")"; + } + else { + console.log( "nodeToString", expr ); + unhandled() + } +} + +function DynamicCall( receiver, fnDereference, arg, start, end ) { + this.receiver = receiver; + this.fnDereference = fnDereference; + this.arg = arg; + this.start = start; + this.end = end; +} + +DynamicCall.prototype.toString = function() { + return nodeToString(this.fnDereference) + ".call(" + + nodeToString(this.receiver) + ", " + + nodeToString(this.arg) + + ")"; +}; + +function DirectCall( receiver, fnName, arg, start, end ) { + this.receiver = receiver; + this.fnName = fnName; + this.arg = arg; + this.start = start; + this.end = end; +} +DirectCall.prototype.toString = function() { + return nodeToString(this.receiver) + "." + nodeToString(this.fnName) + + "(" + nodeToString(this.arg) + ")" +}; + + +function ConstantReplacement( value, start, end ) { + this.value = value; + this.start = start; + this.end = end; +} + +ConstantReplacement.prototype.toString = function() { + return nodeToString(this.value); +}; + +function Empty(start, end) { + this.start = start; + this.end = end; +} +Empty.prototype.toString = function() { + return ""; +}; + +function Assertion( expr, exprStr, start, end ) { + this.expr = expr; + this.exprStr = exprStr; + this.start = start; + this.end = end; +} +Assertion.prototype.toString = function() { + return 'ASSERT('+nodeToString(this.expr)+',\n '+this.exprStr+')'; +}; + +function BitFieldRead(mask, start, end, fieldExpr) { + if (mask === 0) throw new Error("mask cannot be zero"); + this.mask = mask; + this.start = start; + this.end = end; + this.fieldExpr = fieldExpr; +} + +BitFieldRead.prototype.getShiftCount = function() { + var b = 1; + var shiftCount = 0; + while ((this.mask & b) === 0) { + b <<= 1; + shiftCount++; + } + return shiftCount; +}; + +BitFieldRead.prototype.toString = function() { + var fieldExpr = this.fieldExpr ? nodeToString(this.fieldExpr) : "bitField"; + var mask = this.mask; + var shiftCount = this.getShiftCount(); + return shiftCount === 0 + ? "(" + fieldExpr + " & " + mask + ")" + : "((" + fieldExpr + " & " + mask + ") >>> " + shiftCount + ")"; +}; + +function BitFieldCheck(value, inverted, start, end, fieldExpr) { + this.value = value; + this.inverted = inverted; + this.start = start; + this.end = end; + this.fieldExpr = fieldExpr; +} + +BitFieldCheck.prototype.toString = function() { + var fieldExpr = this.fieldExpr ? nodeToString(this.fieldExpr) : "bitField"; + var equality = this.inverted ? "===" : "!=="; + return "((" + fieldExpr + " & " + this.value + ") " + equality + " 0)"; +}; + +function InlineSlice(varExpr, collectionExpression, startExpression, endExpression, start, end, isBrowser, + pad) { + this.varExpr = varExpr; + this.collectionExpression = collectionExpression; + this.startExpression = startExpression; + this.endExpression = endExpression; + this.start = start; + this.end = end; + this.isBrowser = isBrowser; + this.pad = typeof pad === "number" ? pad : 0; +} + +InlineSlice.prototype.hasSimpleStartExpression = +function InlineSlice$hasSimpleStartExpression() { + return this.startExpression.type === "Identifier" || + this.startExpression.type === "Literal"; +}; + +InlineSlice.prototype.hasSimpleEndExpression = +function InlineSlice$hasSimpleEndExpression() { + return this.endExpression.type === "Identifier" || + this.endExpression.type === "Literal"; +}; + +InlineSlice.prototype.hasSimpleCollection = function InlineSlice$hasSimpleCollection() { + return this.collectionExpression.type === "Identifier"; +}; + +InlineSlice.prototype.toString = function InlineSlice$toString() { + var pad = this.pad; + + var init = this.hasSimpleCollection() + ? "" + : "var $_collection = " + nodeToString(this.collectionExpression) + ";"; + + var collectionExpression = this.hasSimpleCollection() + ? nodeToString(this.collectionExpression) + : "$_collection"; + + init += "var $_len = " + collectionExpression + ".length"; + + if (pad !== 0) { + init += " + " + Math.abs(pad) + ";"; + } else { + init += ";"; + } + + var varExpr = nodeToString(this.varExpr); + + //No offset arguments at all + if( this.startExpression === firstElement ) { + if (this.isBrowser) { + if (pad > 0) { + return "var " + varExpr + " = (new Array("+pad+")).concat([].slice.call("+collectionExpression+"));"; + } else if (pad < 0) { + return "var " + varExpr + " = ([].slice.call("+collectionExpression+")).concat(new Array("+Math.abs(pad)+"));"; + } else { + return "var " + varExpr + " = [].slice.call("+collectionExpression+");"; + } + } else { + var startVal = pad > 0 ? String(pad) : "0"; + var collectionExpressionVal = pad > 0 ? " - " + pad : ""; + var lenVal = pad < 0 ? "- " + Math.abs(pad) : ""; + + return init + "var " + varExpr + " = new Array($_len); " + + "for(var $_i = " + startVal + "; $_i < $_len " + lenVal + "; ++$_i) {" + + varExpr + "[$_i] = " + collectionExpression + "[$_i " + collectionExpressionVal + "];" + + "}"; + } + + } + else { + if( !this.hasSimpleStartExpression() ) { + init += "var $_start = " + nodeToString(this.startExpression) + ";"; + } + var startExpression = this.hasSimpleStartExpression() + ? nodeToString(this.startExpression) + : "$_start"; + + //Start offset argument given + if( this.endExpression === lastElement ) { + if (this.isBrowser) { + return "var " + varExpr + " = [].slice.call("+collectionExpression+", "+startExpression+");"; + } else { + return init + "var " + varExpr + " = new Array(Math.max($_len - " + + startExpression + ", 0)); " + + "for(var $_i = " + startExpression + "; $_i < $_len; ++$_i) {" + + varExpr + "[$_i - "+startExpression+"] = " + collectionExpression + "[$_i];" + + "}"; + } + } + //Start and end offset argument given + else { + + if( !this.hasSimpleEndExpression() ) { + init += "var $_end = " + nodeToString(this.endExpression) + ";"; + } + var endExpression = this.hasSimpleEndExpression() + ? nodeToString(this.endExpression) + : "$_end"; + + if (this.isBrowser) { + return "var " + varExpr + " = [].slice.call("+collectionExpression+", "+startExpression+", "+endExpression+");"; + } else { + return init + "var " + varExpr + " = new Array(Math.max(" + endExpression + " - " + + startExpression + ", 0)); " + + "for(var $_i = " + startExpression + "; $_i < " + endExpression + "; ++$_i) {" + + varExpr + "[$_i - "+startExpression+"] = " + collectionExpression + "[$_i];" + + "}"; + } + + } + + } +}; + +var opts = { + ecmaVersion: 5, + strictSemicolons: false, + allowTrailingCommas: true, + forbidReserved: false, + locations: false, + onComment: null, + ranges: false, + program: null, + sourceFile: null +}; + +var rlineterm = /[\r\n\u2028\u2029]/; +var rhorizontalws = /[ \t]/; + +var convertSrc = function( src, results ) { + if( results.length ) { + results.sort(function(a, b){ + var ret = a.start - b.start; + if( ret === 0 ) { + ret = a.end - b.end; + } + return ret; + }); + for( var i = 1; i < results.length; ++i ) { + var item = results[i]; + if( item.start === results[i-1].start && + item.end === results[i-1].end ) { + results.splice(i++, 1); + } + } + var ret = ""; + var start = 0; + for( var i = 0, len = results.length; i < len; ++i ) { + var item = results[i]; + ret += src.substring( start, item.start ); + ret += item.toString(); + start = item.end; + } + ret += src.substring( start ); + return ret; + } + return src; +}; + +var rescape = /[\r\n\u2028\u2029"]/g; + +var replacer = function( ch ) { + return "\\u" + (("0000") + + (ch.charCodeAt(0).toString(16))).slice(-4); +}; + +function safeToEmbedString( str ) { + return str.replace( rescape, replacer ); +} + +function parse( src, opts, fileName) { + if( !fileName ) { + fileName = opts; + opts = void 0; + } + try { + return jsp.parse(src, opts); + } + catch(e) { + e.message = e.message + " " + fileName; + e.scriptSrc = src; + throw e; + } +} + +var inlinedFunctions = Object.create(null); + +var lastElement = jsp.parse("___input.length").body[0].expression; +var firstElement = jsp.parse("0").body[0].expression; + +inlinedFunctions.INLINE_SLICE = function( node, isBrowser ) { + var statement = node; + node = node.expression; + var args = node.arguments; + + if( !(2 <= args.length && args.length <= 4 ) ) { + throw new Error("INLINE_SLICE must have exactly 2, 3 or 4 arguments"); + } + + var varExpression = args[0]; + var collectionExpression = args[1]; + var startExpression = args.length < 3 + ? firstElement + : args[2]; + var endExpression = args.length < 4 + ? lastElement + : args[3]; + return new InlineSlice(varExpression, collectionExpression, + startExpression, endExpression, statement.start, statement.end, isBrowser); +}; + +inlinedFunctions.INLINE_SLICE_LEFT_PADDED = function( node, isBrowser ) { + var statement = node; + node = node.expression; + var args = node.arguments; + + if(args.length !== 3) { + throw new Error("INLINE_SLICE_LEFT_PADDED must have exactly 3 arguments"); + } + + var padCount = Number(nodeToString(args[0])); + var varExpression = args[1]; + var collectionExpression = args[2]; + var startExpression = firstElement; + var endExpression = lastElement; + return new InlineSlice(varExpression, collectionExpression, + startExpression, endExpression, statement.start, statement.end, isBrowser, padCount); +}; + +inlinedFunctions.BIT_FIELD_READ = function(node) { + var statement = node; + var args = node.expression.arguments; + if (args.length !== 1 && args.length !== 2) { + throw new Error("BIT_FIELD must have 1 or 2 arguments"); + } + var arg = args[0]; + if (arg.type !== "Identifier") { + throw new Error("BIT_FIELD argument must be an identifier"); + } + var name = arg.name; + var constant = constants[name]; + if (constant === undefined) { + throw new Error(name + " is not a constant"); + } + var value = constant.value; + return new BitFieldRead(value, statement.start, statement.end, args[1]); +}; +inlinedFunctions.BIT_FIELD_CHECK = function(node) { + var statement = node; + var args = node.expression.arguments; + if (args.length !== 1 && args.length !== 2) { + throw new Error("BIT_FIELD must have 1 or 2 arguments"); + } + var arg = args[0]; + if (arg.type !== "Identifier") { + throw new Error("BIT_FIELD argument must be an identifier"); + } + var name = arg.name; + var constant = constants[name]; + if (constant === undefined) { + throw new Error(name + " is not a constant"); + } + var value = constant.value; + var inverted = false; + if (name.slice(-4) === "_NEG") { + inverted = true; + } + return new BitFieldCheck(value, inverted, statement.start, statement.end, args[1]); +}; +inlinedFunctions.USE = function(node) { + return new Empty(node.start, node.end); +}; + +var constants = {}; +var ignore = []; +Error.stackTraceLimit = 10000; +var astPasses = module.exports = { + + inlineExpansion: function( src, fileName, isBrowser ) { + var ast = parse(src, fileName); + var results = []; + var expr = []; + function doInline(node) { + if( node.expression.type !== 'CallExpression' ) { + return; + } + + var name = node.expression.callee.name; + + if(typeof inlinedFunctions[ name ] === "function" && + expr.indexOf(node.expression) === -1) { + expr.push(node.expression); + try { + results.push( inlinedFunctions[ name ]( node, isBrowser ) ); + } + catch(e) { + e.fileName = fileName; + throw e; + } + + } + } + walk.simple(ast, { + ExpressionStatement: doInline, + CallExpression: function(node) { + node.expression = node; + doInline(node); + } + }); + var ret = convertSrc( src, results ); + return ret; + }, + + //Parse constants in from constants.js + readConstants: function( src, fileName ) { + var ast = parse(src, fileName); + walk.simple(ast, { + ExpressionStatement: function( node ) { + if( node.expression.type !== 'CallExpression' ) { + return; + } + + var start = node.start; + var end = node.end; + node = node.expression; + var callee = node.callee; + if( callee.name === "CONSTANT" && + callee.type === "Identifier" ) { + + if( node.arguments.length !== 2 ) { + throw new Error( "Exactly 2 arguments must be passed to CONSTANT\n" + + src.substring(start, end) + ); + } + + if( node.arguments[0].type !== "Identifier" ) { + throw new Error( "Can only define identifier as a constant\n" + + src.substring(start, end) + ); + } + + var args = node.arguments; + + var name = args[0]; + var nameStr = name.name; + var expr = args[1]; + + var e = eval; + constants[nameStr] = { + identifier: name, + value: e(nodeToString(expr)) + }; + walk.simple( expr, { + Identifier: function( node ) { + ignore.push(node); + } + }); + global[nameStr] = constants[nameStr].value; + } + } + }); + }, + + //Expand constants in normal source files + expandConstants: function( src, fileName ) { + var results = []; + var identifiers = []; + var ast = parse(src, fileName); + walk.simple(ast, { + Identifier: function( node ) { + identifiers.push( node ); + } + }); + + for( var i = 0, len = identifiers.length; i < len; ++i ) { + var id = identifiers[i]; + if( ignore.indexOf(id) > -1 ) { + continue; + } + var constant = constants[id.name]; + if( constant === void 0 ) { + continue; + } + if( constant.identifier === id ) { + continue; + } + + results.push( new ConstantReplacement( constant.value, id.start, id.end ) ); + + } + return convertSrc( src, results ); + }, + + removeComments: function( src, fileName ) { + var results = []; + var rnoremove = /^[*\s\/]*(?:@preserve|jshint|global)/; + opts.onComment = function( block, text, start, end ) { + if( rnoremove.test(text) ) { + return; + } + var e = end + 1; + var s = start - 1; + while(rhorizontalws.test(src.charAt(s--))); + while(rlineterm.test(src.charAt(e++))); + results.push( new Empty( s + 2, e - 1 ) ); + }; + var ast = parse(src, opts, fileName); + return convertSrc( src, results ); + }, + + expandAsserts: function( src, fileName ) { + var ast = parse( src, fileName ); + var results = []; + walk.simple(ast, { + CallExpression: function( node ) { + + var start = node.start; + var end = node.end; + var callee = node.callee; + + if( callee.type === "Identifier" && + callee.name === "ASSERT" ) { + if( node.arguments.length !== 1 ) { + results.push({ + start: start, + end: end, + toString: function() { + return src.substring(start, end); + } + }); + return; + } + + var expr = node.arguments[0]; + var str = src.substring(expr.start, expr.end); + str = '"' + safeToEmbedString(str) + '"' + var assertion = new Assertion( expr, str, start, end ); + + results.push( assertion ); + } + } + }); + return convertSrc( src, results ); + }, + + removeAsserts: function( src, fileName ) { + var ast = parse( src, fileName ); + var results = []; + walk.simple(ast, { + ExpressionStatement: function( node ) { + if( node.expression.type !== 'CallExpression' ) { + return; + } + var start = node.start; + var end = node.end; + node = node.expression; + var callee = node.callee; + + if( callee.type === "Identifier" && + callee.name === "ASSERT" ) { + var e = end + 1; + var s = start - 1; + + while(rhorizontalws.test(src.charAt(s--))); + while(rlineterm.test(src.charAt(e++))); + results.push( new Empty( s + 2, e - 1) ); + } + }, + VariableDeclaration: function(node) { + var start = node.start; + var end = node.end; + if (node.kind === 'var' && node.declarations.length === 1) { + var decl = node.declarations[0]; + if (decl.id.type === "Identifier" && + decl.id.name === "ASSERT") { + var e = end + 1; + var s = start - 1; + while(rhorizontalws.test(src.charAt(s--))); + while(rlineterm.test(src.charAt(e++))); + results.push( new Empty( s + 2, e - 1) ); + } + } + } + }); + return convertSrc( src, results ); + }, + + asyncConvert: function( src, objName, fnProp, fileName ) { + var ast = parse( src, fileName ); + + var results = []; + walk.simple(ast, { + CallExpression: function( node ) { + var start = node.start; + var end = node.end; + if( node.callee.type === "MemberExpression" && + node.callee.object.name === objName && + node.callee.property.name === fnProp && + node.arguments.length === 3 + ) { + + var args = node.arguments; + var fnDereference = args[0]; + var dynamicReceiver = args[1]; + var arg = args[2]; + + var receiver = getReceiver(fnDereference); + + if( receiver == null || !equals(receiver, dynamicReceiver) ) { + //Have to use fnDereference.call(dynamicReceiver, arg); + results.push( + new DynamicCall( dynamicReceiver, fnDereference, arg, start, end ) + ); + } + else { + var fnName = fnDereference.property; + results.push( + new DirectCall( receiver, fnName, arg, start, end ) + ); + //Can use receiver.fnName( arg ); + + } + + + } + } + }); + return convertSrc( src, results ); + } +}; diff --git a/tools/browser_test_generator.js b/tools/browser_test_generator.js new file mode 100644 index 0000000..f114c1a --- /dev/null +++ b/tools/browser_test_generator.js @@ -0,0 +1,58 @@ +var Promise = require("bluebird"); +var path = require("path"); +var readFile = Promise.promisify(require("fs").readFile); +var writeFile = Promise.promisify(require("fs").writeFile); +var stringToStream = require("./utils.js").stringToStream; +var baseDir = path.join(__dirname, "..", "test", "browser"); + +// Sinon pulls the entire NPM as dependencies (including crypto code) for +// some reason so +// the browserify bundle ends up taking megabyte and having high risk of +// IE-incompatible code +function dependsOnSinon(test) { + return /3\.2\.5\.|done|nodeify|2\.2\.6/.test(test.name); +}; + +module.exports = function(tests, options) { + var testRequires = tests.filter(function(test) { + return !dependsOnSinon(test) && test.name.indexOf("generator") === -1; + }).map(function(test) { + var code = "require('../mocha/" + test.name + "');"; + if (test.name.indexOf("2.3.3") >= 0) { + code = "if (haveGetters) " + code; + } + return code; + }).join("\n"); + + var promiseExport = options.cover + ? readFile(path.join(baseDir, "promise_instrumented.js"), "utf8") + : readFile(path.join(baseDir, "promise_debug.js"), "utf8"); + + var main = readFile(path.join(baseDir, "main.js"), "utf8") + + return Promise.join(promiseExport, main, function(promiseExport, main) { + var browserify = require("browserify"); + var contents = promiseExport + "\n" + main + "\n" + testRequires; + var complete = browserify({ + basedir: baseDir, + entries: stringToStream(contents) + }); + var worker = browserify({ + basedir: baseDir, + entries: stringToStream(promiseExport), + }); + return Promise.join( + Promise.promisify(complete.bundle, complete)().then(function(src) { + return writeFile(path.join(baseDir, "bundle.js"), src); + }), Promise.promisify(worker.bundle, worker)().then(function (src) { + return writeFile(path.join(baseDir, "worker_bundle.js"), src); + })); + }).then(function() { + if (options.executeBrowserTests) { + return require("./browser_test_runner.js")(options); + } + }).catch(function(e) { + console.error(e.stack || e.message); + process.exit(2); + }); +}; diff --git a/tools/browser_test_runner.js b/tools/browser_test_runner.js new file mode 100644 index 0000000..af751cc --- /dev/null +++ b/tools/browser_test_runner.js @@ -0,0 +1,97 @@ +var path = require("path"); +var build = require("./build.js"); +var Promise = require("bluebird"); +var cp = Promise.promisifyAll(require("child_process")); +var fs = Promise.promisifyAll(require("fs")); +var baseDir = path.join(__dirname, "..", "test", "browser"); +var browsers = [ + ["Windows XP", "internet explorer", "7"], + ["Windows XP", "internet explorer", "8"], + ["Windows 7", "internet explorer", "9"], + ["Windows 7", "internet explorer", "10"], + ["Windows 8.1", "internet explorer", "11"], + ["Windows 7", "firefox", "3.5"], + ["Windows 7", "firefox", "4"], + ["Windows 7", "firefox", "25"], + ["Windows 7", "firefox", "33"], + ["Windows 7", "chrome", "beta"], + ["Windows 7", "safari", "5"], + ["OS X 10.9", "iphone", "8.1"], + ["OS X 10.8", "safari", "6"], + ["OS X 10.9", "safari", "7"] +]; + +var saucelabsOptions = { + urls: ["http://127.0.0.1:9999/index.html"], + tunnelTimeout: 30, + build: process.env.TRAVIS_JOB_ID, + maxPollRetries: 3, + throttled: 3, + browsers: browsers, + testname: "mocha tests", + tags: ["master"] +}; + +module.exports = function(options) { + var Promise = require("bluebird"); + var ret = Promise.resolve(); + function createServer() { + var http = require("http"); + var serve = require("serve-static")(baseDir, {'index': ['index.html']}); + var bodyParser = require("body-parser").urlencoded({ + limit: "100mb", + extended: false + }); + var server = http.createServer(function(req, res) { + serve(req, res, function() { + if (options.cover && + req.url.indexOf("coverdata") >= 0 && + req.method.toLowerCase() === "post") { + bodyParser(req, res, function() { + try { + var json = JSON.parse(req.body.json); + } catch (e) { + res.writeHead(404, {'Content-Type': 'text/plain'}); + res.end('404\n'); + return; + } + var browser = (req.body.browser + "").replace(/[^a-zA-Z0-9]/g, ""); + var fileName = path.join(build.dirs.coverage, "coverage-" + browser + ".json"); + fs.writeFileAsync(fileName, JSON.stringify(json), "utf8").then(function() { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Success\n'); + }); + }); + } else { + res.writeHead(404, {'Content-Type': 'text/plain'}); + res.end('404\n'); + } + }); + }); + return Promise.promisify(server.listen, server)(options.port) + } + + if (options.saucelabs) { + var saucelabsRunner = require("./saucelabs_runner.js"); + ret = createServer().then(function() { + return saucelabsRunner(saucelabsOptions); + }).then(function() { + process.exit(0); + }); + } else if (options.nw) { + ret = cp.execAsync((options.nwPath || "nw") + " .", { + maxBuffer: 2 * 1024 * 1024, + cwd: path.join(process.cwd(), "test/browser") + }); + } else { + var open = require("open"); + ret = createServer().then(function() { + var url = "http://localhost:" + options.port; + console.log("Test can be run at " + url); + if (options.openBrowser && !options.cover) { + return Promise.promisify(open)(url); + } + }); + } + return ret; +}; diff --git a/tools/build.js b/tools/build.js new file mode 100644 index 0000000..721f513 --- /dev/null +++ b/tools/build.js @@ -0,0 +1,340 @@ +var Promise = require("bluebird"); +var path = require("path"); +var jobRunner = require("./job-runner/job-runner.js"); +Promise.longStackTraces(); +var utils = require("./utils.js"); +var glob = Promise.promisify(require("glob")); +var fs = Promise.promisifyAll(require("fs")); +var mkdirp = Promise.promisify(require("mkdirp")); +var rimraf = Promise.promisify(require("rimraf")); + +jobRunner.init(path.join(__dirname, ".."), function() { + var fs = Promise.promisifyAll(require("fs")); + var utils = require("./tools/utils.js"); + var path = require("path"); + var astPasses = require("./tools/ast_passes.js"); + var Mocha = require("mocha"); + astPasses.readConstants( + fs.readFileSync("./src/constants.js", "utf8"), + "constants.js" + ); + function applyOptionalRequires(code, depsRequireCode) { + return code.replace( /};([^}]*)$/, depsRequireCode + "\n};$1"); + } +}); + +var optionalModuleRequireMap = { + "race.js": true, + "call_get.js": true, + "generators.js": true, + "map.js": true, + "nodeify.js": true, + "promisify.js": true, + "props.js": true, + "reduce.js": true, + "settle.js": true, + "some.js": true, + "using.js": true, + "timers.js": true, + "filter.js": ["map.js"], + "any.js": ["some.js"], + "each.js": ["reduce.js"] +}; + +var lastLineCode = " \n\ + util.toFastProperties(Promise); \n\ + util.toFastProperties(Promise.prototype); \n\ + function fillTypes(value) { \n\ + var p = new Promise(INTERNAL); \n\ + p._fulfillmentHandler0 = value; \n\ + p._rejectionHandler0 = value; \n\ + p._promise0 = value; \n\ + p._receiver0 = value; \n\ + } \n\ + // Complete slack tracking, opt out of field-type tracking and \n\ + // stabilize map \n\ + fillTypes({a: 1}); \n\ + fillTypes({b: 2}); \n\ + fillTypes({c: 3}); \n\ + fillTypes(1); \n\ + fillTypes(function(){}); \n\ + fillTypes(undefined); \n\ + fillTypes(false); \n\ + fillTypes(new Promise(INTERNAL)); \n\ + debug.setBounds(Async.firstLineError, util.lastLineError); \n\ + return Promise; \n\ +"; + +function getOptionalRequireCode(srcs) { + return srcs.sort(function(a, b) { + var aHasDeps = Array.isArray(optionalModuleRequireMap[a.sourceFileName]); + var bHasDeps = Array.isArray(optionalModuleRequireMap[b.sourceFileName]); + return aHasDeps - bHasDeps; + }).reduce(function(ret, cur, i) { + if(optionalModuleRequireMap[cur.sourceFileName]) { + ret += "require('./"+cur.sourceFileName+"')("+ cur.deps.join(", ") +");\n"; + } + return ret; + }, "") + lastLineCode; +} + +function getBrowserBuildHeader(sources, npmPackage) { + var header = "/**\n * bluebird build version " + npmPackage.version + "\n"; + var enabledFeatures = ["core"]; + var disabledFeatures = []; + featureLoop: for (var key in optionalModuleRequireMap) { + for (var i = 0, len = sources.length; i < len; ++i) { + var source = sources[i]; + if(source.sourceFileName === key) { + enabledFeatures.push(key.replace( ".js", "")); + continue featureLoop; + } + } + disabledFeatures.push(key.replace(".js", "")); + } + + header += (" * Features enabled: " + enabledFeatures.join(", ") + "\n"); + + if (disabledFeatures.length) { + header += " * Features disabled: " + disabledFeatures.join(", ") + "\n"; + } + header += "*/\n"; + return header; +} + +function getSourcePaths(features) { + return glob("./src/*.js").map(function(v) { + return path.basename(v); + }).then(function(results) { + if (features) features = features.toLowerCase().split(/\s+/g); + return results.filter(function(fileName) { + if (features && optionalModuleRequireMap[fileName] !== undefined) { + for (var i = 0; i < features.length; ++i) { + if (fileName.indexOf(features[i]) >= 0) { + return true; + } + } + return false; + } + return fileName !== "constants.js"; + }); + }); +} + +function ensureDirectory(dir, isUsed, clean) { + return (clean ? rimraf(dir) : Promise.resolve()).then(function() { + if (!isUsed) return dir; + return mkdirp(dir).thenReturn(dir); + }); +} + +function buildRelease(sources, depsRequireCode, dir) { + return dir.then(function(dir) { + return Promise.map(sources, function(source) { + return jobRunner.run(function() { + var code = source.source; + var sourceFileName = source.sourceFileName; + code = astPasses.removeAsserts(code, sourceFileName); + code = astPasses.inlineExpansion(code, sourceFileName); + code = astPasses.expandConstants(code, sourceFileName); + code = code.replace( /__DEBUG__/g, "false" ); + code = code.replace( /__BROWSER__/g, "false" ); + if (sourceFileName === "promise.js") { + code = applyOptionalRequires(code, depsRequireCode); + } + return fs.writeFileAsync(path.join(root, sourceFileName), code); + }, { + context: { + depsRequireCode: depsRequireCode, + source: source, + root: dir + } + }); + }); + }); +} + +function buildDebug(sources, depsRequireCode, dir) { + return dir.then(function(dir) { + return Promise.map(sources, function(source) { + return jobRunner.run(function() { + var code = source.source; + var sourceFileName = source.sourceFileName; + code = astPasses.expandAsserts(code, sourceFileName); + code = astPasses.inlineExpansion(code, sourceFileName); + code = astPasses.expandConstants(code, sourceFileName); + code = code.replace( /__DEBUG__/g, "true" ); + code = code.replace( /__BROWSER__/g, "false" ); + if (sourceFileName === "promise.js") { + code = applyOptionalRequires(code, depsRequireCode); + } + return fs.writeFileAsync(path.join(root, sourceFileName), code); + }, { + context: { + depsRequireCode: depsRequireCode, + source: source, + root: dir + } + }); + }); + }); +} + +function buildBrowser(sources, dir, tmpDir, depsRequireCode, minify, npmPackage, license) { + return Promise.join(dir, tmpDir, npmPackage, license, function(dir, tmpDir, npmPackage, license) { + return Promise.map(sources, function(source) { + return jobRunner.run(function() { + var code = source.source; + var sourceFileName = source.sourceFileName; + code = astPasses.removeAsserts(code, sourceFileName); + code = astPasses.inlineExpansion(code, sourceFileName, true); + code = astPasses.expandConstants(code, sourceFileName); + code = code.replace( /__BROWSER__/g, "true" ); + if (sourceFileName === "promise.js") { + code = applyOptionalRequires(code, depsRequireCode); + } + return fs.writeFileAsync(path.join(root, sourceFileName), code); + }, { + context: { + depsRequireCode: depsRequireCode, + source: source, + root: tmpDir + } + }); + }).then(function() { + var header = getBrowserBuildHeader(sources, npmPackage); + return jobRunner.run(function() { + var UglifyJS = require("uglify-js"); + var browserify = require("browserify"); + var dest = path.join(root, "bluebird.js"); + var minDest = path.join(root, "bluebird.min.js"); + var b = browserify({ + entries: entries, + detectGlobals: false, + standalone: "Promise" + }).exclude('async_hooks').exclude("timers"); + return Promise.promisify(b.bundle, b)().then(function(src) { + var alias = "\ + ;if (typeof window !== 'undefined' && window !== null) { \ + window.P = window.Promise; \ + } else if (typeof self !== 'undefined' && self !== null) { \ + self.P = self.Promise; \ + }"; + src = src + alias; + src = src.replace(/\brequire\b/g, "_dereq_"); + var minWrite, write; + if (minify) { + var minSrc = src.replace( /__DEBUG__/g, "false" ); + minSrc = UglifyJS.minify(minSrc, { + comments: false, + compress: true, + fromString: true + }).code; + minSrc = license + header + minSrc; + minWrite = fs.writeFileAsync(minDest, minSrc); + } + src = src.replace( /__DEBUG__/g, "true" ); + src = license + header + src; + write = fs.writeFileAsync(dest, src); + + return Promise.all([write, minWrite]); + }) + }, { + context: { + header: header, + root: dir, + entries: path.join(tmpDir, "bluebird.js"), + license: license, + minify: minify + } + }) + }); + }); +} + +var root = process.cwd(); +// Since rm -rf is called, better be sure... +if (path.basename(root).toLowerCase().indexOf("bluebird") !== 0) { + throw new Error("cwd must be set to bluebird project root. cwd is currently\n\n" + + " " + process.cwd() + "\n"); +} +var dirs = { + release: path.join(root, "js", "release"), + debug: path.join(root, "js", "debug"), + browser: path.join(root, "js", "browser"), + browserTmp: path.join(root, "js", "tmp"), + instrumented: path.join(root, "js", "instrumented"), + coverage: path.join(root, "coverage") +}; + +function build(options) { + options = Object(options); + var clean = (typeof options.clean !== "boolean" ? true : options.clean); + var npmPackage = fs.readFileAsync("./package.json").then(JSON.parse); + var version = npmPackage.get("version"); + var sourceFileNames = getSourcePaths(options.features); + var license = utils.getLicense(); + var releaseDir = ensureDirectory(dirs.release, options.release, clean); + var debugDir = ensureDirectory(dirs.debug, options.debug, clean); + var browserDir = ensureDirectory(dirs.browser, options.browser, clean); + var browserTmpDir = ensureDirectory(dirs.browserTmp, options.browser, clean); + return Promise.join(license, version, function(license, version) { + return sourceFileNames.map(function(sourceFileName) { + return jobRunner.run(function() { + var sourcePath = path.join("./src", sourceFileName); + var source = fs.readFileAsync(sourcePath, "utf8"); + return source.then(function(source) { + utils.checkAscii(sourceFileName, source); + var deps = null; + if (optionalModuleRequireMap[sourceFileName] !== undefined) { + deps = utils.parseDeps(source); + } + source = astPasses.removeComments(source, sourceFileName); + source = source.replace(/__VERSION__/g, version); + return { + sourceFileName: sourceFileName, + source: source, + deps: deps + }; + }); + }, { + context: { + sourceFileName: sourceFileName, + optionalModuleRequireMap: optionalModuleRequireMap, + license: license, + version: version + } + }); + }); + }).then(function(results) { + var depsRequireCode = getOptionalRequireCode(results); + var release, debug, browser; + if (options.release) + release = buildRelease(results, depsRequireCode, releaseDir); + if (options.debug) + debug = buildDebug(results, depsRequireCode, debugDir); + if (options.browser) + browser = buildBrowser(results, browserDir, browserTmpDir, depsRequireCode, options.minify, npmPackage, license); + + return Promise.all([release, debug, browser]); + }); +} + +module.exports = build; +module.exports.ensureDirectory = ensureDirectory; +module.exports.dirs = dirs; + + +if (require.main === module) { + var argv = require("optimist").argv; + var browser = (typeof argv.browser !== "boolean" ? false : argv.browser) || !!argv.features; + var clean = (typeof argv.clean !== "boolean" ? true : argv.clean); + module.exports({ + minify: browser && (typeof argv.minify !== "boolean" ? true : argv.minify), + browser: browser, + debug: !!argv.debug, + release: !!argv.release, + features: argv.features || null, + clean: clean + }); +} diff --git a/tools/job-runner/job-runner.js b/tools/job-runner/job-runner.js new file mode 100644 index 0000000..c8f30b2 --- /dev/null +++ b/tools/job-runner/job-runner.js @@ -0,0 +1,557 @@ +var Promise = require("bluebird"); +var spawn = require("child_process").spawn; +var assert = require("assert"); +var argv = require("optimist").argv; +var ARGS = [].concat( + process.execArgv, + __filename, + process.argv.slice(2) +); +function sanitizeCpCount(input) { + return Math.min(Math.max(1, (+input | 0)), 16); +} + +if (typeof argv["child-processes"] === "number") { + var CHILD_PROCESSES = sanitizeCpCount(argv["child-processes"]); +} else if (process.env.CHILD_PROCESSES) { + var CHILD_PROCESSES = sanitizeCpCount(process.env.CHILD_PROCESSES); +} else { + var CHILD_PROCESSES = 4; +} + + +var debugging = false; + +function debug() { + if (debugging) { + var msg = [].slice.call(arguments).join(" "); + console.log(msg); + } +} + +function ResultWithOutput(result, stdout, stderr) { + this.result = result; + this.stdout = stdout; + this.stderr = stderr; +} + +var jobRunner = (function() { + var taskId = 0; + var workerCount; + var workers = []; + var tasks = []; + var killed = true; + + function leastTotalRunningTime(a, b) { + return a.totalRunningTime() - b.totalRunningTime(); + } + + function each(fn) { + if (typeof fn === "string") { + var args = [].slice.call(arguments, 1); + return workers.forEach(function(worker) { + worker[fn].apply(worker, args); + }); + } + return workers.forEach(fn); + } + + function retLogger(result) { + if (result instanceof ResultWithOutput) { + process.stdout.write(result.stdout); + process.stderr.write(result.stderr); + } + return result; + } + + function throwLogger(result) { + if (result && (result.stderr || result.stdout)) { + process.stdout.write(result.stdout); + process.stderr.write(result.stderr); + } + throw result; + } + + function reinit() { + each("init"); + } + + function checkShutDown(secondCheck) { + if (tasks.length > 0) return; + var anyWorkerHasTasks = workers.some(function(w) { + return w.hasTasks(); + }); + if (anyWorkerHasTasks) return; + if (secondCheck) return ret.exit(); + setTimeout(function() { + checkShutDown(true); + }, 10); + } + + function schedule(task, queued) { + var worker = workers.filter(function(worker) { + return task.isolated ? + !worker.hasTasks() : !worker._runningIsolatedTask; + }).sort(leastTotalRunningTime)[0]; + + if (!worker) { + if (!queued) tasks.push(task); + return false; + } else { + assert(task.isolated ? !worker.hasTasks() : true); + debug("found free worker", worker._id, "for task", task.id); + worker.performTask(task); + return true; + } + } + + var ret = { + init: function(requirePath, initTaskFn) { + if (workers.length) return; + if (typeof requirePath !== "string") throw new TypeError(); + var count = CHILD_PROCESSES; + workerCount = count; + var id = 0; + for (var i = 0; i < count; ++i) { + workers.push(new Worker(id++, requirePath, initTaskFn)); + } + process.on("exit", ret.exit); + }, + + exit: function() { + if (killed) return; + killed = true; + each("kill"); + }, + + run: function(task, opts) { + if (!workerCount) throw new Error("task runner has not been initialized"); + if (typeof task !== "function") throw new TypeError("fn not function"); + if (killed) { + killed = false; + reinit(); + } + opts = opts || {}; + var context = opts.context || {}; + var log = opts.log === false ? false : true; + var estimate = typeof opts.estimate === "number" ? opts.estimate : null; + var progress = typeof opts.progress === "function" ? opts.progress : null; + var isolated = !!opts.isolated; + var resolve, reject; + var promise = new Promise(function() { + resolve = arguments[0]; + reject = arguments[1]; + }); + task = { + isolated: isolated, + task: { + code: task + "", + context: context + }, + resolve: resolve, + reject: reject, + estimate: estimate, + id: taskId++, + log: log, + progress: progress + }; + schedule(task, false); + if (log) promise = promise.then(retLogger, throwLogger); + return promise; + }, + + setVerbose: function(v) { + debugging = !!v; + }, + + _workerIdleNotification: function() { + var _t = tasks; + if (_t.length === 0) { + return checkShutDown(); + } + while(_t.length > 0) { + var task = _t.shift(); + if (!schedule(task, true)) { + _t.unshift(task); + return; + } + } + } + }; + return ret; +})(); + +function Worker(id, requirePath, initTaskFn) { + this._initTaskFn = initTaskFn; + this._requirePath = requirePath; + this._id = id; + this._runningTaskCount = 0; + this._runningIsolatedTask = false; + this._performingIsolatedTask = false; + this._queuedIsolatedTask = null; + this._bufferingStdio = false; + this._runningTime = 0; + this._c = null; + this._stdout = ""; + this._stderr = ""; + this._tasks = {}; + this._onStdOut = bind(this._onStdOut, this); + this._onStdErr = bind(this._onStdErr, this); + this._onError = bind(this._onError, this); + this._onMessage = bind(this._onMessage, this); +} + +Worker.prototype.totalRunningTime = function() { + var ret = this._runningTime; + var ids = Object.keys(this._tasks); + var now = Date.now(); + for (var i = 0; i < ids.length; ++i) { + var task = this._tasks[ids[i]]; + ret += task.estimate === null ? (now - task.started + 1): task.estimate; + } + return ret; +}; + +Worker.prototype._onStdOut = function(data) { + data = data.toString(); + if (this._bufferingStdio) { + this._stdout += data; + } else { + process.stdout.write(data); + } +}; + +Worker.prototype._onStdErr = function(data) { + data = data.toString(); + + if (this._bufferingStdio) { + this._stderr += data; + } else { + process.stderr.write(data); + } +}; + +Worker.prototype._onError = function(e) { + process.stderr.write(e && e.stack && e.stack + "" || (e + "")); +}; + +Worker.prototype._onMessage = function(payload) { + var self = this; + setImmediate(function() { + self[payload.type].call(self, payload); + }); +}; + +Worker.prototype.removeListeners = function() { + var c = this._c; + c.stdout.removeListener("data", this._onStdOut); + c.stderr.removeListener("data", this._onStdErr); + c.removeListener("message", this._onMessage); + c.removeListener("error", this._onError); +}; + +Worker.prototype.debug = function(msg, task) { + debug("worker", this._id, msg, (task.isolated ? + "isolated" : ""), "task", task.id); +}; + +Worker.prototype.hasTasks = function() { + return this.runningTaskCount() > 0 || + this._queuedIsolatedTask || + this._runningIsolatedTask; +}; + +Worker.prototype.runningTaskCount = function() { + return this._runningTaskCount; +}; + +Worker.prototype.performTask = function(task) { + if (task !== this._queuedIsolatedTask) { + assert(!this._runningIsolatedTask); + if (task.isolated) { + this._runningIsolatedTask = true; + if (this.runningTaskCount() > 0) { + this.debug("queued", task); + this._queuedIsolatedTask = task; + return; + } else { + assert(this._queuedIsolatedTask === null); + this._performingIsolatedTask = true; + } + } + } else { + assert(this.runningTaskCount() === 0); + assert(this._runningIsolatedTask); + this._queuedIsolatedTask = null; + this._performingIsolatedTask = true; + } + this._runningTaskCount++; + assert(this._performingIsolatedTask ? this._runningTaskCount === 1 : true); + this._tasks[task.id] = task; + task.started = Date.now(); + this.debug("starts to perform", task); + this._c.send({ + type: "newTask", + id: task.id, + task: task.task, + isolated: task.isolated, + log: task.log, + progress: !!task.progress + }); + +}; + +function getFunctionSource(fn) { + return (fn + "") + .replace(/^\s*function\s*\(\s*\)\s*{/, "") + .replace(/}\s*$/, ""); +} + +Worker.prototype.init = function() { + assert(Array.isArray(ARGS)); + assert(!this._c); + var env = {}; + Object.getOwnPropertyNames(process.env).forEach(function(key) { + env[key] = process.env[key]; + }); + env.requirePath = this._requirePath; + if (typeof this._initTaskFn === "function") { + env.initialCode = getFunctionSource(this._initTaskFn); + } + + var c = spawn(process.execPath, ARGS, { + env: env, + stdio: ["pipe", "pipe", "pipe", "ipc"], + cwd: this._requirePath + }); + assert(typeof c.send === "function"); + this._c = c; + c.stdout.on("data", this._onStdOut); + c.stderr.on("data", this._onStdErr); + c.on("error", this._onError); + c.on("message", this._onMessage); +}; + +Worker.prototype.taskComplete = function(payload) { + var task = this._tasks[payload.id]; + this.debug("completed", task); + delete this._tasks[payload.id]; + this._runningTaskCount--; + var resolve, result; + if (payload.isError) { + resolve = task.reject; + var err = payload.error; + if (err.__isErrorInstance__) { + result = new Error(); + Object.keys(err).forEach(function(key) { + if (key === "__isErrorInstance__") return;; + result[key] = err[key]; + }); + result.name = err.name; + result.stack = err.stack; + result.message = err.message; + } else { + result = err; + } + } else { + resolve = task.resolve; + result = payload.result; + } + if (this._runningIsolatedTask) { + if (this._queuedIsolatedTask) { + if (this.runningTaskCount() === 0) { + this.performTask(this._queuedIsolatedTask); + } + } else { + if (payload.error) { + result.stdout = this._stdout; + result.stderr = this._stderr; + } else { + result = new ResultWithOutput(result, this._stdout, this._stderr); + } + this._stderr = this._stdout = ""; + this._performingIsolatedTask = false; + this._runningIsolatedTask = false; + this._bufferingStdio = false; + this.kill(); + this.init(); + } + } + resolve(result); + if (!this._runningIsolatedTask) { + jobRunner._workerIdleNotification(); + } +}; + +Worker.prototype.kill = function() { + if (this._c) { + this.removeListeners(); + this._c.kill("SIGKILL"); + this._c = null; + } +}; + +Worker.prototype.progress = function(payload) { + var id = payload.id; + var task = this._tasks[id]; + if (task && typeof task.progress === "function") { + task.progress.call(undefined, payload.value); + } +}; + +Worker.prototype.outputFlushed = function() { + this._bufferingStdio = true; + this._c.send({type: "outputFlushedAck"}); +}; + + + +function bind(fn, ctx) {return function() {return fn.apply(ctx, arguments); };} + + +function getTaskFunction(context, code) { + with (context) { + return eval( "(" + code + ")"); + } +} + +if (require.main === module) { + var __requirePath = process.env.requirePath; + var __oldreq = require; + var __path = require("path"); + require = function(p) { + if (p.charAt(0) === ".") { + p = __path.join(__requirePath, p); + } + return __oldreq(p); + }; + if (process.env.initialCode) { + eval(process.env.initialCode); + } + (function() { + function waitForOutput() { + return new Promise(function(resolve, reject) { + var flushCount = 0; + function onFlushed() { + flushCount++; + if (flushCount === 2) { + resolve(); + } + } + function checkStream(stream) { + if (stream.bufferSize === 0) { + onFlushed(); + } else { + stream.write("", "utf-8", onFlushed); + } + } + checkStream(process.stdout); + checkStream(process.stderr); + }); + } + + function waitForPreviousOutput(id) { + return waitForOutput().then(function() { + var ret = waitForFlushAck(id); + process.send({type: "outputFlushed", id: id}); + return ret; + }); + } + + var ackWaitResolve = null; + function waitForFlushAck(id) { + assert(ackWaitResolve === null); + return new Promise(function(resolve) { + ackWaitResolve = resolve; + }); + } + var noop = function() {}; + var noopWrite = function(_, a, b) { + if (typeof a === "function") return a(); + if (typeof b === "function") return b(); + }; + + function toSerializableError(err) { + if (err instanceof Error) { + var ret = Object.create(null); + Object.keys(err).forEach(function(key){ + ret[key] = err[key]; + }); + ret.name = err.name; + ret.stack = err.stack; + ret.message = err.message; + ret.__isErrorInstance__ = true; + return ret; + } else { + return err; + } + } + + var actions = { + newTask: function(payload) { + var task = payload.task; + var code = task.code; + var context = task.context; + var id = payload.id; + var promise = payload.isolated + ? waitForPreviousOutput(id) : Promise.resolve(); + + + return promise + .then(function() { + if (payload.log === false && payload.isolated) { + process.stdout.write = noopWrite; + } + var fn = getTaskFunction(context, code); + if (typeof fn !== "function") + throw new Error("fn must be function"); + return fn(payload.progress ? function(value) { + process.send({type: "progress", id: id, value: value}); + } : noop); + }) + .finally(function() { + if (payload.isolated) { + return waitForOutput(); + } + }) + .then(function(result) { + process.send({ + type: "taskComplete", + id: payload.id, + result: result + }); + }) + .catch(function(error) { + process.send({ + type: "taskComplete", + id: payload.id, + error: toSerializableError(error), + isError: true + }); + }); + }, + + outputFlushedAck: function() { + var resolve = ackWaitResolve; + ackWaitResolve = null; + resolve(); + }, + + addGlobals: function(payload) { + new Function(payload.code)(); + } + }; + + process.on("message", function(payload) { + setImmediate(function() { + actions[payload.type].call(actions, payload); + }); + }); + })(); +} else { + module.exports = jobRunner; + Object.defineProperty(module.exports, "CHILD_PROCESSES", { + value: CHILD_PROCESSES, + writable: false + }); +} diff --git a/tools/jshint.js b/tools/jshint.js new file mode 100644 index 0000000..e5af351 --- /dev/null +++ b/tools/jshint.js @@ -0,0 +1,20 @@ +var utils = require("./utils.js"); +var path = require("path"); + +module.exports = function() { + var wd = path.join(__dirname, ".."); + return utils.run("node_modules/jshint/bin/jshint", [ + "--verbose", + "--reporter", + "node_modules/jshint-stylish/stylish.js", + "src/" + ], wd); +}; +function log(value) { + process.stdout.write(value.stdout); + process.stderr.write(value.stderr); +} +if (require.main === module) { + module.exports().then(log, log); +} + diff --git a/tools/jshintrc_generator.js b/tools/jshintrc_generator.js new file mode 100644 index 0000000..a24a7c3 --- /dev/null +++ b/tools/jshintrc_generator.js @@ -0,0 +1,53 @@ +var assert = require("assert"); +assert.equal(require.main, module); +// Since many globals are dynamic, this file is needed to generate jshintrc dynamically +var Promise = require("bluebird"); +var path = require("path"); +Promise.longStackTraces(); +var fs = Promise.promisifyAll(require("fs")); + +var constantsFile = path.join(__dirname, "..", "src", "constants.js"); +var globals = fs.readFileAsync(constantsFile, "utf8").then(function(contents) { + var rconstantname = /CONSTANT\(\s*([^,]+)/g; + var m; + var globals = { + Symbol: false, + Map: false, + JSON: false, + Error: true, + args: true, + chrome: true, + INLINE_SLICE: false, + INLINE_SLICE_LEFT_PADDED: false, + BIT_FIELD_CHECK: false, + BIT_FIELD_READ: false, + USE: false, + global: true, + setImmediate: true, + Promise: true, + WebKitMutationObserver: true, + TypeError: true, + RangeError: true, + __DEBUG__: false, + __BROWSER__: false, + process: true, + self: true, + "console": false, + "require": false, + "module": false, + "define": false + }; + while((m = rconstantname.exec(contents))) { + globals[m[1]] = false; + } + return globals; +}); + +var jshintrcFile = path.join(__dirname, "..", ".jshintrc"); +var jshintrc = fs.readFileAsync(jshintrcFile, "utf8").then(JSON.parse); + +Promise.join(jshintrc, globals, function(jshintrc, globals) { + jshintrc.globals = globals; + var json = JSON.stringify(jshintrc, null, " "); + return fs.writeFileAsync(jshintrcFile, json); +}); diff --git a/tools/mocha_runner.js b/tools/mocha_runner.js new file mode 100644 index 0000000..96ce3ca --- /dev/null +++ b/tools/mocha_runner.js @@ -0,0 +1,143 @@ +module.exports = function mochaRun(progress) { + var currentTime = 0; + var timers = {}; + var currentId = 0; + + function checkTimers() { + var keys = Object.keys(timers); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + var timer = timers[key]; + if (!timer) continue; + if (currentTime >= (timer.started + timer.time)) { + if (timer.interval) { + timer.started = currentTime; + } else { + delete timers[key]; + } + var fn = timer.fn; + if (timer.domain) timer.domain.enter(); + fn(); + if (timer.domain) timer.domain.exit(); + } + } + } + + function setInterval(fn, time) { + var id = currentId++; + time = (+time || 0) | 0; + if (time < 0) time = 0; + timers[id] = { + fn: fn, + time: time, + started: currentTime, + interval: true, + domain: process.domain + }; + return id; + } + + function setTimeout(fn, time) { + var id = currentId++; + time = (+time || 0) | 0; + if (time < 11) time = 11; + timers[id] = { + fn: fn, + time: time, + started: currentTime, + interval: false, + domain: process.domain + }; + return id; + } + + function clearTimeout(id) { + delete timers[id]; + } + + var clearInterval = clearTimeout; + if (fakeTimers) { + (function timerLoop() { + currentTime += 10; + try { + checkTimers(); + } finally { + setImmediate(timerLoop); + } + })(); + + global.oldSetTimeout = global.setTimeout; + global.oldClearTimeout = global.clearTimeout; + global.setTimeout = setTimeout; + global.clearTimeout = clearTimeout; + global.setInterval = setInterval; + global.clearInterval = clearInterval; + } + var failures = []; + delete Error.__BluebirdErrorTypes__; + global.adapter = cover + ? require("./js/instrumented/bluebird.js") + : require("./js/debug/bluebird.js"); + global.Promise = adapter; + Promise = adapter; + adapter.defer = adapter.pending = function() { + var ret = {}; + ret.promise = new Promise(function(resolve, reject) { + ret.resolve = ret.fulfill = resolve; + ret.reject = reject; + }); + return ret; + }; + Promise.config({cancellation: true}); + Promise.config({longStackTraces: false}); + Promise.config({longStackTraces: true}); + return Promise.each(testGroup, function(test, index, length) { + var mocha = new Mocha({ + reporter: "spec", + timeout: "300s", + enableTimeouts: true, + slow: Infinity, + bail: true + }); + mocha.addFile(test.path); + return new Promise(function(resolve, reject) { + mocha.run(function(failures) { + if (failures === 0) { + test.failed = false; + progress(test); + } + resolve(); + }).on("fail", function(_, err) { + test.failed = true; + progress(test); + failures.push({ + name: test.name, + error: err + }); + }); + }); + }).then(function() { + function failAdvice(failedTestFileName) { + return "For additional details you can run it individually " + + " with the command `node tools/test --run=" + failedTestFileName + "`"; + } + if (failures.length > 0) { + var error; + if (singleTest) { + error = failures[0].error; + } + else { + var message = "\u001b[31mSome tests failed: \u001b[m\n" + failures.forEach(function(failResult) { + message += " " + failResult.name + " " + failAdvice(failResult.name) + "\n"; + }); + error = new Error(message); + error.noStackPrint = true; + } + throw error; + } + if (cover) { + return __coverage__; + } + }); +}; diff --git a/tools/saucelabs_runner.js b/tools/saucelabs_runner.js new file mode 100644 index 0000000..c6da6b8 --- /dev/null +++ b/tools/saucelabs_runner.js @@ -0,0 +1,198 @@ +/* + +Ripped from grunt-saucelabs node module and made independent on grunt + +MIT license: + +Copyright (c) 2012 Parashuram + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + +module.exports = function(options) { + var Promise = require("bluebird"); + var SauceTunnel = require('grunt-saucelabs/node_modules/sauce-tunnel/index.js'); + var TestRunner = require('grunt-saucelabs/src/TestRunner.js'); + + function reportProgress(notification) { + switch (notification.type) { + case 'tunnelOpen': + console.log('=> Starting Tunnel to Sauce Labs'); + break; + case 'tunnelOpened': + console.log('Connected to Saucelabs'); + break; + case 'tunnelClose': + console.log('=> Stopping Tunnel to Sauce Labs'); + break; + case 'tunnelEvent': + console.log(notification.text); + break; + case 'jobStarted': + console.log('\n', notification.startedJobs, '/', notification.numberOfJobs, 'tests started'); + break; + case 'jobCompleted': + console.log('\nTested %s', notification.url); + console.log('Platform: %s', notification.platform); + + if (notification.tunnelId && unsupportedPort(notification.url)) { + console.log('Warning: This url might use a port that is not proxied by Sauce Connect.'); + } + + console.log('Passed: %s', notification.passed); + console.log('Url %s', notification.jobUrl); + break; + case 'testCompleted': + console.log('All tests completed with status %s', notification.passed); + break; + case 'retrying': + console.log('Timed out, retrying'); + break; + default: + console.error('Unexpected notification type'); + } + } + + function createTunnel(arg) { + var tunnel; + + reportProgress({ + type: 'tunnelOpen' + }); + + tunnel = new SauceTunnel(arg.username, arg.key, arg.identifier, true, ['-P', '0'].concat(arg.tunnelArgs)); + + ['write', 'writeln', 'error', 'ok', 'debug'].forEach(function (method) { + tunnel.on('log:' + method, function (text) { + reportProgress({ + type: 'tunnelEvent', + verbose: false, + method: method, + text: text + }); + }); + tunnel.on('verbose:' + method, function (text) { + reportProgress({ + type: 'tunnelEvent', + verbose: true, + method: method, + text: text + }); + }); + }); + + return tunnel; + } + + function runTask(arg, framework) { + var tunnel; + + return Promise + .try(function () { + var deferred; + + if (arg.tunneled) { + deferred = Promise.defer(); + + tunnel = createTunnel(arg); + tunnel.start(function (succeeded) { + if (!succeeded) { + deferred.reject('Could not create tunnel to Sauce Labs'); + } else { + reportProgress({ + type: 'tunnelOpened' + }); + + deferred.resolve(); + } + }); + return deferred.promise; + } + }) + .then(function () { + var testRunner = new TestRunner(arg, framework, reportProgress); + return testRunner.runTests(); + }) + .finally(function () { + var deferred; + + if (tunnel) { + deferred = Promise.defer(); + + reportProgress({ + type: 'tunnelClose' + }); + + tunnel.stop(function () { + deferred.resolve(); + }); + + return deferred.promise; + } + }); + } + + function unsupportedPort(url) { + // Not all ports are proxied by Sauce Connect. List of supported ports is + // available at https://saucelabs.com/docs/connect#localhost + var portRegExp = /:(\d+)\//; + var matches = portRegExp.exec(url); + var port = matches ? parseInt(matches[1], 10) : null; + var supportedPorts = [ + 80, 443, 888, 2000, 2001, 2020, 2109, 2222, 2310, 3000, 3001, 3030, + 3210, 3333, 4000, 4001, 4040, 4321, 4502, 4503, 4567, 5000, 5001, 5050, 5555, 5432, 6000, + 6001, 6060, 6666, 6543, 7000, 7070, 7774, 7777, 8000, 8001, 8003, 8031, 8080, 8081, 8765, + 8888, 9000, 9001, 9080, 9090, 9876, 9877, 9999, 49221, 55001 + ]; + + if (port) { + return supportedPorts.indexOf(port) === -1; + } + + return false; + } + + var defaults = { + username: process.env.SAUCE_USERNAME, + key: process.env.SAUCE_ACCESS_KEY, + tunneled: true, + identifier: Math.floor((new Date()).getTime() / 1000 - 1230768000).toString(), + pollInterval: 1000 * 2, + maxPollRetries: 0, + testname: '', + browsers: [{}], + tunnelArgs: [], + sauceConfig: {}, + maxRetries: 0 + }; + + var opts = {}; + options = options || {}; + Object.keys(defaults).forEach(function(key) { + opts[key] = defaults[key]; + }); + Object.keys(options).forEach(function(key) { + opts[key] = options[key]; + }); + + return runTask(opts, "mocha"); +}; diff --git a/tools/test.js b/tools/test.js new file mode 100644 index 0000000..27f5355 --- /dev/null +++ b/tools/test.js @@ -0,0 +1,315 @@ +process.env.BLUEBIRD_WARNINGS = 0; +var assert = require("assert"); +assert.equal(require.main, module); +var Promise = require("bluebird"); +var build = require("./build.js"); +var utils = require("./utils.js"); +var tableLogger = utils.tableLogger; +var argv = require("optimist").argv; +var glob = Promise.promisify(require("glob")); +var path = require("path"); +var mkdirp = Promise.promisify(require("mkdirp")); +var rimraf = Promise.promisify(require("rimraf")); +var jobRunner = require("./job-runner/job-runner.js"); +var mochaRunner = require("./mocha_runner.js"); +var fs = Promise.promisifyAll(require("fs")); +var testUtils = require("../test/mocha/helpers/util"); +jobRunner.setVerbose(0); +// Random slowness after tests complete +function getTests(options) { + var g; + + if (options.testName === "all" || options.testName.indexOf("no") === 0) { + g = "./test/mocha/*.js"; + } else if (options.testName === "aplus") { + g = "./test/mocha/[0-9].[0-9].[0-9].js"; + } else if (options.testName.indexOf("*") >= 0) { + g = "./test/mocha/" + options.testName; + } else { + var testName = options.testName.replace(/^(\d)(\d)(\d)$/, "$1.$2.$3"); + g = "./test/mocha/" + testName + ".js"; + } + + var excludes = []; + if (options.testName.indexOf("no") === 0) { + excludes = options.testName.slice(2).split(","); + } + + return glob(g).then(function(matches) { + return matches.filter(function(match) { + if (match.indexOf("generator") >= 0) { + return options.generators; + } + + for (var i = 0; i < excludes.length; ++i) { + if (match.indexOf(excludes[i]) >= 0) { + return false; + } + } + + return true; + }) + }).tap(function(m) { + if (m.length === 0) { + throw new Error("No test file matches: '" + options.testName + "'"); + } + }).map(function(filePath, i) { + var name = path.basename(filePath); + return { + name: name, + path: filePath, + index: i, + nameMatcher: "\\b" + + name.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + + "\\b" + }; + }); +} + +function getColorForCoverage(coveragePct) { + var colorThresholds = { + 95: "brightgreen", + 85: "green", + 80: "yellowgreen", + 70: "yellow", + 60: "red" + }; + var values = Object.keys(colorThresholds).map(Number).sort(function(a, b) { + return b - a; + }); + for (var i = 0; i < values.length; ++i) { + if (coveragePct >= values[i]) return colorThresholds[values[i].toString()]; + } + return colorThresholds[values[values.length - 1].toString()]; +} + +function getCoverage() { + return utils.run("npm", ["run", "istanbul", "--", "report", "text-summary"]).then(function(result) { + var stdout = result.stdout; + var pctPattern = /(\d+\.\d+)%/g; + var matches = stdout.match(pctPattern); + var sum = matches.map(function(pct) { + return parseFloat(pct.replace(/[^0-9.]/g, "")) + }).reduce(function(a, b) { + return a + b; + }, 0); + var average = Math.round(sum / matches.length); + return average; + }); +} + +function generateCoverageBadge(coverage) { + var text = "coverage-" + coverage + "%"; + var color = getColorForCoverage(coverage); + var imgSrc = "http://img.shields.io/badge/" + text + "-" + color + ".svg?style=flat"; + var link = "http://petkaantonov.github.io/bluebird/coverage/debug/index.html"; + var markdown = "[!["+text+"]("+imgSrc+")]("+link+")"; + return markdown; +} + +function writeCoverageFile(coverage, groupNumber) { + var dir = build.dirs.coverage; + var fileName = path.join(dir, "coverage-group" + groupNumber + ".json"); + var json = JSON.stringify(coverage); + return fs.writeFileAsync(fileName, json, "utf8").thenReturn(fileName); +} + +function needsFreshProcess(testName) { + return /regress|using|domain|multiple-copies|unhandled_rejections|nodeify|getNewLibraryCopy|long_stack_traces/.test(testName) || + testUtils.isOldNode && /api_exceptions|promisify/.test(testName); +} + +function runTestGroup(testGroup, options, progress) { + return jobRunner.run(mochaRunner, { + isolated: !options.singleTest, + log: options.singleTest, + progress: progress, + context: { + testGroup: testGroup, + singleTest: options.singleTest, + fakeTimers: options.fakeTimers, + cover: options.cover + } + }); +} + +function combineTests(tests) { + var arrays = new Array(jobRunner.CHILD_PROCESSES); + for (var i = 0; i < arrays.length; ++i) { + arrays[i] = []; + } + + var initialLength = arrays.length; + for (var i = 0; i < tests.length; ++i) { + var test = tests[i]; + if (needsFreshProcess(test.name)) { + arrays.push([test]); + } else { + arrays[i % initialLength].push(tests[i]); + } + + } + return arrays; +} + +var testName = "all"; +if ("run" in argv) { + testName = (argv.run + ""); + if (testName.indexOf("*") === -1) { + testName = testName.toLowerCase() + .replace( /\.js$/, "" ) + .replace( /[^,a-zA-Z0-9_\-.]/g, "" ); + } +} + +var options = { + generators: (function() { + try { + new Function("(function*(){})"); + return true; + } catch (e) { + return false; + } + })() || !!argv.nw, + cover: !!argv["cover"], + testName: testName, + singleTest: false, + saucelabs: !!argv.saucelabs, + testBrowser: !!argv.saucelabs || !!argv.browser || !!argv.nw, + executeBrowserTests: !!argv.saucelabs || !!argv.nw || (typeof argv["execute-browser-tests"] === "boolean" ? + argv["execute-browser-tests"] : !!argv.browser), + openBrowser: typeof argv["open-browser"] === "boolean" ? argv["open-browser"] : true, + port: argv.port || 9999, + fakeTimers: typeof argv["fake-timers"] === "boolean" + ? argv["fake-timers"] : true, + jsHint: typeof argv["js-hint"] === "boolean" ? argv["js-hint"] : true, + nw: !!argv.nw, + nwPath: argv["nw-path"] +}; + + + +if (options.cover && typeof argv["cover"] === "string") { + options.coverFormat = argv["cover"]; +} else { + options.coverFormat = "html"; +} + +var jsHint = options.jsHint ? require("./jshint.js")() : Promise.resolve(); +var tests = getTests(options); +var buildOpts = { + debug: true +}; +if (options.testBrowser) { + buildOpts.browser = true; + buildOpts.minify = true; +} +var buildResult = build(buildOpts); +if (options.cover) { + var exclusions = ["assert.js", "captured_trace.js"]; + var coverageInstrumentedRoot = build.ensureDirectory(build.dirs.instrumented,options.cover, true); + var coverageReportsRoot = mkdirp(build.dirs.coverage, true).then(function() { + return fs.readdirAsync(build.dirs.coverage); + }).map(function(fileName) { + var filePath = path.join(build.dirs.coverage, fileName); + if (path.extname(fileName).indexOf("json") === -1) { + return rimraf(filePath); + } + }); + buildResult = Promise.join(coverageInstrumentedRoot, buildResult, coverageReportsRoot, function() { + return utils.run("npm", ["-v"]).then(function(result) { + var version = result.stdout.split(".").map(Number); + if (version[0] < 2) { + throw new Error("Npm version 2.x.x required, current version is " + result.stdout); + } + }); + }).tap(function() { + var copyExclusions = Promise.map(exclusions, function(exclusion) { + var fromPath = path.join(build.dirs.debug, exclusion); + var toPath = path.join(build.dirs.instrumented, exclusion); + return fs.readFileAsync(fromPath, "utf8").then(function(contents) { + return fs.writeFileAsync(toPath, contents, "utf8"); + }); + }); + var args = [ + "run", + "istanbul", + "--", + "instrument", + "--output", + build.dirs.instrumented, + "--no-compact", + "--preserve-comments", + "--embed-source" + ]; + exclusions.forEach(function(x) { + args.push("-x", x); + }); + args.push(build.dirs.debug); + var istanbul = utils.run("npm", args, null, true); + return Promise.all([istanbul, copyExclusions]); + }); +} + +var testResults = Promise.join(tests, buildResult, function(tests) { + var singleTest = tests.length === 1; + options.singleTest = singleTest; + process.stdout.write("\u001b[m"); + if (options.testBrowser) { + return require("./browser_test_generator.js")(tests, options); + } else if (singleTest) { + return runTestGroup(tests, options); + } else { + utils.cursorTo(0, 0); + utils.clearScreenDown(); + tableLogger.addTests(tests); + return Promise.map(combineTests(tests), function(testGroup, index) { + return runTestGroup(testGroup, options, function(test) { + if (test.failed) { + tableLogger.testFail(test); + } else { + tableLogger.testSuccess(test); + } + }).then(function(maybeCoverage) { + if (options.cover) { + return writeCoverageFile(maybeCoverage.result, index + 1); + } + }) + }).then(function() { + var p = Promise.resolve(); + if (options.cover) { + var coverage = getCoverage(); + if (process.execPath.indexOf("iojs") >= 0 && testName === "all") { + p = p.return(coverage).then(generateCoverageBadge).then(console.log); + } + p = p.then(function() { + return utils.run("npm", ["run", "istanbul", "--", "report", options.coverFormat], null, true); + }).return(coverage).then(function(coverage) { + console.log("Total coverage " + coverage + "%"); + }); + } + console.log("All tests passed"); + return p; + }); + } +}); + +Promise.all([testResults, jsHint]).spread(function(_, jsHintResponse) { + if (jsHintResponse) { + console.log("JSHint:"); + console.log(jsHintResponse.stdout); + console.log(jsHintResponse.stderr); + } +}).catch(function(e) { + if (e && e.stdout || e.stderr) { + console.log(e.stdout); + console.error(e.stderr); + } + + if (!e || !e.stack) { + console.error(e + ""); + } else { + console.error(e.noStackPrint ? e.message : e.stack); + } + process.exit(2); +}); diff --git a/tools/utils.js b/tools/utils.js new file mode 100755 index 0000000..d082a4f --- /dev/null +++ b/tools/utils.js @@ -0,0 +1,216 @@ +var Promise = require("bluebird"); +var assert = require("assert"); +var path = require("path"); +var spawn = require("cross-spawn"); +Promise.longStackTraces(); +var fs = Promise.promisifyAll(require("fs")); +var notAscii = /[^\u000D\u0019-\u007E]/; +var Table = require('cli-table'); + +function noStackError(message) { + var e = new Error(message); + e.noStackPrint = true; + return e; +} + +function checkAscii(fileName, contents) { + if (notAscii.test(contents)) { + contents.split("\n").forEach(function(line, i) { + if (notAscii.test(line)) { + var lineNo = i + 1; + var col = line.indexOf(RegExp.lastMatch) + 1; + var code = "U+" + (("0000" + line.charCodeAt(col-1) + .toString(16)).slice(-4)); + code = RegExp.lastMatch + " (" + code.toUpperCase() + ")"; + var fullPath = path.join(process.cwd(), "src", fileName); + throw noStackError("The file " + fullPath + "\ncontains an illegal character: " + + code + " on line " + lineNo + " at column " + col); + } + }); + } +} + +var license; +function getLicense() { + if (license) return license; + var licenseFile = path.join(__dirname, "..", "LICENSE"); + return license = fs.readFileAsync(licenseFile, "utf8").then(function(text) { + return "/* @preserve\n" + text.split("\n").map(function(line) { + return " * " + line; + }).join("\n") + "\n */\n"; + }); +} + +function cursorTo(x, y) { + if (process.stdout.cursorTo) + process.stdout.cursorTo(x, y); +} + +function clearScreenDown() { + if (process.stdout.clearScreenDown) + process.stdout.clearScreenDown(); +} + +function run(cmd, args, dir, log) { + return new Promise(function(resolve, reject) { + function makeResult(errorMessage) { + var ret = errorMessage ? new Error(errorMessage) : {}; + ret.stdout = out.trim(); + ret.stderr = err.trim(); + return ret; + } + + var out = ""; + var err = ""; + var c = spawn(cmd, args, {stdin: ["ignore", "ignore", "ignore"], cwd: dir || process.cwd()}); + + c.stdout.on("data", function(data) { + if (log) process.stdout.write(data.toString()); + out += data; + }); + c.stderr.on("data", function(data) { + if (log) process.stderr.write(data.toString()); + err += data; + }); + + c.on("error", function(err) { + reject(makeResult(err.message)); + }); + c.on("close", function(code) { + if (code == 0) resolve(makeResult()) + else reject(makeResult(path.basename(cmd) + " exited with code: " + code + "\n" + err.trim())); + }) + }); +} + +function parseDeps(src) { + var rdeps = /function\s*\(\s*([^)]+)\)/; + var match = rdeps.exec(src); + assert.equal(match.length, 2); + var deps = match[1].split(/\s*,\s*/g).map(function(val) { + return val.trim(); + }); + return deps; +} + +var tableLogger = (function() { + var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/; + var functionKeyCodeReAnywhere = new RegExp('(?:\x1b+)(O|N|\\[|\\[\\[)(?:' + [ + '(\\d+)(?:;(\\d+))?([~^$])', + '(?:M([@ #!a`])(.)(.))', // mouse + '(?:1;)?(\\d+)?([a-zA-Z])' + ].join('|') + ')'); + + function stripVTControlCharacters(str) { + str = str.replace(new RegExp(functionKeyCodeReAnywhere.source, 'g'), ''); + return str.replace(new RegExp(metaKeyCodeReAnywhere.source, 'g'), ''); + } + + var ROWS = 35; + var log = new Array(ROWS); + for (var i = 0; i < ROWS; ++i) log[i] = []; + var tableOpts = { + chars: { + 'mid': '', + 'left-mid': '', + 'mid-mid': '', + 'right-mid': '' + }, + style: { + 'padding-left': 0, + 'padding-right': 0, + compact: true + } + }; + var table; + var split; + + function showTable() { + assert(!table); + table = new Table(tableOpts); + table.push.apply(table, log); + table = table.toString(); + split = table.split("\n").map(function(line) { + return stripVTControlCharacters(line); + }); + cursorTo(0, 0); + process.stdout.write(table); + cursorTo(0, split.length + 1); + } + + function addTests(tests) { + var cols = 0; + tests.forEach(function(test) { + var index = test.index; + var row = index % ROWS; + var column = (index / ROWS) | 0; + cols = Math.max(column, cols); + log[row][column] = "\u001b[m" + test.name + " \u001b[31m\u00D7 "; + }); + cols = cols + 1; + for (var i = 0; i < log.length; ++i) { + var row = log[i]; + for (var j = 0; j < cols; ++j) { + if (!row[j]) { + row[j] = " "; + } + } + } + showTable(); + } + + function getPosition(test) { + for (var y = 0; y < split.length; ++y) { + var s = split[y]; + var x = s.search(new RegExp(test.nameMatcher)); + if (x >= 0) { + return { + x: x + test.name.length, + y: y + }; + } + } + assert(false); + } + + function update(test, message) { + var pos = getPosition(test); + cursorTo(pos.x + 1, pos.y); + process.stdout.write(message); + cursorTo(0, split.length + 2); + } + + function testFail(test) { + update(test, "\u001b[31m\u00D7 FAILURE\u001b[39m"); + } + + + function testSuccess(test) { + update(test, "\u001b[32m\u221A\u001b[39m") + } + + return { + addTests: addTests, + testFail: testFail, + testSuccess: testSuccess + } +})(); + +function stringToStream(str) { + var Readable = require('stream').Readable; + var readable = new Readable() + readable.push(str + ""); + readable.push(null); + return readable; +} + +module.exports = { + checkAscii: checkAscii, + getLicense: getLicense, + run: run, + parseDeps: parseDeps, + tableLogger: tableLogger, + stringToStream: stringToStream, + cursorTo: cursorTo, + clearScreenDown: clearScreenDown +};