From be76ba655ce5e1fafb1294e07abcd081d8d2351e Mon Sep 17 00:00:00 2001 From: zhouganqing Date: Fri, 17 Feb 2023 11:33:40 +0800 Subject: [PATCH] Import Upstream version 3.7.2+dfsg1 --- .editorconfig | 14 + .gitignore | 11 + .istanbul.yml | 49 + .jshintignore | 1 + .jshintrc | 161 ++ .travis.yml | 26 + API.md | 1 + CONTRIBUTING.md | 9 + LICENSE | 21 + README.md | 57 + bench | 39 + bower.json | 34 + build | 2 + changelog.md | 1 + deprecated_apis.md | 1 + issue_template.md | 12 + package-lock.json | 3209 +++++++++++++++++++++ package.json | 78 + src/any.js | 25 + src/assert.js | 55 + src/async.js | 129 + src/bind.js | 67 + src/bluebird.js | 11 + src/call_get.js | 123 + src/cancel.js | 136 + src/catch_filter.js | 42 + src/constants.js | 154 + src/context.js | 69 + src/debuggability.js | 1043 +++++++ src/direct_resolve.js | 46 + src/each.js | 30 + src/errors.js | 117 + src/es5.js | 80 + src/filter.js | 12 + src/finally.js | 146 + src/generators.js | 230 ++ src/join.js | 165 ++ src/map.js | 192 ++ src/method.js | 57 + src/nodeback.js | 51 + src/nodeify.js | 62 + src/promise.js | 827 ++++++ src/promise_array.js | 203 ++ src/promisify.js | 326 +++ src/props.js | 125 + src/queue.js | 79 + src/race.js | 50 + src/reduce.js | 191 ++ src/schedule.js | 79 + src/settle.js | 55 + src/some.js | 159 + src/synchronous_inspection.js | 103 + src/thenables.js | 89 + src/timers.js | 93 + src/using.js | 226 ++ src/util.js | 432 +++ test/mocha/2.1.2.js | 76 + test/mocha/2.1.3.js | 76 + test/mocha/2.2.1.js | 41 + test/mocha/2.2.2.js | 151 + test/mocha/2.2.3.js | 151 + test/mocha/2.2.4.js | 182 ++ test/mocha/2.2.5.js | 58 + test/mocha/2.2.6.js | 257 ++ test/mocha/2.2.7.js | 109 + test/mocha/2.3.1.js | 34 + test/mocha/2.3.2.js | 126 + test/mocha/2.3.3.js | 993 +++++++ test/mocha/2.3.4.js | 69 + test/mocha/3.2.1.js | 41 + test/mocha/3.2.2.js | 187 ++ test/mocha/3.2.3.js | 187 ++ test/mocha/3.2.4.js | 31 + test/mocha/3.2.5.js | 257 ++ test/mocha/3.2.6.js | 322 +++ test/mocha/any.js | 112 + test/mocha/api_exceptions.js | 231 ++ test/mocha/async.js | 245 ++ test/mocha/async_hooks.js | 145 + test/mocha/bind.js | 1225 ++++++++ test/mocha/bluebird-multiple-instances.js | 107 + test/mocha/call.js | 57 + test/mocha/cancel.js | 3184 ++++++++++++++++++++ test/mocha/catch_filter.js | 431 +++ test/mocha/collections_thenables.js | 355 +++ test/mocha/constructor.js | 201 ++ test/mocha/cycles.js | 68 + test/mocha/direct_resolving.js | 257 ++ test/mocha/domain.js | 503 ++++ test/mocha/done.js | 131 + test/mocha/each.js | 184 ++ test/mocha/error.js | 184 ++ test/mocha/filter.js | 120 + test/mocha/finally.js | 274 ++ test/mocha/following.js | 185 ++ test/mocha/generator.js | 754 +++++ test/mocha/get.js | 83 + test/mocha/getNewLibraryCopy.js | 30 + test/mocha/github-2xx-76.js | 21 + test/mocha/github-3.6.4.js | 40 + test/mocha/github-3.7.3.js | 14 + test/mocha/github-4.1.7.js | 46 + test/mocha/github36.js | 59 + test/mocha/helpers/assert_long_trace.js | 86 + test/mocha/helpers/bluebird0_7_0.js | 2272 +++++++++++++++ test/mocha/helpers/error.js | 1 + test/mocha/helpers/reasons.js | 56 + test/mocha/helpers/testThreeCases.js | 64 + test/mocha/helpers/thenables.js | 146 + test/mocha/helpers/util.js | 280 ++ test/mocha/is.js | 15 + test/mocha/join.js | 119 + test/mocha/late_buffer_safety.js | 37 + test/mocha/long_stack_traces.js | 537 ++++ test/mocha/map.js | 295 ++ test/mocha/method.js | 134 + test/mocha/monitoring.js | 183 ++ test/mocha/multiple-copies.js | 18 + test/mocha/no_conflict.js | 21 + test/mocha/nodeify.js | 200 ++ test/mocha/promise_array.js | 178 ++ test/mocha/promisify.js | 1116 +++++++ test/mocha/props.js | 220 ++ test/mocha/race.js | 121 + test/mocha/reduce.js | 705 +++++ test/mocha/reflect.js | 23 + test/mocha/regress.js | 169 ++ test/mocha/rejections.js | 220 ++ test/mocha/resolution.js | 434 +++ test/mocha/schedule.js | 48 + test/mocha/settle.js | 172 ++ test/mocha/some.js | 174 ++ test/mocha/spread.js | 304 ++ test/mocha/synchronous_inspection.js | 144 + test/mocha/tap.js | 66 + test/mocha/tapCatch.js | 130 + test/mocha/timers.js | 192 ++ test/mocha/try.js | 73 + test/mocha/unhandled_rejections.js | 875 ++++++ test/mocha/using.js | 450 +++ tests | 2 + tools/README.md | 19 + tools/ast_passes.js | 746 +++++ tools/browser_test_generator.js | 58 + tools/browser_test_runner.js | 97 + tools/build.js | 340 +++ tools/job-runner/job-runner.js | 557 ++++ tools/jshint.js | 20 + tools/jshintrc_generator.js | 53 + tools/mocha_runner.js | 143 + tools/saucelabs_runner.js | 198 ++ tools/test.js | 315 ++ tools/utils.js | 216 ++ 153 files changed, 35441 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .istanbul.yml create mode 100644 .jshintignore create mode 100644 .jshintrc create mode 100644 .travis.yml create mode 100644 API.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100755 bench create mode 100644 bower.json create mode 100755 build create mode 100644 changelog.md create mode 100644 deprecated_apis.md create mode 100644 issue_template.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/any.js create mode 100644 src/assert.js create mode 100644 src/async.js create mode 100644 src/bind.js create mode 100644 src/bluebird.js create mode 100644 src/call_get.js create mode 100644 src/cancel.js create mode 100644 src/catch_filter.js create mode 100644 src/constants.js create mode 100644 src/context.js create mode 100644 src/debuggability.js create mode 100644 src/direct_resolve.js create mode 100644 src/each.js create mode 100644 src/errors.js create mode 100644 src/es5.js create mode 100644 src/filter.js create mode 100644 src/finally.js create mode 100644 src/generators.js create mode 100644 src/join.js create mode 100644 src/map.js create mode 100644 src/method.js create mode 100644 src/nodeback.js create mode 100644 src/nodeify.js create mode 100644 src/promise.js create mode 100644 src/promise_array.js create mode 100644 src/promisify.js create mode 100644 src/props.js create mode 100644 src/queue.js create mode 100644 src/race.js create mode 100644 src/reduce.js create mode 100644 src/schedule.js create mode 100644 src/settle.js create mode 100644 src/some.js create mode 100644 src/synchronous_inspection.js create mode 100644 src/thenables.js create mode 100644 src/timers.js create mode 100644 src/using.js create mode 100644 src/util.js create mode 100644 test/mocha/2.1.2.js create mode 100644 test/mocha/2.1.3.js create mode 100644 test/mocha/2.2.1.js create mode 100644 test/mocha/2.2.2.js create mode 100644 test/mocha/2.2.3.js create mode 100644 test/mocha/2.2.4.js create mode 100644 test/mocha/2.2.5.js create mode 100644 test/mocha/2.2.6.js create mode 100644 test/mocha/2.2.7.js create mode 100644 test/mocha/2.3.1.js create mode 100644 test/mocha/2.3.2.js create mode 100644 test/mocha/2.3.3.js create mode 100644 test/mocha/2.3.4.js create mode 100644 test/mocha/3.2.1.js create mode 100644 test/mocha/3.2.2.js create mode 100644 test/mocha/3.2.3.js create mode 100644 test/mocha/3.2.4.js create mode 100644 test/mocha/3.2.5.js create mode 100644 test/mocha/3.2.6.js create mode 100644 test/mocha/any.js create mode 100644 test/mocha/api_exceptions.js create mode 100644 test/mocha/async.js create mode 100644 test/mocha/async_hooks.js create mode 100644 test/mocha/bind.js create mode 100644 test/mocha/bluebird-multiple-instances.js create mode 100644 test/mocha/call.js create mode 100644 test/mocha/cancel.js create mode 100644 test/mocha/catch_filter.js create mode 100644 test/mocha/collections_thenables.js create mode 100644 test/mocha/constructor.js create mode 100644 test/mocha/cycles.js create mode 100644 test/mocha/direct_resolving.js create mode 100644 test/mocha/domain.js create mode 100644 test/mocha/done.js create mode 100644 test/mocha/each.js create mode 100644 test/mocha/error.js create mode 100644 test/mocha/filter.js create mode 100644 test/mocha/finally.js create mode 100644 test/mocha/following.js create mode 100644 test/mocha/generator.js create mode 100644 test/mocha/get.js create mode 100644 test/mocha/getNewLibraryCopy.js create mode 100644 test/mocha/github-2xx-76.js create mode 100644 test/mocha/github-3.6.4.js create mode 100644 test/mocha/github-3.7.3.js create mode 100644 test/mocha/github-4.1.7.js create mode 100644 test/mocha/github36.js create mode 100644 test/mocha/helpers/assert_long_trace.js create mode 100644 test/mocha/helpers/bluebird0_7_0.js create mode 100644 test/mocha/helpers/error.js create mode 100644 test/mocha/helpers/reasons.js create mode 100644 test/mocha/helpers/testThreeCases.js create mode 100644 test/mocha/helpers/thenables.js create mode 100644 test/mocha/helpers/util.js create mode 100644 test/mocha/is.js create mode 100644 test/mocha/join.js create mode 100644 test/mocha/late_buffer_safety.js create mode 100644 test/mocha/long_stack_traces.js create mode 100644 test/mocha/map.js create mode 100644 test/mocha/method.js create mode 100644 test/mocha/monitoring.js create mode 100644 test/mocha/multiple-copies.js create mode 100644 test/mocha/no_conflict.js create mode 100644 test/mocha/nodeify.js create mode 100644 test/mocha/promise_array.js create mode 100644 test/mocha/promisify.js create mode 100644 test/mocha/props.js create mode 100644 test/mocha/race.js create mode 100644 test/mocha/reduce.js create mode 100644 test/mocha/reflect.js create mode 100644 test/mocha/regress.js create mode 100644 test/mocha/rejections.js create mode 100644 test/mocha/resolution.js create mode 100644 test/mocha/schedule.js create mode 100644 test/mocha/settle.js create mode 100644 test/mocha/some.js create mode 100644 test/mocha/spread.js create mode 100644 test/mocha/synchronous_inspection.js create mode 100644 test/mocha/tap.js create mode 100644 test/mocha/tapCatch.js create mode 100644 test/mocha/timers.js create mode 100644 test/mocha/try.js create mode 100644 test/mocha/unhandled_rejections.js create mode 100644 test/mocha/using.js create mode 100755 tests create mode 100644 tools/README.md create mode 100644 tools/ast_passes.js create mode 100644 tools/browser_test_generator.js create mode 100644 tools/browser_test_runner.js create mode 100644 tools/build.js create mode 100644 tools/job-runner/job-runner.js create mode 100644 tools/jshint.js create mode 100644 tools/jshintrc_generator.js create mode 100644 tools/mocha_runner.js create mode 100644 tools/saucelabs_runner.js create mode 100644 tools/test.js create mode 100755 tools/utils.js 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 +};