├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTION.md ├── LICENSE ├── Makefile ├── README.md ├── benchmarks ├── comparison.js ├── data.js └── package.json ├── bower.json ├── lib └── vow.js ├── package-lock.json ├── package.json ├── test ├── defer.notify.js ├── defer.reject.js ├── defer.resolve.js ├── event.unhandledRejection.js ├── promise.always.js ├── promise.delay.js ├── promise.done.js ├── promise.fail.js ├── promise.finally.js ├── promise.spread.js ├── promise.then.js ├── promise.timeout.js ├── promise.valueof.js ├── utils │ ├── aplus-adapter.js │ ├── helpers.js │ └── runner.js ├── vow.all.js ├── vow.allPatiently.js ├── vow.allResolved.js ├── vow.allSettled.js ├── vow.always.js ├── vow.any.js ├── vow.anyResolved.js ├── vow.cast.js ├── vow.delay.js ├── vow.done.js ├── vow.fail.js ├── vow.fulfill.js ├── vow.invoke.js ├── vow.isFulfilled.js ├── vow.isPromise.js ├── vow.isRejected.js ├── vow.isResolved.js ├── vow.promise.js ├── vow.reject.js ├── vow.resolve.js ├── vow.spread.js ├── vow.timeout.js └── vow.when.js ├── utils ├── gen-doc.js ├── jsdoc2html.yate └── update-gh-pages.sh └── vow.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | /benchmarks 3 | /test 4 | /utils 5 | /bower.json 6 | /CHANGELOG.md 7 | /CONTRIBUTION.md 8 | /Makefile -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.1 4 | branches: 5 | only: 6 | - master 7 | after_success: ./utils/update-gh-pages.sh 8 | env: 9 | global: 10 | secure: SDul0CRIgerC9FzqhAmh5r1qFIHdECxTC0rdDmRqKrpLaLg1nWBorokwkKoY95MDxy3wJto9M8gBeIN/1jejyLVibZqOPKTP4T17kd59korEsj+gkYatGDMw7X5+4djTLfLgSHuwSO9kRIhJMfReULUElYKql0LqCIlJ8WW312o= 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 0.4.20 4 | ----- 5 | * Added `Promise.allSettled` [#118](https://github.com/dfilatov/vow/issues/118) 6 | 7 | 0.4.18 8 | ----- 9 | * Added `promise.finally` [#116](https://github.com/dfilatov/vow/issues/116) 10 | 11 | 0.4.17 12 | ----- 13 | * Fixed an issue with wrong resolving after adding `onProgress` callback 14 | 15 | 0.4.16 16 | ----- 17 | * Fixed an issue with throwing exception within resolver function [#113](https://github.com/dfilatov/vow/issues/114) 18 | 19 | 0.4.15 20 | ----- 21 | * Fixed an issue with double emit of `PromiseRejectionEvent` [#113](https://github.com/dfilatov/vow/issues/113) 22 | 23 | 0.4.14 24 | ----- 25 | * Added support of unhandled rejection event [#111](https://github.com/dfilatov/vow/issues/111) 26 | 27 | 0.4.13 28 | ----- 29 | * Added workaround to avoid bug with `Array.prototype.push` in Opera 41 30 | 31 | 0.4.12 32 | ----- 33 | * Wrong propagation of progress state fixed 34 | 35 | 0.4.11 36 | ----- 37 | * Now global object (`window`, `global`) is passed properly 38 | 39 | 0.4.10 40 | ----- 41 | * Now `MutationObserver` is used for internal "next tick" operations 42 | 43 | 0.4.9 44 | ----- 45 | * `vow.cast` method was fixed to properly work with external promises [#88](https://github.com/dfilatov/vow/issues/88) 46 | 47 | 0.4.8 48 | ----- 49 | * Detection of ymaps modular system was improved [#82](https://github.com/dfilatov/vow/issues/82) 50 | 51 | 0.4.7 52 | ----- 53 | * `vow.all` had wrong behaviour in case of passing of another promise implementation [#77](https://github.com/dfilatov/vow/issues/77) 54 | * `vow.timeout` rejects with `vow.TimedOutError` instead of `Error` reason in case of timeout [#76](https://github.com/dfilatov/vow/issues/76) 55 | 56 | 0.4.6 57 | ----- 58 | * `defer.reject` had wrong behaviour in case of already rejected promise was passed [#72](https://github.com/dfilatov/vow/issues/72) 59 | * CommonJS environment detection became more accurate [#74](https://github.com/dfilatov/vow/issues/74) 60 | 61 | 0.4.5 62 | ----- 63 | * Throwing exceptions inside `vow.reject` was removed [#69](https://github.com/dfilatov/vow/issues/69) 64 | * `promise.isFulfilled`/`promise.isRejected` immediately return proper state of promise got from `vow.fulfill(value)`/`reject(value`) [#68](https://github.com/dfilatov/vow/issues/68) 65 | * Minor optimizations were added 66 | 67 | 0.4.4 68 | ----- 69 | * ENB sources were added 70 | 71 | 0.4.3 72 | ----- 73 | * Some optimizations for V8 were added [#60](https://github.com/dfilatov/vow/issues/60). Thanks to [B-Vladi](https://github.com/B-Vladi). 74 | 75 | 0.4.2 76 | ----- 77 | * Pass progress state from items in all arrays/objects methods [#58](https://github.com/dfilatov/vow/issues/58) 78 | 79 | 0.4.1 80 | ----- 81 | * Improve detection of vow-compatible promises 82 | 83 | 0.4.0 84 | ----- 85 | * Implement [DOM Promise](http://dom.spec.whatwg.org/#promises) specification 86 | * Implement [new Promise A+](https://github.com/promises-aplus/promises-spec) specification 87 | * Remove `promise.fulfill`, `promise.reject`, `promise.notify` methods 88 | * Add `vow.anyResolved` method [#53](https://github.com/dfilatov/vow/issues/53) 89 | * Add `vow.cast` method [#53](https://github.com/dfilatov/vow/issues/56) 90 | 91 | 0.3.12 92 | ------ 93 | * Make `Promise` class accessible from outside 94 | 95 | 0.3.11 96 | ------ 97 | * Fix bug with inner timer in `delay` method [#45](https://github.com/dfilatov/jspromise/issues/45) 98 | 99 | 0.3.10 100 | ------ 101 | * Use `setImmediate` instead of `process.nextTick` in Node.js >= 0.10.x [#40](https://github.com/dfilatov/jspromise/issues/40) 102 | * Up Promises/A+ Compliance Test Suite to 1.3.2 103 | 104 | 0.3.9 105 | ----- 106 | * Fix for propagation of progress state [#37](https://github.com/dfilatov/jspromise/issues/37) 107 | 108 | 0.3.8 109 | ----- 110 | * Fix for ignoring callback's context in always method [#35](https://github.com/dfilatov/jspromise/issues/35) 111 | * Callback in `Vow.invoke` called in global context now 112 | * bower.json added [#34](https://github.com/dfilatov/jspromise/issues/34) 113 | 114 | 0.3.7 115 | ----- 116 | * `Vow.allPatiently` method added [#32](https://github.com/dfilatov/jspromise/issues/32) 117 | 118 | 0.3.6 119 | ----- 120 | * Fix for properly work in mocha/phantomjs environment [#31](https://github.com/dfilatov/jspromise/issues/31) 121 | 122 | 0.3.5 123 | ----- 124 | * Fix for synchronize `onProgress` callback in `promise.sync` method [#30](https://github.com/dfilatov/jspromise/issues/30) 125 | 126 | 0.3.4 127 | ----- 128 | * Add ability to use multiple modules system simultaneously [#26](https://github.com/dfilatov/jspromise/issues/26) 129 | * Add callbacks to `promise.done` method [#29](https://github.com/dfilatov/jspromise/issues/29) 130 | 131 | 0.3.3 132 | ----- 133 | * Use `Vow` instead `this` in all static methods 134 | * Speed up optimizations 135 | 136 | 0.3.2 137 | ----- 138 | * Ability to specify context for callbacks [#28](https://github.com/dfilatov/jspromise/issues/28) 139 | 140 | 0.3.1 141 | ----- 142 | * Add support for [ym module's system](https://github.com/ymaps/modules) [#24](https://github.com/dfilatov/jspromise/issues/24) 143 | 144 | 0.3.0 145 | ----- 146 | * Add support for `progress/notify` [#23](https://github.com/dfilatov/jspromise/issues/23) 147 | 148 | 0.2.6 149 | ----- 150 | * `promise.always` doesn't pass the return value of `onResolved` [#19](https://github.com/dfilatov/jspromise/issues/19) 151 | -------------------------------------------------------------------------------- /CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | Contribution Guide 2 | ================== 3 | 4 | This document describes some points about contribution process for jscs package. 5 | 6 | The maintainer of the project is Filatov Dmitry. 7 | 8 | The project is being developed within community. Maintainer merges pull-requests, fixes critical bugs. 9 | 10 | Pull-requests 11 | ------------- 12 | 13 | If you fixed or added something useful to the project, you can send pull-request. It will be reviewed by maintainer and accepted, or commented for rework, or declined. 14 | 15 | Bugs 16 | ---- 17 | 18 | If you found an error, mistype or any other flawback in the project, please report about it using [issues](https://github.com/dfilatov/jspromise/issues). 19 | The more details you provide, the easier it can be reproduced and the faster can be fixed. 20 | Unfortunately, sometimes the bug can be only reproduced in your project or in your environment, so maintainers cannot reproduce it. In this case we believe you can fix the bug and send us the fix. 21 | 22 | Features 23 | -------- 24 | 25 | It you've got an idea about a new feature, it's most likely that you have do implement it on your own. 26 | If you cannot implement the feature, but it is very important, you can add a task in [issues](https://github.com/dfilatov/jspromise/issues), but expect it be declined by the maintainer. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012 Dmitry Filatov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | node test/utils/runner.js 3 | 4 | benchmark: 5 | node benchmarks/comparison.js 6 | 7 | min: 8 | ./node_modules/.bin/terser --compress --mangle -- lib/vow.js > vow.min.js 9 | 10 | .PHONY: test benchmark min 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Vow 3 | [![NPM Version](https://img.shields.io/npm/v/vow.svg?style=flat-square)](https://www.npmjs.com/package/vow) 4 | [![Build Status](https://img.shields.io/travis/dfilatov/vow/master.svg?style=flat-square)](https://travis-ci.org/dfilatov/vow/branches) 5 | [![NPM Downloads](https://img.shields.io/npm/dm/vow.svg?style=flat-square)](https://www.npmjs.org/package/vow) 6 | ========= 7 | 8 | Vow is a [Promises/A+](http://promisesaplus.com/) implementation. 9 | It also supports [ES6 Promises](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects) specification. 10 | 11 | Full API reference can be found at http://dfilatov.github.io/vow/. 12 | 13 | Getting Started 14 | --------------- 15 | ### In Node.js ### 16 | You can install using Node Package Manager (npm): 17 | 18 | npm install vow 19 | 20 | ### In Browsers ### 21 | ```html 22 | 23 | ``` 24 | It also supports RequireJS module format and [YM module](https://github.com/ymaps/modules) format. 25 | 26 | Vow has been tested in IE6+, Mozilla Firefox 3+, Chrome 5+, Safari 5+, Opera 10+. 27 | 28 | Usage 29 | ----- 30 | ### Creating a promise ### 31 | There are two possible ways to create a promise. 32 | #### 1. Using a deferred #### 33 | ```js 34 | function doSomethingAsync() { 35 | var deferred = vow.defer(); 36 | 37 | // now you can resolve, reject, notify corresponging promise within `deferred` 38 | // e.g. `defer.resolve('ok');` 39 | 40 | return deferred.promise(); // and return corresponding promise to subscribe to reactions 41 | } 42 | 43 | doSomethingAsync().then( 44 | function() {}, // onFulfilled reaction 45 | function() {}, // onRejected reaction 46 | function() {} // onNotified reaction 47 | ); 48 | ``` 49 | The difference between `deferred` and `promise` is that `deferred` contains methods to resolve, reject and notify corresponding promise, but the `promise` by itself allows only to subscribe on these actions. 50 | 51 | #### 2. ES6-compatible way #### 52 | ```js 53 | function doSomethingAsync() { 54 | return new vow.Promise(function(resolve, reject, notify) { 55 | // now you can resolve, reject, notify the promise 56 | }); 57 | } 58 | 59 | doSomethingAsync().then( 60 | function() {}, // onFulfilled reaction 61 | function() {}, // onRejected reaction 62 | function() {} // onNotified reaction 63 | ); 64 | ``` 65 | 66 | Extensions and related projects 67 | ------------------------------- 68 | * [vow-fs](https://github.com/dfilatov/vow-fs) — vow-based file I/O for Node.js 69 | * [vow-node](https://github.com/dfilatov/vow-node) — extension for vow to work with nodejs-style callbacks 70 | * [vow-queue](https://github.com/dfilatov/vow-queue) — vow-based task queue with weights and priorities 71 | * [vow-asker](https://github.com/nodules/vow-asker) — wraps [asker](https://github.com/nodules/asker) API in the vow promises implementation 72 | 73 | **NOTE**. Documentation for old versions of the library can be found at https://github.com/dfilatov/vow/blob/0.3.x/README.md. 74 | -------------------------------------------------------------------------------- /benchmarks/comparison.js: -------------------------------------------------------------------------------- 1 | var cliff = require('cliff'), 2 | benchmark = require('benchmark'), 3 | fs = require('fs'), 4 | data = require('./data'), 5 | Vow = require('..'), 6 | Q = require('q'), 7 | When = require('when'), 8 | Bluebird = require('bluebird'), 9 | Pinkie = require('pinkie'), 10 | corePromise = require('core-js/library/es6/promise'), 11 | Davy = require('davy'), 12 | tests = { 13 | 'Q' : function(deferred) { 14 | var toResolve = [], 15 | topPromises = []; 16 | 17 | Object.keys(data).forEach(function(key) { 18 | var defer = Q.defer(); 19 | Q.all(data[key].map(function(val) { 20 | var defer = Q.defer(); 21 | toResolve.push({ defer : defer, val : val }); 22 | return defer.promise; 23 | })) 24 | .then(function(val) { 25 | defer.resolve(val); 26 | }); 27 | topPromises.push(defer.promise); 28 | }); 29 | 30 | Q.all(topPromises).then(function() { 31 | deferred.resolve(); 32 | }); 33 | 34 | toResolve.forEach(function(obj) { 35 | obj.defer.resolve(obj.val); 36 | }); 37 | }, 38 | 39 | 'When' : function(deferred) { 40 | var toResolve = [], 41 | topPromises = []; 42 | 43 | Object.keys(data).forEach(function(key) { 44 | var defer = When.defer(); 45 | When.all(data[key].map(function(val) { 46 | var defer = When.defer(); 47 | toResolve.push({ defer : defer, val : val }); 48 | return defer.promise; 49 | })) 50 | .then(function(val) { 51 | defer.resolve(val); 52 | }); 53 | topPromises.push(defer.promise); 54 | }); 55 | 56 | When.all(topPromises).then(function() { 57 | deferred.resolve(); 58 | }); 59 | 60 | toResolve.forEach(function(obj) { 61 | obj.defer.resolve(obj.val); 62 | }); 63 | }, 64 | 65 | 'Bluebird' : function(deferred) { 66 | var toResolve = [], 67 | topPromises = []; 68 | 69 | Object.keys(data).forEach(function(key) { 70 | var resolve, 71 | promise = new Bluebird(function(_resolve) { 72 | resolve = _resolve; 73 | }); 74 | Bluebird.all(data[key].map(function(val) { 75 | return new Bluebird(function(_resolve) { 76 | toResolve.push({ resolve : _resolve, val : val }); 77 | }); 78 | })) 79 | .then(function(val) { 80 | resolve(val); 81 | }); 82 | topPromises.push(promise); 83 | }); 84 | 85 | Bluebird.all(topPromises).then(function() { 86 | deferred.resolve(); 87 | }); 88 | 89 | toResolve.forEach(function(obj) { 90 | obj.resolve(obj.val); 91 | }); 92 | }, 93 | 94 | 'Pinkie' : function(deferred) { 95 | var toResolve = [], 96 | topPromises = []; 97 | 98 | Object.keys(data).forEach(function(key) { 99 | var resolve, 100 | promise = new Pinkie(function(_resolve) { 101 | resolve = _resolve; 102 | }); 103 | Pinkie.all(data[key].map(function(val) { 104 | return new Pinkie(function(_resolve) { 105 | toResolve.push({ resolve : _resolve, val : val }); 106 | }); 107 | })) 108 | .then(function(val) { 109 | resolve(val); 110 | }); 111 | topPromises.push(promise); 112 | }); 113 | 114 | Pinkie.all(topPromises).then(function() { 115 | deferred.resolve(); 116 | }); 117 | 118 | toResolve.forEach(function(obj) { 119 | obj.resolve(obj.val); 120 | }); 121 | }, 122 | 123 | 'Davy': function (deferred) { 124 | var toResolve = [], 125 | topPromises = Object.keys(data).map(function(key) { 126 | var defer = Davy.defer(); 127 | Davy.all(data[key].map(function(val) { 128 | var defer = Davy.defer(); 129 | toResolve.push({ defer : defer, val : val }); 130 | return defer.promise; 131 | })) 132 | .then(function(val) { 133 | defer.fulfill(val); 134 | }); 135 | return defer.promise; 136 | }); 137 | 138 | Davy.all(topPromises).then(function() { 139 | deferred.resolve(); 140 | }); 141 | 142 | toResolve.forEach(function(obj) { 143 | obj.defer.fulfill(obj.val); 144 | }); 145 | }, 146 | 147 | 'ES2015 Promise' : function(deferred) { 148 | var toResolve = [], 149 | topPromises = []; 150 | 151 | Object.keys(data).forEach(function(key) { 152 | var resolve, 153 | promise = new Promise(function(_resolve) { 154 | resolve = _resolve; 155 | }); 156 | Promise.all(data[key].map(function(val) { 157 | return new Promise(function(_resolve) { 158 | toResolve.push({ resolve : _resolve, val : val }); 159 | }); 160 | })) 161 | .then(function(val) { 162 | resolve(val); 163 | }); 164 | topPromises.push(promise); 165 | }); 166 | 167 | Promise.all(topPromises).then(function() { 168 | deferred.resolve(); 169 | }); 170 | 171 | toResolve.forEach(function(obj) { 172 | obj.resolve(obj.val); 173 | }); 174 | }, 175 | 176 | 'core-js Promise' : function(deferred) { 177 | var toResolve = [], 178 | topPromises = []; 179 | 180 | Object.keys(data).forEach(function(key) { 181 | var resolve, 182 | promise = new corePromise(function(_resolve) { 183 | resolve = _resolve; 184 | }); 185 | corePromise.all(data[key].map(function(val) { 186 | return new corePromise(function(_resolve) { 187 | toResolve.push({ resolve : _resolve, val : val }); 188 | }); 189 | })) 190 | .then(function(val) { 191 | resolve(val); 192 | }); 193 | topPromises.push(promise); 194 | }); 195 | 196 | corePromise.all(topPromises).then(function() { 197 | deferred.resolve(); 198 | }); 199 | 200 | toResolve.forEach(function(obj) { 201 | obj.resolve(obj.val); 202 | }); 203 | }, 204 | 205 | 'Vow' : function(deferred) { 206 | var toResolve = [], 207 | topPromises = Object.keys(data).map(function(key) { 208 | var defer = Vow.defer(); 209 | Vow.all(data[key].map(function(val) { 210 | var defer = Vow.defer(); 211 | toResolve.push({ defer : defer, val : val }); 212 | return defer.promise(); 213 | })) 214 | .then(function(val) { 215 | defer.resolve(val); 216 | }); 217 | return defer.promise(); 218 | }); 219 | 220 | Vow.all(topPromises).then(function() { 221 | deferred.resolve(); 222 | }); 223 | 224 | toResolve.forEach(function(obj) { 225 | obj.defer.resolve(obj.val); 226 | }); 227 | } 228 | }, 229 | results = [], 230 | onTestCompleted = function(name) { 231 | results.push({ 232 | '' : name, 233 | 'mean time' : (this.stats.mean * 1000).toFixed(3) + 'ms', 234 | 'ops/sec' : (1 / this.stats.mean).toFixed(0) 235 | }); 236 | }; 237 | 238 | var suite = new benchmark.Suite( 239 | 'comparison', 240 | { 241 | onStart : function() { 242 | console.log('Starts\n'); 243 | }, 244 | 245 | onComplete : function() { 246 | console.log(cliff.stringifyObjectRows( 247 | results, 248 | ['', 'mean time', 'ops/sec'], 249 | ['red', 'green', 'blue']) + '\n'); 250 | results = []; 251 | } 252 | }); 253 | 254 | Object.keys(tests).forEach(function(name) { 255 | var i = 0; 256 | suite.add( 257 | name, 258 | function(deferred) { 259 | tests[name](deferred); 260 | }, 261 | { 262 | defer : true, 263 | onStart : function() { 264 | //console.log(name + ' \n'); 265 | }, 266 | onCycle : function() { 267 | console.log('\033[1A' + new Array(i++).join('.')); 268 | }, 269 | onComplete : function() { 270 | console.log(''); 271 | onTestCompleted.call(this, name); 272 | } 273 | }); 274 | }); 275 | 276 | suite.run(); 277 | -------------------------------------------------------------------------------- /benchmarks/data.js: -------------------------------------------------------------------------------- 1 | var res = {}; 2 | 3 | ['A', 'B', 'C'].forEach(function(key) { 4 | res[key] = []; 5 | for(var i = 0; i < 100; i++) { 6 | res[key].push(key + i); 7 | } 8 | }); 9 | 10 | module.exports = res; 11 | -------------------------------------------------------------------------------- /benchmarks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promises-library-speed-comparison", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "benchmark": "", 6 | "bluebird": "3.4.6", 7 | "cliff": "", 8 | "core-js": "2.4.1", 9 | "pinkie": "2.0.4", 10 | "q": "1.4.1", 11 | "when": "3.7.7", 12 | "davy": "1.3.0" 13 | }, 14 | "private": true 15 | } 16 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "vow", 3 | "version" : "0.4.20", 4 | "main" : "lib/vow.js", 5 | "ignore" : ["**/.*", "node_modules", "benchmarks", "test", "utils", "Makefile", "package.json"] 6 | } 7 | -------------------------------------------------------------------------------- /lib/vow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module vow 3 | * @author Filatov Dmitry 4 | * @version 0.4.20 5 | * @license 6 | * Dual licensed under the MIT and GPL licenses: 7 | * * http://www.opensource.org/licenses/mit-license.php 8 | * * http://www.gnu.org/licenses/gpl.html 9 | */ 10 | 11 | (function(global) { 12 | 13 | var undef, 14 | nextTick = (function() { 15 | var fns = [], 16 | enqueueFn = function(fn) { 17 | fns.push(fn); 18 | return fns.length === 1; 19 | }, 20 | callFns = function() { 21 | var fnsToCall = fns, i = 0, len = fns.length; 22 | fns = []; 23 | while(i < len) { 24 | fnsToCall[i++](); 25 | } 26 | }; 27 | 28 | var MutationObserver = global.MutationObserver || global.WebKitMutationObserver; // modern browsers 29 | if(MutationObserver) { 30 | var num = 1, 31 | node = document.createTextNode(''); 32 | 33 | new MutationObserver(callFns).observe(node, { characterData : true }); 34 | 35 | return function(fn) { 36 | enqueueFn(fn) && (node.data = (num *= -1)); 37 | }; 38 | } 39 | 40 | if(typeof process === 'object' && process.nextTick) { 41 | return function(fn) { 42 | enqueueFn(fn) && process.nextTick(callFns); 43 | }; 44 | } 45 | 46 | if(typeof setImmediate === 'function') { 47 | return function(fn) { 48 | enqueueFn(fn) && setImmediate(callFns); 49 | }; 50 | } 51 | 52 | if(global.postMessage) { 53 | var isPostMessageAsync = true; 54 | if(global.attachEvent) { 55 | var checkAsync = function() { 56 | isPostMessageAsync = false; 57 | }; 58 | global.attachEvent('onmessage', checkAsync); 59 | global.postMessage('__checkAsync', '*'); 60 | global.detachEvent('onmessage', checkAsync); 61 | } 62 | 63 | if(isPostMessageAsync) { 64 | var msg = '__promise' + Math.random() + '_' +new Date, 65 | onMessage = function(e) { 66 | if(e.data === msg) { 67 | e.stopPropagation && e.stopPropagation(); 68 | callFns(); 69 | } 70 | }; 71 | 72 | global.addEventListener? 73 | global.addEventListener('message', onMessage, true) : 74 | global.attachEvent('onmessage', onMessage); 75 | 76 | return function(fn) { 77 | enqueueFn(fn) && global.postMessage(msg, '*'); 78 | }; 79 | } 80 | } 81 | 82 | var doc = global.document; 83 | if('onreadystatechange' in doc.createElement('script')) { // ie6-ie8 84 | var createScript = function() { 85 | var script = doc.createElement('script'); 86 | script.onreadystatechange = function() { 87 | script.parentNode.removeChild(script); 88 | script = script.onreadystatechange = null; 89 | callFns(); 90 | }; 91 | (doc.documentElement || doc.body).appendChild(script); 92 | }; 93 | 94 | return function(fn) { 95 | enqueueFn(fn) && createScript(); 96 | }; 97 | } 98 | 99 | return function(fn) { // old browsers 100 | enqueueFn(fn) && setTimeout(callFns, 0); 101 | }; 102 | })(), 103 | throwException = function(e) { 104 | nextTick(function() { 105 | throw e; 106 | }); 107 | }, 108 | isFunction = function(obj) { 109 | return typeof obj === 'function'; 110 | }, 111 | isObject = function(obj) { 112 | return obj !== null && typeof obj === 'object'; 113 | }, 114 | toStr = Object.prototype.toString, 115 | isArray = Array.isArray || function(obj) { 116 | return toStr.call(obj) === '[object Array]'; 117 | }, 118 | getArrayKeys = function(arr) { 119 | var res = [], 120 | i = 0, len = arr.length; 121 | while(i < len) { 122 | res.push(i++); 123 | } 124 | return res; 125 | }, 126 | getObjectKeys = Object.keys || function(obj) { 127 | var res = []; 128 | for(var i in obj) { 129 | obj.hasOwnProperty(i) && res.push(i); 130 | } 131 | return res; 132 | }, 133 | defineCustomErrorType = function(name) { 134 | var res = function(message) { 135 | this.name = name; 136 | this.message = message; 137 | }; 138 | 139 | res.prototype = new Error(); 140 | 141 | return res; 142 | }, 143 | wrapOnFulfilled = function(onFulfilled, idx) { 144 | return function(val) { 145 | onFulfilled.call(this, val, idx); 146 | }; 147 | }, 148 | emitUnhandledRejection = global.PromiseRejectionEvent? 149 | function(reason, promise) { 150 | new global.PromiseRejectionEvent( 151 | 'unhandledrejection', 152 | { 153 | promise : promise, 154 | reason : reason 155 | }); 156 | } : 157 | typeof process === 'object' && process.emit? 158 | function(reason, promise) { 159 | process.emit('unhandledRejection', reason, promise); 160 | } : 161 | function() {}; 162 | 163 | /** 164 | * @class Deferred 165 | * @exports vow:Deferred 166 | * @description 167 | * The `Deferred` class is used to encapsulate newly-created promise object along with functions that resolve, reject or notify it. 168 | */ 169 | 170 | /** 171 | * @constructor 172 | * @description 173 | * You can use `vow.defer()` instead of using this constructor. 174 | * 175 | * `new vow.Deferred()` gives the same result as `vow.defer()`. 176 | */ 177 | var Deferred = function() { 178 | this._promise = new Promise(); 179 | }; 180 | 181 | Deferred.prototype = /** @lends Deferred.prototype */{ 182 | /** 183 | * Returns the corresponding promise. 184 | * 185 | * @returns {vow:Promise} 186 | */ 187 | promise : function() { 188 | return this._promise; 189 | }, 190 | 191 | /** 192 | * Resolves the corresponding promise with the given `value`. 193 | * 194 | * @param {*} value 195 | * 196 | * @example 197 | * ```js 198 | * var defer = vow.defer(), 199 | * promise = defer.promise(); 200 | * 201 | * promise.then(function(value) { 202 | * // value is "'success'" here 203 | * }); 204 | * 205 | * defer.resolve('success'); 206 | * ``` 207 | */ 208 | resolve : function(value) { 209 | this._promise.isResolved() || this._promise._resolve(value); 210 | }, 211 | 212 | /** 213 | * Rejects the corresponding promise with the given `reason`. 214 | * 215 | * @param {*} reason 216 | * 217 | * @example 218 | * ```js 219 | * var defer = vow.defer(), 220 | * promise = defer.promise(); 221 | * 222 | * promise.fail(function(reason) { 223 | * // reason is "'something is wrong'" here 224 | * }); 225 | * 226 | * defer.reject('something is wrong'); 227 | * ``` 228 | */ 229 | reject : function(reason) { 230 | if(this._promise.isResolved()) { 231 | return; 232 | } 233 | 234 | if(vow.isPromise(reason)) { 235 | reason = reason.then(function(val) { 236 | var defer = vow.defer(); 237 | defer.reject(val); 238 | return defer.promise(); 239 | }); 240 | this._promise._resolve(reason); 241 | } 242 | else { 243 | this._promise._reject(reason); 244 | } 245 | }, 246 | 247 | /** 248 | * Notifies the corresponding promise with the given `value`. 249 | * 250 | * @param {*} value 251 | * 252 | * @example 253 | * ```js 254 | * var defer = vow.defer(), 255 | * promise = defer.promise(); 256 | * 257 | * promise.progress(function(value) { 258 | * // value is "'20%'", "'40%'" here 259 | * }); 260 | * 261 | * defer.notify('20%'); 262 | * defer.notify('40%'); 263 | * ``` 264 | */ 265 | notify : function(value) { 266 | this._promise.isResolved() || this._promise._notify(value); 267 | } 268 | }; 269 | 270 | var PROMISE_STATUS = { 271 | PENDING : 0, 272 | RESOLVED : 1, 273 | FULFILLED : 2, 274 | REJECTED : 3 275 | }; 276 | 277 | /** 278 | * @class Promise 279 | * @exports vow:Promise 280 | * @description 281 | * The `Promise` class is used when you want to give to the caller something to subscribe to, 282 | * but not the ability to resolve or reject the deferred. 283 | */ 284 | 285 | /** 286 | * @constructor 287 | * @param {Function} resolver See https://github.com/domenic/promises-unwrapping/blob/master/README.md#the-promise-constructor for details. 288 | * @description 289 | * You should use this constructor directly only if you are going to use `vow` as DOM Promises implementation. 290 | * In other case you should use `vow.defer()` and `defer.promise()` methods. 291 | * @example 292 | * ```js 293 | * function fetchJSON(url) { 294 | * return new vow.Promise(function(resolve, reject, notify) { 295 | * var xhr = new XMLHttpRequest(); 296 | * xhr.open('GET', url); 297 | * xhr.responseType = 'json'; 298 | * xhr.send(); 299 | * xhr.onload = function() { 300 | * if(xhr.response) { 301 | * resolve(xhr.response); 302 | * } 303 | * else { 304 | * reject(new TypeError()); 305 | * } 306 | * }; 307 | * }); 308 | * } 309 | * ``` 310 | */ 311 | var Promise = function(resolver) { 312 | this._value = undef; 313 | this._status = PROMISE_STATUS.PENDING; 314 | this._shouldEmitUnhandledRejection = true; 315 | 316 | this._fulfilledCallbacks = []; 317 | this._rejectedCallbacks = []; 318 | this._progressCallbacks = []; 319 | 320 | if(resolver) { // NOTE: see https://github.com/domenic/promises-unwrapping/blob/master/README.md 321 | var _this = this, 322 | resolverFnLen = resolver.length; 323 | 324 | try { 325 | resolver( 326 | function(val) { 327 | _this.isResolved() || _this._resolve(val); 328 | }, 329 | resolverFnLen > 1? 330 | function(reason) { 331 | _this.isResolved() || _this._reject(reason); 332 | } : 333 | undef, 334 | resolverFnLen > 2? 335 | function(val) { 336 | _this.isResolved() || _this._notify(val); 337 | } : 338 | undef); 339 | } 340 | catch(e) { 341 | this._reject(e); 342 | } 343 | } 344 | }; 345 | 346 | Promise.prototype = /** @lends Promise.prototype */ { 347 | /** 348 | * Returns the value of the fulfilled promise or the reason in case of rejection. 349 | * 350 | * @returns {*} 351 | */ 352 | valueOf : function() { 353 | return this._value; 354 | }, 355 | 356 | /** 357 | * Returns `true` if the promise is resolved. 358 | * 359 | * @returns {Boolean} 360 | */ 361 | isResolved : function() { 362 | return this._status !== PROMISE_STATUS.PENDING; 363 | }, 364 | 365 | /** 366 | * Returns `true` if the promise is fulfilled. 367 | * 368 | * @returns {Boolean} 369 | */ 370 | isFulfilled : function() { 371 | return this._status === PROMISE_STATUS.FULFILLED; 372 | }, 373 | 374 | /** 375 | * Returns `true` if the promise is rejected. 376 | * 377 | * @returns {Boolean} 378 | */ 379 | isRejected : function() { 380 | return this._status === PROMISE_STATUS.REJECTED; 381 | }, 382 | 383 | /** 384 | * Adds reactions to the promise. 385 | * 386 | * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled 387 | * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected 388 | * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified 389 | * @param {Object} [ctx] Context of the callbacks execution 390 | * @returns {vow:Promise} A new promise, see https://github.com/promises-aplus/promises-spec for details 391 | */ 392 | then : function(onFulfilled, onRejected, onProgress, ctx) { 393 | this._shouldEmitUnhandledRejection = false; 394 | var defer = new Deferred(); 395 | this._addCallbacks(defer, onFulfilled, onRejected, onProgress, ctx); 396 | return defer.promise(); 397 | }, 398 | 399 | /** 400 | * Adds only a rejection reaction. This method is a shorthand for `promise.then(undefined, onRejected)`. 401 | * 402 | * @param {Function} onRejected Callback that will be called with a provided 'reason' as argument after the promise has been rejected 403 | * @param {Object} [ctx] Context of the callback execution 404 | * @returns {vow:Promise} 405 | */ 406 | 'catch' : function(onRejected, ctx) { 407 | return this.then(undef, onRejected, ctx); 408 | }, 409 | 410 | /** 411 | * Adds only a rejection reaction. This method is a shorthand for `promise.then(null, onRejected)`. It's also an alias for `catch`. 412 | * 413 | * @param {Function} onRejected Callback to be called with the value after promise has been rejected 414 | * @param {Object} [ctx] Context of the callback execution 415 | * @returns {vow:Promise} 416 | */ 417 | fail : function(onRejected, ctx) { 418 | return this.then(undef, onRejected, ctx); 419 | }, 420 | 421 | /** 422 | * Adds a resolving reaction (for both fulfillment and rejection). 423 | * 424 | * @param {Function} onResolved Callback that will be invoked with the promise as an argument, after the promise has been resolved. 425 | * @param {Object} [ctx] Context of the callback execution 426 | * @returns {vow:Promise} 427 | */ 428 | always : function(onResolved, ctx) { 429 | var _this = this, 430 | cb = function() { 431 | return onResolved.call(this, _this); 432 | }; 433 | 434 | return this.then(cb, cb, ctx); 435 | }, 436 | 437 | /** 438 | * Adds a resolving reaction (for both fulfillment and rejection). The returned promise will be fullfiled with the same value or rejected with the same reason as the original promise. 439 | * 440 | * @param {Function} onFinalized Callback that will be invoked after the promise has been resolved. 441 | * @param {Object} [ctx] Context of the callback execution 442 | * @returns {vow:Promise} 443 | */ 444 | 'finally' : function(onFinalized, ctx) { 445 | var _this = this, 446 | cb = function() { 447 | return onFinalized.call(this); 448 | }; 449 | return this 450 | .then(cb, cb, ctx) 451 | .then(function() { 452 | return _this; 453 | }); 454 | }, 455 | 456 | /** 457 | * Adds a progress reaction. 458 | * 459 | * @param {Function} onProgress Callback that will be called with a provided value when the promise has been notified 460 | * @param {Object} [ctx] Context of the callback execution 461 | * @returns {vow:Promise} 462 | */ 463 | progress : function(onProgress, ctx) { 464 | return this.then(undef, undef, onProgress, ctx); 465 | }, 466 | 467 | /** 468 | * Like `promise.then`, but "spreads" the array into a variadic value handler. 469 | * It is useful with the `vow.all` and the `vow.allResolved` methods. 470 | * 471 | * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled 472 | * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected 473 | * @param {Object} [ctx] Context of the callbacks execution 474 | * @returns {vow:Promise} 475 | * 476 | * @example 477 | * ```js 478 | * var defer1 = vow.defer(), 479 | * defer2 = vow.defer(); 480 | * 481 | * vow.all([defer1.promise(), defer2.promise()]).spread(function(arg1, arg2) { 482 | * // arg1 is "1", arg2 is "'two'" here 483 | * }); 484 | * 485 | * defer1.resolve(1); 486 | * defer2.resolve('two'); 487 | * ``` 488 | */ 489 | spread : function(onFulfilled, onRejected, ctx) { 490 | return this.then( 491 | function(val) { 492 | return onFulfilled.apply(this, val); 493 | }, 494 | onRejected, 495 | ctx); 496 | }, 497 | 498 | /** 499 | * Like `then`, but terminates a chain of promises. 500 | * If the promise has been rejected, this method throws it's "reason" as an exception in a future turn of the event loop. 501 | * 502 | * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled 503 | * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected 504 | * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified 505 | * @param {Object} [ctx] Context of the callbacks execution 506 | * 507 | * @example 508 | * ```js 509 | * var defer = vow.defer(); 510 | * defer.reject(Error('Internal error')); 511 | * defer.promise().done(); // exception to be thrown 512 | * ``` 513 | */ 514 | done : function(onFulfilled, onRejected, onProgress, ctx) { 515 | this 516 | .then(onFulfilled, onRejected, onProgress, ctx) 517 | .fail(throwException); 518 | }, 519 | 520 | /** 521 | * Returns a new promise that will be fulfilled in `delay` milliseconds if the promise is fulfilled, 522 | * or immediately rejected if the promise is rejected. 523 | * 524 | * @param {Number} delay 525 | * @returns {vow:Promise} 526 | */ 527 | delay : function(delay) { 528 | var timer, 529 | promise = this.then(function(val) { 530 | var defer = new Deferred(); 531 | timer = setTimeout( 532 | function() { 533 | defer.resolve(val); 534 | }, 535 | delay); 536 | 537 | return defer.promise(); 538 | }); 539 | 540 | promise.always(function() { 541 | clearTimeout(timer); 542 | }); 543 | 544 | return promise; 545 | }, 546 | 547 | /** 548 | * Returns a new promise that will be rejected in `timeout` milliseconds 549 | * if the promise is not resolved beforehand. 550 | * 551 | * @param {Number} timeout 552 | * @returns {vow:Promise} 553 | * 554 | * @example 555 | * ```js 556 | * var defer = vow.defer(), 557 | * promiseWithTimeout1 = defer.promise().timeout(50), 558 | * promiseWithTimeout2 = defer.promise().timeout(200); 559 | * 560 | * setTimeout( 561 | * function() { 562 | * defer.resolve('ok'); 563 | * }, 564 | * 100); 565 | * 566 | * promiseWithTimeout1.fail(function(reason) { 567 | * // promiseWithTimeout to be rejected in 50ms 568 | * }); 569 | * 570 | * promiseWithTimeout2.then(function(value) { 571 | * // promiseWithTimeout to be fulfilled with "'ok'" value 572 | * }); 573 | * ``` 574 | */ 575 | timeout : function(timeout) { 576 | var defer = new Deferred(), 577 | timer = setTimeout( 578 | function() { 579 | defer.reject(new vow.TimedOutError('timed out')); 580 | }, 581 | timeout); 582 | 583 | this.then( 584 | function(val) { 585 | defer.resolve(val); 586 | }, 587 | function(reason) { 588 | defer.reject(reason); 589 | }); 590 | 591 | defer.promise().always(function() { 592 | clearTimeout(timer); 593 | }); 594 | 595 | return defer.promise(); 596 | }, 597 | 598 | _vow : true, 599 | 600 | _resolve : function(val) { 601 | if(this._status > PROMISE_STATUS.RESOLVED) { 602 | return; 603 | } 604 | 605 | if(val === this) { 606 | this._reject(TypeError('Can\'t resolve promise with itself')); 607 | return; 608 | } 609 | 610 | this._status = PROMISE_STATUS.RESOLVED; 611 | 612 | if(val && !!val._vow) { // shortpath for vow.Promise 613 | if(val.isFulfilled()) { 614 | this._fulfill(val.valueOf()); 615 | } 616 | else if(val.isRejected()) { 617 | val._shouldEmitUnhandledRejection = false; 618 | this._reject(val.valueOf()); 619 | } 620 | else { 621 | val.then( 622 | this._fulfill, 623 | this._reject, 624 | this._notify, 625 | this); 626 | } 627 | 628 | return; 629 | } 630 | 631 | if(isObject(val) || isFunction(val)) { 632 | var then; 633 | try { 634 | then = val.then; 635 | } 636 | catch(e) { 637 | this._reject(e); 638 | return; 639 | } 640 | 641 | if(isFunction(then)) { 642 | var _this = this, 643 | isResolved = false; 644 | 645 | try { 646 | then.call( 647 | val, 648 | function(val) { 649 | if(isResolved) { 650 | return; 651 | } 652 | 653 | isResolved = true; 654 | _this._resolve(val); 655 | }, 656 | function(err) { 657 | if(isResolved) { 658 | return; 659 | } 660 | 661 | isResolved = true; 662 | _this._reject(err); 663 | }, 664 | function(val) { 665 | _this._notify(val); 666 | }); 667 | } 668 | catch(e) { 669 | isResolved || this._reject(e); 670 | } 671 | 672 | return; 673 | } 674 | } 675 | 676 | this._fulfill(val); 677 | }, 678 | 679 | _fulfill : function(val) { 680 | if(this._status > PROMISE_STATUS.RESOLVED) { 681 | return; 682 | } 683 | 684 | this._status = PROMISE_STATUS.FULFILLED; 685 | this._value = val; 686 | 687 | this._callCallbacks(this._fulfilledCallbacks, val); 688 | this._fulfilledCallbacks = this._rejectedCallbacks = this._progressCallbacks = undef; 689 | }, 690 | 691 | _reject : function(reason) { 692 | if(this._status > PROMISE_STATUS.RESOLVED) { 693 | return; 694 | } 695 | 696 | this._status = PROMISE_STATUS.REJECTED; 697 | this._value = reason; 698 | 699 | this._callCallbacks(this._rejectedCallbacks, reason); 700 | 701 | if(!this._rejectedCallbacks.length) { 702 | var _this = this; 703 | nextTick(function() { 704 | if(_this._shouldEmitUnhandledRejection) { 705 | emitUnhandledRejection(reason, _this); 706 | } 707 | }); 708 | } 709 | 710 | this._fulfilledCallbacks = this._rejectedCallbacks = this._progressCallbacks = undef; 711 | }, 712 | 713 | _notify : function(val) { 714 | this._callCallbacks(this._progressCallbacks, val); 715 | }, 716 | 717 | _addCallbacks : function(defer, onFulfilled, onRejected, onProgress, ctx) { 718 | if(onRejected && !isFunction(onRejected)) { 719 | ctx = onRejected; 720 | onRejected = undef; 721 | } 722 | else if(onProgress && !isFunction(onProgress)) { 723 | ctx = onProgress; 724 | onProgress = undef; 725 | } 726 | 727 | if(onRejected) { 728 | this._shouldEmitUnhandledRejection = false; 729 | } 730 | 731 | var cb; 732 | 733 | if(!this.isRejected()) { 734 | cb = { defer : defer, fn : isFunction(onFulfilled)? onFulfilled : undef, ctx : ctx }; 735 | this.isFulfilled()? 736 | this._callCallbacks([cb], this._value) : 737 | this._fulfilledCallbacks.push(cb); 738 | } 739 | 740 | if(!this.isFulfilled()) { 741 | cb = { defer : defer, fn : onRejected, ctx : ctx }; 742 | this.isRejected()? 743 | this._callCallbacks([cb], this._value) : 744 | this._rejectedCallbacks.push(cb); 745 | } 746 | 747 | if(this._status <= PROMISE_STATUS.RESOLVED) { 748 | this._progressCallbacks.push({ defer : defer, fn : onProgress, ctx : ctx }); 749 | } 750 | }, 751 | 752 | _callCallbacks : function(callbacks, arg) { 753 | var len = callbacks.length; 754 | if(!len) { 755 | return; 756 | } 757 | 758 | var isResolved = this.isResolved(), 759 | isFulfilled = this.isFulfilled(), 760 | isRejected = this.isRejected(); 761 | 762 | nextTick(function() { 763 | var i = 0, cb, defer, fn; 764 | while(i < len) { 765 | cb = callbacks[i++]; 766 | defer = cb.defer; 767 | fn = cb.fn; 768 | 769 | if(fn) { 770 | var ctx = cb.ctx, 771 | res; 772 | try { 773 | res = ctx? fn.call(ctx, arg) : fn(arg); 774 | } 775 | catch(e) { 776 | defer.reject(e); 777 | continue; 778 | } 779 | 780 | isFulfilled || isRejected? 781 | defer.resolve(res) : 782 | defer.notify(res); 783 | } 784 | else if(isFulfilled) { 785 | defer.resolve(arg); 786 | } 787 | else if(isRejected) { 788 | defer.reject(arg); 789 | } 790 | else { 791 | defer.notify(arg); 792 | } 793 | } 794 | }); 795 | } 796 | }; 797 | 798 | /** @lends Promise */ 799 | var staticMethods = { 800 | /** 801 | * Coerces the given `value` to a promise, or returns the `value` if it's already a promise. 802 | * 803 | * @param {*} value 804 | * @returns {vow:Promise} 805 | */ 806 | cast : function(value) { 807 | return vow.cast(value); 808 | }, 809 | 810 | /** 811 | * Returns a promise, that will be fulfilled only after all the items in `iterable` are fulfilled. 812 | * If any of the `iterable` items gets rejected, then the returned promise will be rejected. 813 | * 814 | * @param {Array|Object} iterable 815 | * @returns {vow:Promise} 816 | */ 817 | all : function(iterable) { 818 | return vow.all(iterable); 819 | }, 820 | 821 | /** 822 | * Returns a promise, that will be fulfilled only after all the items in `iterable` are fulfilled or rejected. 823 | * 824 | * @param {Array|Object} iterable 825 | * @returns {vow:Promise} 826 | */ 827 | allSettled : function(iterable) { 828 | return vow.allSettled(iterable); 829 | }, 830 | 831 | /** 832 | * Returns a promise, that will be fulfilled only when any of the items in `iterable` are fulfilled. 833 | * If any of the `iterable` items gets rejected, then the returned promise will be rejected. 834 | * 835 | * @param {Array} iterable 836 | * @returns {vow:Promise} 837 | */ 838 | race : function(iterable) { 839 | return vow.anyResolved(iterable); 840 | }, 841 | 842 | /** 843 | * Returns a promise that has already been resolved with the given `value`. 844 | * If `value` is a promise, the returned promise will have `value`'s state. 845 | * 846 | * @param {*} value 847 | * @returns {vow:Promise} 848 | */ 849 | resolve : function(value) { 850 | return vow.resolve(value); 851 | }, 852 | 853 | /** 854 | * Returns a promise that has already been rejected with the given `reason`. 855 | * 856 | * @param {*} reason 857 | * @returns {vow:Promise} 858 | */ 859 | reject : function(reason) { 860 | return vow.reject(reason); 861 | } 862 | }; 863 | 864 | for(var prop in staticMethods) { 865 | staticMethods.hasOwnProperty(prop) && 866 | (Promise[prop] = staticMethods[prop]); 867 | } 868 | 869 | var vow = /** @exports vow */ { 870 | Deferred : Deferred, 871 | 872 | Promise : Promise, 873 | 874 | /** 875 | * Creates a new deferred. This method is a factory method for `vow:Deferred` class. 876 | * It's equivalent to `new vow.Deferred()`. 877 | * 878 | * @returns {vow:Deferred} 879 | */ 880 | defer : function() { 881 | return new Deferred(); 882 | }, 883 | 884 | /** 885 | * Static equivalent to `promise.then`. 886 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 887 | * 888 | * @param {*} value 889 | * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled 890 | * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected 891 | * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified 892 | * @param {Object} [ctx] Context of the callbacks execution 893 | * @returns {vow:Promise} 894 | */ 895 | when : function(value, onFulfilled, onRejected, onProgress, ctx) { 896 | return vow.cast(value).then(onFulfilled, onRejected, onProgress, ctx); 897 | }, 898 | 899 | /** 900 | * Static equivalent to `promise.fail`. 901 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 902 | * 903 | * @param {*} value 904 | * @param {Function} onRejected Callback that will be invoked with a provided reason after the promise has been rejected 905 | * @param {Object} [ctx] Context of the callback execution 906 | * @returns {vow:Promise} 907 | */ 908 | fail : function(value, onRejected, ctx) { 909 | return vow.when(value, undef, onRejected, ctx); 910 | }, 911 | 912 | /** 913 | * Static equivalent to `promise.always`. 914 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 915 | * 916 | * @param {*} value 917 | * @param {Function} onResolved Callback that will be invoked with the promise as an argument, after the promise has been resolved. 918 | * @param {Object} [ctx] Context of the callback execution 919 | * @returns {vow:Promise} 920 | */ 921 | always : function(value, onResolved, ctx) { 922 | return vow.when(value).always(onResolved, ctx); 923 | }, 924 | 925 | /** 926 | * Static equivalent to `promise.progress`. 927 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 928 | * 929 | * @param {*} value 930 | * @param {Function} onProgress Callback that will be invoked with a provided value after the promise has been notified 931 | * @param {Object} [ctx] Context of the callback execution 932 | * @returns {vow:Promise} 933 | */ 934 | progress : function(value, onProgress, ctx) { 935 | return vow.when(value).progress(onProgress, ctx); 936 | }, 937 | 938 | /** 939 | * Static equivalent to `promise.spread`. 940 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 941 | * 942 | * @param {*} value 943 | * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled 944 | * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected 945 | * @param {Object} [ctx] Context of the callbacks execution 946 | * @returns {vow:Promise} 947 | */ 948 | spread : function(value, onFulfilled, onRejected, ctx) { 949 | return vow.when(value).spread(onFulfilled, onRejected, ctx); 950 | }, 951 | 952 | /** 953 | * Static equivalent to `promise.done`. 954 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 955 | * 956 | * @param {*} value 957 | * @param {Function} [onFulfilled] Callback that will be invoked with a provided value after the promise has been fulfilled 958 | * @param {Function} [onRejected] Callback that will be invoked with a provided reason after the promise has been rejected 959 | * @param {Function} [onProgress] Callback that will be invoked with a provided value after the promise has been notified 960 | * @param {Object} [ctx] Context of the callbacks execution 961 | */ 962 | done : function(value, onFulfilled, onRejected, onProgress, ctx) { 963 | vow.when(value).done(onFulfilled, onRejected, onProgress, ctx); 964 | }, 965 | 966 | /** 967 | * Checks whether the given `value` is a promise-like object 968 | * 969 | * @param {*} value 970 | * @returns {Boolean} 971 | * 972 | * @example 973 | * ```js 974 | * vow.isPromise('something'); // returns false 975 | * vow.isPromise(vow.defer().promise()); // returns true 976 | * vow.isPromise({ then : function() { }); // returns true 977 | * ``` 978 | */ 979 | isPromise : function(value) { 980 | return isObject(value) && isFunction(value.then); 981 | }, 982 | 983 | /** 984 | * Coerces the given `value` to a promise, or returns the `value` if it's already a promise. 985 | * 986 | * @param {*} value 987 | * @returns {vow:Promise} 988 | */ 989 | cast : function(value) { 990 | return value && !!value._vow? 991 | value : 992 | vow.resolve(value); 993 | }, 994 | 995 | /** 996 | * Static equivalent to `promise.valueOf`. 997 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 998 | * 999 | * @param {*} value 1000 | * @returns {*} 1001 | */ 1002 | valueOf : function(value) { 1003 | return value && isFunction(value.valueOf)? value.valueOf() : value; 1004 | }, 1005 | 1006 | /** 1007 | * Static equivalent to `promise.isFulfilled`. 1008 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 1009 | * 1010 | * @param {*} value 1011 | * @returns {Boolean} 1012 | */ 1013 | isFulfilled : function(value) { 1014 | return value && isFunction(value.isFulfilled)? value.isFulfilled() : true; 1015 | }, 1016 | 1017 | /** 1018 | * Static equivalent to `promise.isRejected`. 1019 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 1020 | * 1021 | * @param {*} value 1022 | * @returns {Boolean} 1023 | */ 1024 | isRejected : function(value) { 1025 | return value && isFunction(value.isRejected)? value.isRejected() : false; 1026 | }, 1027 | 1028 | /** 1029 | * Static equivalent to `promise.isResolved`. 1030 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 1031 | * 1032 | * @param {*} value 1033 | * @returns {Boolean} 1034 | */ 1035 | isResolved : function(value) { 1036 | return value && isFunction(value.isResolved)? value.isResolved() : true; 1037 | }, 1038 | 1039 | /** 1040 | * Returns a promise that has already been resolved with the given `value`. 1041 | * If `value` is a promise, the returned promise will have `value`'s state. 1042 | * 1043 | * @param {*} value 1044 | * @returns {vow:Promise} 1045 | */ 1046 | resolve : function(value) { 1047 | var res = vow.defer(); 1048 | res.resolve(value); 1049 | return res.promise(); 1050 | }, 1051 | 1052 | /** 1053 | * Returns a promise that has already been fulfilled with the given `value`. 1054 | * If `value` is a promise, the returned promise will be fulfilled with the fulfill/rejection value of `value`. 1055 | * 1056 | * @param {*} value 1057 | * @returns {vow:Promise} 1058 | */ 1059 | fulfill : function(value) { 1060 | var defer = vow.defer(), 1061 | promise = defer.promise(); 1062 | 1063 | defer.resolve(value); 1064 | 1065 | return promise.isFulfilled()? 1066 | promise : 1067 | promise.then(null, function(reason) { 1068 | return reason; 1069 | }); 1070 | }, 1071 | 1072 | /** 1073 | * Returns a promise that has already been rejected with the given `reason`. 1074 | * If `reason` is a promise, the returned promise will be rejected with the fulfill/rejection value of `reason`. 1075 | * 1076 | * @param {*} reason 1077 | * @returns {vow:Promise} 1078 | */ 1079 | reject : function(reason) { 1080 | var defer = vow.defer(); 1081 | defer.reject(reason); 1082 | return defer.promise(); 1083 | }, 1084 | 1085 | /** 1086 | * Invokes the given function `fn` with arguments `args` 1087 | * 1088 | * @param {Function} fn 1089 | * @param {...*} [args] 1090 | * @returns {vow:Promise} 1091 | * 1092 | * @example 1093 | * ```js 1094 | * var promise1 = vow.invoke(function(value) { 1095 | * return value; 1096 | * }, 'ok'), 1097 | * promise2 = vow.invoke(function() { 1098 | * throw Error(); 1099 | * }); 1100 | * 1101 | * promise1.isFulfilled(); // true 1102 | * promise1.valueOf(); // 'ok' 1103 | * promise2.isRejected(); // true 1104 | * promise2.valueOf(); // instance of Error 1105 | * ``` 1106 | */ 1107 | invoke : function(fn, args) { 1108 | var len = Math.max(arguments.length - 1, 0), 1109 | callArgs; 1110 | if(len) { // optimization for V8 1111 | callArgs = Array(len); 1112 | var i = 0; 1113 | while(i < len) { 1114 | callArgs[i++] = arguments[i]; 1115 | } 1116 | } 1117 | 1118 | try { 1119 | return vow.resolve(callArgs? 1120 | fn.apply(global, callArgs) : 1121 | fn.call(global)); 1122 | } 1123 | catch(e) { 1124 | return vow.reject(e); 1125 | } 1126 | }, 1127 | 1128 | /** 1129 | * Returns a promise, that will be fulfilled only after all the items in `iterable` are fulfilled. 1130 | * If any of the `iterable` items gets rejected, the promise will be rejected. 1131 | * 1132 | * @param {Array|Object} iterable 1133 | * @returns {vow:Promise} 1134 | * 1135 | * @example 1136 | * with array: 1137 | * ```js 1138 | * var defer1 = vow.defer(), 1139 | * defer2 = vow.defer(); 1140 | * 1141 | * vow.all([defer1.promise(), defer2.promise(), 3]) 1142 | * .then(function(value) { 1143 | * // value is "[1, 2, 3]" here 1144 | * }); 1145 | * 1146 | * defer1.resolve(1); 1147 | * defer2.resolve(2); 1148 | * ``` 1149 | * 1150 | * @example 1151 | * with object: 1152 | * ```js 1153 | * var defer1 = vow.defer(), 1154 | * defer2 = vow.defer(); 1155 | * 1156 | * vow.all({ p1 : defer1.promise(), p2 : defer2.promise(), p3 : 3 }) 1157 | * .then(function(value) { 1158 | * // value is "{ p1 : 1, p2 : 2, p3 : 3 }" here 1159 | * }); 1160 | * 1161 | * defer1.resolve(1); 1162 | * defer2.resolve(2); 1163 | * ``` 1164 | */ 1165 | all : function(iterable) { 1166 | var defer = new Deferred(), 1167 | isPromisesArray = isArray(iterable), 1168 | keys = isPromisesArray? 1169 | getArrayKeys(iterable) : 1170 | getObjectKeys(iterable), 1171 | len = keys.length, 1172 | res = isPromisesArray? [] : {}; 1173 | 1174 | if(!len) { 1175 | defer.resolve(res); 1176 | return defer.promise(); 1177 | } 1178 | 1179 | var i = len; 1180 | vow._forEach( 1181 | iterable, 1182 | function(value, idx) { 1183 | res[keys[idx]] = value; 1184 | if(!--i) { 1185 | defer.resolve(res); 1186 | } 1187 | }, 1188 | defer.reject, 1189 | defer.notify, 1190 | defer, 1191 | keys); 1192 | 1193 | return defer.promise(); 1194 | }, 1195 | 1196 | /** 1197 | * Returns a promise, that will be fulfilled only after all the items in `iterable` are resolved. 1198 | * 1199 | * @param {Array|Object} iterable 1200 | * @returns {vow:Promise} 1201 | * 1202 | * @example 1203 | * ```js 1204 | * var defer1 = vow.defer(), 1205 | * defer2 = vow.defer(); 1206 | * 1207 | * vow.allResolved([defer1.promise(), defer2.promise()]).spread(function(promise1, promise2) { 1208 | * promise1.isRejected(); // returns true 1209 | * promise1.valueOf(); // returns "'error'" 1210 | * promise2.isFulfilled(); // returns true 1211 | * promise2.valueOf(); // returns "'ok'" 1212 | * }); 1213 | * 1214 | * defer1.reject('error'); 1215 | * defer2.resolve('ok'); 1216 | * ``` 1217 | */ 1218 | allResolved : function(iterable) { 1219 | var defer = new Deferred(), 1220 | isPromisesArray = isArray(iterable), 1221 | keys = isPromisesArray? 1222 | getArrayKeys(iterable) : 1223 | getObjectKeys(iterable), 1224 | i = keys.length, 1225 | res = isPromisesArray? [] : {}; 1226 | 1227 | if(!i) { 1228 | defer.resolve(res); 1229 | return defer.promise(); 1230 | } 1231 | 1232 | var onResolved = function() { 1233 | --i || defer.resolve(iterable); 1234 | }; 1235 | 1236 | vow._forEach( 1237 | iterable, 1238 | onResolved, 1239 | onResolved, 1240 | defer.notify, 1241 | defer, 1242 | keys); 1243 | 1244 | return defer.promise(); 1245 | }, 1246 | 1247 | allSettled : function(iterable) { 1248 | return vow.allResolved(iterable).then(function() { 1249 | var isPromisesArray = isArray(iterable), 1250 | keys = isPromisesArray? 1251 | getArrayKeys(iterable) : 1252 | getObjectKeys(iterable), 1253 | res = isPromisesArray? [] : {}, 1254 | len = keys.length, i = 0, key, value, item; 1255 | 1256 | while(i < len) { 1257 | key = keys[i++]; 1258 | promise = iterable[key]; 1259 | value = promise.valueOf(); 1260 | item = promise.isRejected()? 1261 | { status : 'rejected', reason : value } : 1262 | { status : 'fulfilled', value : value }; 1263 | 1264 | isPromisesArray? 1265 | res.push(item) : 1266 | res[key] = item; 1267 | } 1268 | 1269 | return res; 1270 | }); 1271 | }, 1272 | 1273 | allPatiently : function(iterable) { 1274 | return vow.allResolved(iterable).then(function() { 1275 | var isPromisesArray = isArray(iterable), 1276 | keys = isPromisesArray? 1277 | getArrayKeys(iterable) : 1278 | getObjectKeys(iterable), 1279 | rejectedPromises, fulfilledPromises, 1280 | len = keys.length, i = 0, key, promise; 1281 | 1282 | if(!len) { 1283 | return isPromisesArray? [] : {}; 1284 | } 1285 | 1286 | while(i < len) { 1287 | key = keys[i++]; 1288 | promise = iterable[key]; 1289 | if(vow.isRejected(promise)) { 1290 | rejectedPromises || (rejectedPromises = isPromisesArray? [] : {}); 1291 | isPromisesArray? 1292 | rejectedPromises.push(promise.valueOf()) : 1293 | rejectedPromises[key] = promise.valueOf(); 1294 | } 1295 | else if(!rejectedPromises) { 1296 | (fulfilledPromises || (fulfilledPromises = isPromisesArray? [] : {}))[key] = vow.valueOf(promise); 1297 | } 1298 | } 1299 | 1300 | if(rejectedPromises) { 1301 | throw rejectedPromises; 1302 | } 1303 | 1304 | return fulfilledPromises; 1305 | }); 1306 | }, 1307 | 1308 | /** 1309 | * Returns a promise, that will be fulfilled if any of the items in `iterable` is fulfilled. 1310 | * If all of the `iterable` items get rejected, the promise will be rejected (with the reason of the first rejected item). 1311 | * 1312 | * @param {Array} iterable 1313 | * @returns {vow:Promise} 1314 | */ 1315 | any : function(iterable) { 1316 | var defer = new Deferred(), 1317 | len = iterable.length; 1318 | 1319 | if(!len) { 1320 | defer.reject(Error()); 1321 | return defer.promise(); 1322 | } 1323 | 1324 | var i = 0, reason; 1325 | vow._forEach( 1326 | iterable, 1327 | defer.resolve, 1328 | function(e) { 1329 | i || (reason = e); 1330 | ++i === len && defer.reject(reason); 1331 | }, 1332 | defer.notify, 1333 | defer); 1334 | 1335 | return defer.promise(); 1336 | }, 1337 | 1338 | /** 1339 | * Returns a promise, that will be fulfilled only when any of the items in `iterable` is fulfilled. 1340 | * If any of the `iterable` items gets rejected, the promise will be rejected. 1341 | * 1342 | * @param {Array} iterable 1343 | * @returns {vow:Promise} 1344 | */ 1345 | anyResolved : function(iterable) { 1346 | var defer = new Deferred(), 1347 | len = iterable.length; 1348 | 1349 | if(!len) { 1350 | defer.reject(Error()); 1351 | return defer.promise(); 1352 | } 1353 | 1354 | vow._forEach( 1355 | iterable, 1356 | defer.resolve, 1357 | defer.reject, 1358 | defer.notify, 1359 | defer); 1360 | 1361 | return defer.promise(); 1362 | }, 1363 | 1364 | /** 1365 | * Static equivalent to `promise.delay`. 1366 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 1367 | * 1368 | * @param {*} value 1369 | * @param {Number} delay 1370 | * @returns {vow:Promise} 1371 | */ 1372 | delay : function(value, delay) { 1373 | return vow.resolve(value).delay(delay); 1374 | }, 1375 | 1376 | /** 1377 | * Static equivalent to `promise.timeout`. 1378 | * If `value` is not a promise, then `value` is treated as a fulfilled promise. 1379 | * 1380 | * @param {*} value 1381 | * @param {Number} timeout 1382 | * @returns {vow:Promise} 1383 | */ 1384 | timeout : function(value, timeout) { 1385 | return vow.resolve(value).timeout(timeout); 1386 | }, 1387 | 1388 | _forEach : function(promises, onFulfilled, onRejected, onProgress, ctx, keys) { 1389 | var len = keys? keys.length : promises.length, 1390 | i = 0; 1391 | 1392 | while(i < len) { 1393 | vow.when( 1394 | promises[keys? keys[i] : i], 1395 | wrapOnFulfilled(onFulfilled, i), 1396 | onRejected, 1397 | onProgress, 1398 | ctx); 1399 | ++i; 1400 | } 1401 | }, 1402 | 1403 | TimedOutError : defineCustomErrorType('TimedOut') 1404 | }; 1405 | 1406 | var defineAsGlobal = true; 1407 | if(typeof module === 'object' && typeof module.exports === 'object') { 1408 | module.exports = vow; 1409 | defineAsGlobal = false; 1410 | } 1411 | 1412 | if(typeof modules === 'object' && isFunction(modules.define)) { 1413 | modules.define('vow', function(provide) { 1414 | provide(vow); 1415 | }); 1416 | defineAsGlobal = false; 1417 | } 1418 | 1419 | if(typeof define === 'function') { 1420 | define(function(require, exports, module) { 1421 | module.exports = vow; 1422 | }); 1423 | defineAsGlobal = false; 1424 | } 1425 | 1426 | defineAsGlobal && (global.vow = vow); 1427 | 1428 | })(typeof window !== 'undefined'? window : global); 1429 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vow", 3 | "version": "0.4.20", 4 | "description": "DOM Promise and Promises/A+ implementation for Node.js and browsers", 5 | "homepage": "http://dfilatov.github.io/vow/", 6 | "keywords": [ 7 | "nodejs", 8 | "browser", 9 | "async", 10 | "promise", 11 | "dom", 12 | "a+" 13 | ], 14 | "author": "Dmitry Filatov ", 15 | "contributors": [ 16 | { 17 | "name": "Dmitry Filatov", 18 | "email": "dfilatov@yandex-team.ru" 19 | } 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "http://github.com/dfilatov/vow.git" 24 | }, 25 | "dependencies": {}, 26 | "devDependencies": { 27 | "nodeunit": "0.11.3", 28 | "istanbul": "0.4.5", 29 | "terser": "4.0.2", 30 | "promises-aplus-tests": "2.1.0", 31 | "marked": "0.6.3", 32 | "jspath": "0.2.11", 33 | "yate": "0.0.65", 34 | "highlight.js": "7.5.0", 35 | "bem-jsd": "1.3.1" 36 | }, 37 | "license": "MIT", 38 | "main": "lib/vow", 39 | "engines": { 40 | "node": ">= 0.4.0" 41 | }, 42 | "scripts": { 43 | "test": "./node_modules/istanbul/lib/cli.js test test/utils/runner.js" 44 | }, 45 | "enb": { 46 | "sources": [ 47 | "lib/vow.js" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/defer.notify.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'onProgress callbacks should be called on each notify' : function(test) { 3 | var defer = Vow.defer(), 4 | promise = defer.promise(), 5 | calledArgs1 = [], 6 | calledArgs2 = []; 7 | 8 | promise.progress(function(val) { 9 | calledArgs1.push(val); 10 | }); 11 | 12 | promise.then(null, null, function(val) { 13 | calledArgs2.push(val); 14 | }); 15 | 16 | defer.notify(1); 17 | defer.notify(2); 18 | promise.then(function() { 19 | test.deepEqual(calledArgs1, [1, 2]); 20 | test.deepEqual(calledArgs2, [1, 2]); 21 | test.done(); 22 | }); 23 | 24 | defer.resolve(); 25 | }, 26 | 27 | 'onProgress callbacks shouldn\'t be called after promise has been fulfilled' : function(test) { 28 | var defer = Vow.defer(), 29 | promise = defer.promise(), 30 | called = false; 31 | 32 | promise.progress(function() { 33 | called = true; 34 | }); 35 | 36 | defer.resolve(); 37 | defer.notify(); 38 | defer.notify(); 39 | promise.then(function() { 40 | test.ok(!called); 41 | test.done(); 42 | }); 43 | }, 44 | 45 | 'onProgress callbacks shouldn\'t be called after promise has been rejected' : function(test) { 46 | var defer = Vow.defer(), 47 | promise = defer.promise(), 48 | called = false; 49 | 50 | promise.progress(function() { 51 | called = true; 52 | }); 53 | 54 | defer.reject(); 55 | defer.notify(); 56 | defer.notify(); 57 | promise.fail(function() { 58 | test.ok(!called); 59 | test.done(); 60 | }); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /test/defer.reject.js: -------------------------------------------------------------------------------- 1 | var nextTick = process.nextTick; 2 | 3 | module.exports = { 4 | 'onRejected callbacks should be called on reject only' : function(test) { 5 | var defer = Vow.defer(), 6 | promise = defer.promise(), 7 | called1 = false, 8 | called2 = false, 9 | called3 = false, 10 | called4 = false; 11 | 12 | promise.then(null, function() { 13 | called1 = true; 14 | }); 15 | 16 | promise.then(function() { 17 | called2 = true; 18 | }); 19 | 20 | promise.then(null, function() { 21 | called3 = true; 22 | }); 23 | 24 | defer.reject(); 25 | 26 | promise.then(null, function() { 27 | called4 = true; 28 | }); 29 | 30 | promise.then(null, function() { 31 | test.ok(called1); 32 | test.ok(!called2); 33 | test.ok(called3); 34 | test.done(); 35 | }); 36 | }, 37 | 38 | 'onRejected callbacks should be called once' : function(test) { 39 | var defer = Vow.defer(), 40 | promise = defer.promise(), 41 | calledCnt = 0; 42 | 43 | promise.then(null, function() { 44 | calledCnt++; 45 | }); 46 | 47 | defer.reject(); 48 | defer.reject(); 49 | 50 | promise.then(null, function() { 51 | test.strictEqual(calledCnt, 1); 52 | test.done(); 53 | }); 54 | }, 55 | 56 | 'onRejected callbacks shouldn\'t be called if reject have been called after fulfill' : function(test) { 57 | var defer = Vow.defer(), 58 | promise = defer.promise(), 59 | called = false; 60 | 61 | promise.then(null, function() { 62 | called = true; 63 | }); 64 | 65 | defer.resolve(); 66 | defer.reject(); 67 | 68 | promise.then(function() { 69 | test.ok(!called); 70 | test.done(); 71 | }); 72 | }, 73 | 74 | 'onRejected callbacks should be executed in the order of their originating calls to then' : function(test) { 75 | var defer = Vow.defer(), 76 | promise = defer.promise(), 77 | resOrder = []; 78 | 79 | promise.then(null, function() { 80 | resOrder.push(1); 81 | }); 82 | 83 | promise.then(null, function() { 84 | resOrder.push(2); 85 | }); 86 | 87 | defer.reject(); 88 | 89 | promise.then(null, function() { 90 | resOrder.push(3); 91 | }); 92 | 93 | promise.then(null, function() { 94 | resOrder.push(4); 95 | }); 96 | 97 | setTimeout(function() { 98 | promise.then(null, function() { 99 | resOrder.push(5); 100 | setTimeout(function() { 101 | test.deepEqual(resOrder, [1, 2, 3, 4, 5, 6]); 102 | test.done(); 103 | }, 10); 104 | }); 105 | 106 | promise.then(null, function() { 107 | resOrder.push(6); 108 | }); 109 | }, 10); 110 | }, 111 | 112 | 'onRejected callback shouldn\'t be called in the same turn of the event loop as the call to then' : function(test) { 113 | var defer = Vow.defer(), 114 | promise = defer.promise(), 115 | resOrder = []; 116 | 117 | promise.then(null, function() { 118 | resOrder.push(3); 119 | }); 120 | 121 | nextTick(function() { 122 | nextTick(function() { 123 | resOrder.push(6); 124 | nextTick(function() { 125 | resOrder.push(7); 126 | }); 127 | promise.then(null, function() { 128 | resOrder.push(8); 129 | }); 130 | }); 131 | 132 | promise.then(null, function() { 133 | resOrder.push(4); 134 | }); 135 | 136 | promise.then(null, function() { 137 | resOrder.push(5); 138 | }); 139 | 140 | resOrder.push(2); 141 | }); 142 | 143 | resOrder.push(1); 144 | defer.reject(); 145 | 146 | setTimeout(function() { 147 | test.deepEqual(resOrder, [1, 2, 3, 4, 5, 6, 7, 8]); 148 | test.done(); 149 | }, 20); 150 | }, 151 | 152 | 'should reject promise if reason is fulfilled promise': function (test) { 153 | var defer = Vow.defer(); 154 | defer.reject(Vow.fulfill('ok')); 155 | defer.promise().fail(function(val) { 156 | test.strictEqual(val, 'ok'); 157 | test.done(); 158 | }); 159 | }, 160 | 161 | 'should reject promise if reason is rejected promise': function (test) { 162 | var defer = Vow.defer(); 163 | defer.reject(Vow.reject('error')); 164 | defer.promise().fail(function(val) { 165 | test.strictEqual(val, 'error'); 166 | test.done(); 167 | }); 168 | } 169 | }; 170 | -------------------------------------------------------------------------------- /test/defer.resolve.js: -------------------------------------------------------------------------------- 1 | var nextTick = process.nextTick; 2 | 3 | module.exports = { 4 | 'onFulfilled callbacks should be called on fulfill only' : function(test) { 5 | var defer = Vow.defer(), 6 | promise = defer.promise(), 7 | called1 = false, 8 | called2 = false, 9 | called3 = false, 10 | called4 = false; 11 | 12 | promise.then(function() { 13 | called1 = true; 14 | }); 15 | 16 | promise.then(null, function() { 17 | called2 = true; 18 | }); 19 | 20 | promise.then(function() { 21 | called3 = true; 22 | }); 23 | 24 | defer.resolve(); 25 | 26 | promise.then(function() { 27 | called4 = true; 28 | }); 29 | 30 | promise.then(function() { 31 | test.ok(called1); 32 | test.ok(!called2); 33 | test.ok(called3); 34 | test.done(); 35 | }); 36 | }, 37 | 38 | 'onFulfilled callbacks should be called once' : function(test) { 39 | var defer = Vow.defer(), 40 | promise = defer.promise(), 41 | calledCnt = 0; 42 | 43 | promise.then(function() { 44 | calledCnt++; 45 | }); 46 | 47 | defer.resolve(); 48 | defer.resolve(); 49 | 50 | promise.then(function() { 51 | test.strictEqual(calledCnt, 1); 52 | test.done(); 53 | }); 54 | }, 55 | 56 | 'onFulfilled callbacks shouldn\'t be called if fulfill have been called after reject' : function(test) { 57 | var defer = Vow.defer(), 58 | promise = defer.promise(), 59 | called = false; 60 | 61 | promise.then(function() { 62 | called = true; 63 | }); 64 | 65 | defer.reject(); 66 | defer.resolve(); 67 | 68 | promise.then(null, function() { 69 | test.ok(!called); 70 | test.done(); 71 | }); 72 | }, 73 | 74 | 'onFulfilled callbacks should be executed in the order of their originating calls to then' : function(test) { 75 | var defer = Vow.defer(), 76 | promise = defer.promise(), 77 | resOrder = []; 78 | 79 | promise.then(function() { 80 | resOrder.push(1); 81 | }); 82 | 83 | promise.then(function() { 84 | resOrder.push(2); 85 | }); 86 | 87 | defer.resolve(); 88 | 89 | promise.then(function() { 90 | resOrder.push(3); 91 | }); 92 | 93 | promise.then(function() { 94 | resOrder.push(4); 95 | }); 96 | 97 | setTimeout(function() { 98 | promise.then(function() { 99 | resOrder.push(5); 100 | setTimeout(function() { 101 | test.deepEqual(resOrder, [1, 2, 3, 4, 5, 6]); 102 | test.done(); 103 | }, 10); 104 | }); 105 | 106 | promise.then(function() { 107 | resOrder.push(6); 108 | }); 109 | }, 10); 110 | }, 111 | 112 | 'onFulfilled callback shouldn\'t be called in the same turn of the event loop as the call to then' : function(test) { 113 | var defer = Vow.defer(), 114 | promise = defer.promise(), 115 | resOrder = []; 116 | 117 | promise.then(function() { 118 | resOrder.push(3); 119 | }); 120 | 121 | nextTick(function() { 122 | nextTick(function() { 123 | resOrder.push(6); 124 | nextTick(function() { 125 | resOrder.push(7); 126 | }); 127 | promise.then(function() { 128 | resOrder.push(8); 129 | }); 130 | }); 131 | 132 | promise.then(function() { 133 | resOrder.push(4); 134 | }); 135 | 136 | promise.then(function() { 137 | resOrder.push(5); 138 | }); 139 | 140 | resOrder.push(2); 141 | }); 142 | 143 | resOrder.push(1); 144 | defer.resolve(); 145 | 146 | setTimeout(function() { 147 | test.deepEqual(resOrder, [1, 2, 3, 4, 5, 6, 7, 8]); 148 | test.done(); 149 | }, 20); 150 | } 151 | }; 152 | -------------------------------------------------------------------------------- /test/event.unhandledRejection.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '"unhandledRejection" event should be emitted if there is no onRejected callback' : function(test) { 3 | var defer = Vow.defer(), 4 | promise = defer.promise(), 5 | listener = function(reason, promise) { 6 | test.strictEqual(reason, 'err'); 7 | test.ok(promise.isRejected()); 8 | 9 | process.removeListener('unhandledRejection', listener); 10 | 11 | test.done(); 12 | }; 13 | 14 | process.on('unhandledRejection', listener); 15 | 16 | promise.then().then(); 17 | 18 | defer.reject('err'); 19 | }, 20 | 21 | '"unhandledRejection" event should not be emitted if there is onRejected callback' : function(test) { 22 | var defer = Vow.defer(), 23 | promise = defer.promise(), 24 | listenerCalled = false, 25 | listener = function() { 26 | listenerCalled = true; 27 | }; 28 | 29 | process.on('unhandledRejection', listener); 30 | 31 | promise.then().fail(function() {}); 32 | 33 | defer.reject('err'); 34 | 35 | setTimeout(function() { 36 | test.ok(!listenerCalled); 37 | 38 | process.removeListener('unhandledRejection', listener); 39 | 40 | test.done(); 41 | }, 30); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /test/promise.always.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'onResolved callback should be called on fulfill' : function(test) { 3 | var defer = Vow.defer(); 4 | defer.resolve('ok'); 5 | defer.promise().always(function(promise) { 6 | test.ok(promise.isFulfilled()); 7 | test.strictEqual(promise.valueOf(), 'ok'); 8 | test.done(); 9 | }); 10 | }, 11 | 12 | 'onResolved callback should be called on reject' : function(test) { 13 | var defer = Vow.defer(); 14 | defer.reject('error'); 15 | defer.promise().always(function(promise) { 16 | test.ok(promise.isRejected()); 17 | test.strictEqual(promise.valueOf(), 'error'); 18 | test.done(); 19 | }); 20 | }, 21 | 22 | 'resulting promise should be fulfilled with returned value of onResolved callback' : function(test) { 23 | var defer = Vow.defer(); 24 | defer.resolve('ok'); 25 | defer.promise() 26 | .always(function() { 27 | return 'ok-always'; 28 | }) 29 | .then(function(val) { 30 | test.strictEqual(val, 'ok-always'); 31 | test.done(); 32 | }); 33 | }, 34 | 35 | 'resulting promise should be rejected with exception in onResolved callback' : function(test) { 36 | var defer = Vow.defer(); 37 | defer.resolve('ok'); 38 | defer.promise() 39 | .always(function() { 40 | throw 'error-always'; 41 | }) 42 | .fail(function(err) { 43 | test.strictEqual(err, 'error-always'); 44 | test.done(); 45 | }); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /test/promise.delay.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'resulting promise should be fulfilled after delay' : function(test) { 3 | var defer = Vow.defer(), 4 | resPromise = defer.promise().delay(30); 5 | 6 | setTimeout(function() { 7 | test.ok(!resPromise.isRejected()); 8 | test.ok(!resPromise.isFulfilled()); 9 | }, 20); 10 | setTimeout(function() { 11 | test.ok(resPromise.isFulfilled()); 12 | test.strictEqual(resPromise.valueOf(), 'ok'); 13 | test.done(); 14 | }, 40); 15 | defer.resolve('ok'); 16 | }, 17 | 18 | 'resulting promise should be immediately rejected' : function(test) { 19 | var defer = Vow.defer(), 20 | resPromise = defer.promise().delay(30); 21 | 22 | setTimeout(function() { 23 | test.ok(resPromise.isRejected()); 24 | test.strictEqual(resPromise.valueOf(), 'error'); 25 | test.done(); 26 | }, 10); 27 | 28 | defer.reject('error'); 29 | } 30 | }; -------------------------------------------------------------------------------- /test/promise.done.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'exception should be throwed' : function(test) { 3 | var defer = Vow.defer(), 4 | e = Error(); 5 | 6 | defer.promise().done(); 7 | defer.reject(e); 8 | 9 | process.once('uncaughtException', function(_e) { 10 | test.strictEqual(_e, e); 11 | test.done(); 12 | }); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /test/promise.fail.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'onRejected callback should be called on reject' : function(test) { 3 | var defer = Vow.defer(); 4 | 5 | defer.reject('error'); 6 | defer.promise().fail(function(error) { 7 | test.strictEqual(error, 'error'); 8 | test.done(); 9 | }); 10 | } 11 | }; -------------------------------------------------------------------------------- /test/promise.finally.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'onFinalized callback should be called without arguments on fulfill' : function(test) { 3 | var defer = Vow.defer(); 4 | defer.resolve('ok'); 5 | defer.promise().finally(function() { 6 | test.strictEqual(arguments.length, 0); 7 | test.done(); 8 | }); 9 | }, 10 | 11 | 'onFinalized callback should be called without arguments on reject' : function(test) { 12 | var defer = Vow.defer(); 13 | defer.reject('error'); 14 | defer.promise().finally(function() { 15 | test.strictEqual(arguments.length, 0); 16 | test.done(); 17 | }); 18 | }, 19 | 20 | 'returned promise should be fulfilled with value of original promise' : function(test) { 21 | var defer = Vow.defer(); 22 | defer.resolve('ok'); 23 | defer.promise() 24 | .finally(function() { 25 | return 'finally'; 26 | }) 27 | .then(function(val) { 28 | test.strictEqual(val, 'ok'); 29 | test.done(); 30 | }); 31 | }, 32 | 33 | 'returned promise should be rejected with value of original promise' : function(test) { 34 | var defer = Vow.defer(), 35 | error = new Error('error'); 36 | defer.reject(error); 37 | defer.promise() 38 | .finally(function() { 39 | return new Error('finally'); 40 | }) 41 | .fail(function(_error) { 42 | test.strictEqual(_error, error); 43 | test.done(); 44 | }); 45 | }, 46 | 47 | 'returned promise should be rejected with value of returned promise' : function(test) { 48 | var defer = Vow.defer(), 49 | error = new Error('error'), 50 | finallyError = new Error('finally'); 51 | defer.reject(error); 52 | defer.promise() 53 | .finally(function() { 54 | return Vow.reject(finallyError); 55 | }) 56 | .fail(function(_error) { 57 | test.strictEqual(_error, finallyError); 58 | test.done(); 59 | }); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /test/promise.spread.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'onFulfilled argument should be spreaded' : function(test) { 3 | var defer = Vow.defer(); 4 | defer.promise().spread(function(arg1, arg2, arg3) { 5 | test.strictEqual(arguments.length, 3); 6 | test.strictEqual(arg1, 1); 7 | test.strictEqual(arg2, '2'); 8 | test.strictEqual(arg3, true); 9 | test.done(); 10 | }); 11 | defer.resolve([1, '2', true]); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /test/promise.then.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'resulting promise should be fulfilled with same value' : function(test) { 3 | var defer = Vow.defer(); 4 | 5 | defer.promise().then().then(function(val) { 6 | test.strictEqual(val, 'val'); 7 | test.done(); 8 | }); 9 | 10 | defer.resolve('val'); 11 | }, 12 | 13 | 'resulting promise should be rejected with same reason' : function(test) { 14 | var defer = Vow.defer(), 15 | error = new Error('error'); 16 | 17 | defer.promise().then().then(null, function(_error) { 18 | test.strictEqual(_error, error); 19 | test.done(); 20 | }); 21 | 22 | defer.reject(error); 23 | }, 24 | 25 | 'resulting promise should be notified with same value' : function(test) { 26 | var defer = Vow.defer(); 27 | 28 | defer.promise().then().then(null, null, function(val) { 29 | test.strictEqual(val, 1); 30 | test.done(); 31 | }); 32 | 33 | defer.notify(1); 34 | }, 35 | 36 | 'resulting promise should be fulfilled with returned value of onFulfilled callback' : function(test) { 37 | var defer = Vow.defer(), 38 | resPromise = defer.promise().then(function() { 39 | return 'val'; 40 | }); 41 | 42 | defer.resolve(); 43 | 44 | resPromise.then(function(val) { 45 | test.strictEqual(val, 'val'); 46 | test.done(); 47 | }); 48 | }, 49 | 50 | 'resulting promise should be fulfilled with same value as returned promise of onFulfilled callback' : function(test) { 51 | var defer = Vow.defer(), 52 | retdefer = Vow.defer(), 53 | resPromise = defer.promise().then(function() { 54 | return retdefer.promise(); 55 | }); 56 | 57 | defer.promise().then(function() { 58 | retdefer.resolve('ok'); 59 | }); 60 | 61 | resPromise.then(function(val) { 62 | test.strictEqual(val, 'ok'); 63 | test.done(); 64 | }); 65 | 66 | defer.resolve(); 67 | }, 68 | 69 | 'resulting promise should be rejected with same value as returned promise of onRejected callback' : function(test) { 70 | var defer = Vow.defer(), 71 | retdefer = Vow.defer(), 72 | resPromise = defer.promise().then(function() { 73 | return retdefer.promise(); 74 | }), 75 | error = new Error('error'); 76 | 77 | defer.promise().then(function() { 78 | retdefer.reject(error); 79 | }); 80 | 81 | resPromise.then(null, function(_error) { 82 | test.strictEqual(_error, error); 83 | test.done(); 84 | }); 85 | 86 | defer.resolve(); 87 | }, 88 | 89 | 'resulting promise should be rejected if onFulfilled callback throw exception' : function(test) { 90 | var defer = Vow.defer(), 91 | resPromise = defer.promise().then(function() { 92 | throw { message : 'error' }; 93 | }); 94 | 95 | resPromise.then(null, function(arg) { 96 | test.deepEqual(arg, { message : 'error' }); 97 | test.done(); 98 | }); 99 | 100 | defer.resolve(); 101 | }, 102 | 103 | 'resulting promise should be rejected if onRejected callback throw exception' : function(test) { 104 | var defer = Vow.defer(), 105 | resPromise = defer.promise().then(null, function() { 106 | throw { message : 'error' }; 107 | }); 108 | 109 | resPromise.then(null, function(arg) { 110 | test.deepEqual(arg, { message : 'error' }); 111 | test.done(); 112 | }); 113 | 114 | defer.reject(); 115 | }, 116 | 117 | 'resulting promise should be notified with same value as returned promise of onFulfilled callback' : function(test) { 118 | var defer = Vow.defer(), 119 | retdefer = Vow.defer(), 120 | resPromise = defer.promise().then(function() { 121 | return retdefer.promise(); 122 | }); 123 | 124 | defer.promise().then(function() { 125 | retdefer.notify('ok'); 126 | }); 127 | 128 | resPromise.then(null, null, function(val) { 129 | test.strictEqual(val, 'ok'); 130 | test.done(); 131 | }); 132 | 133 | defer.resolve(); 134 | }, 135 | 136 | 'resulting promise should be notified with same value as returned promise of onRejected callback' : function(test) { 137 | var defer = Vow.defer(), 138 | retdefer = Vow.defer(), 139 | resPromise = defer.promise().then(null, function() { 140 | return retdefer.promise(); 141 | }); 142 | 143 | defer.promise().then(null, function() { 144 | retdefer.notify('ok'); 145 | }); 146 | 147 | resPromise.then(null, null, function(val) { 148 | test.strictEqual(val, 'ok'); 149 | test.done(); 150 | }); 151 | 152 | defer.reject(); 153 | }, 154 | 155 | 'resulting promise should be notified with returned value of onProgress callback' : function(test) { 156 | var defer = Vow.defer(); 157 | 158 | defer.promise() 159 | .then(null, null, function(val) { 160 | return val + 1; 161 | }) 162 | .then(null, null, function(val) { 163 | test.strictEqual(val, 2); 164 | test.done(); 165 | }); 166 | 167 | defer.notify(1); 168 | }, 169 | 170 | 'onFulfilled callback should be called in given context' : function(test) { 171 | var defer = Vow.defer(), 172 | ctx = {}; 173 | 174 | defer.promise().then(function() { 175 | test.strictEqual(ctx, this); 176 | test.done(); 177 | }, ctx); 178 | 179 | defer.resolve(); 180 | }, 181 | 182 | 'onRejected callback should be called in given context' : function(test) { 183 | var defer = Vow.defer(), 184 | ctx = {}; 185 | 186 | defer.promise().then(null, function() { 187 | test.strictEqual(ctx, this); 188 | test.done(); 189 | }, ctx); 190 | 191 | defer.reject(); 192 | }, 193 | 194 | 'onProgress callback should be called in given context' : function(test) { 195 | var defer = Vow.defer(), 196 | ctx = {}; 197 | 198 | defer.promise().then(null, null, function() { 199 | test.strictEqual(ctx, this); 200 | test.done(); 201 | }, ctx); 202 | 203 | defer.notify(); 204 | } 205 | }; -------------------------------------------------------------------------------- /test/promise.timeout.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'resulting promise should be rejected after timeout' : function(test) { 3 | var resPromise = Vow.defer().promise().timeout(10); 4 | setTimeout(function() { 5 | test.ok(resPromise.isRejected()); 6 | test.ok(resPromise.valueOf() instanceof Vow.TimedOutError); 7 | test.done(); 8 | }, 20); 9 | }, 10 | 11 | 'resulting promise should be fulfilled before timeout' : function(test) { 12 | var defer = Vow.defer(), 13 | resPromise = defer.promise().timeout(20); 14 | 15 | setTimeout(function() { 16 | defer.resolve('val'); 17 | }, 10); 18 | 19 | resPromise.then(function(val) { 20 | test.strictEqual(val, 'val'); 21 | test.done(); 22 | }); 23 | }, 24 | 25 | 'resulting promise should be rejected before timeout' : function(test) { 26 | var defer = Vow.defer(), 27 | resPromise = defer.promise().timeout(20), 28 | error = new Error('error'); 29 | 30 | setTimeout(function() { 31 | defer.reject(error); 32 | }, 10); 33 | 34 | resPromise.then(null, function(_error) { 35 | test.strictEqual(_error, error); 36 | test.done(); 37 | }); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /test/promise.valueof.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'should return undefined if promise is unresolved' : function(test) { 3 | var promise = Vow.defer().promise(); 4 | 5 | test.strictEqual(promise.valueOf(), undefined); 6 | test.done(); 7 | }, 8 | 9 | 'should return value of fulfillment if promise if fulfilled' : function(test) { 10 | var defer = Vow.defer(); 11 | 12 | defer.resolve('ok'); 13 | 14 | test.strictEqual(defer.promise().valueOf(), 'ok'); 15 | test.done(); 16 | }, 17 | 18 | 'should return reason of rejection if promise if rejected' : function(test) { 19 | var defer = Vow.defer(), 20 | error = Error(); 21 | 22 | defer.reject(error); 23 | 24 | test.strictEqual(defer.promise().valueOf(), error); 25 | test.done(); 26 | } 27 | }; -------------------------------------------------------------------------------- /test/utils/aplus-adapter.js: -------------------------------------------------------------------------------- 1 | var Vow = require('../..'); 2 | 3 | module.exports = { 4 | resolved : function(val) { 5 | var defer = Vow.defer(); 6 | defer.resolve(val); 7 | return defer.promise(); 8 | }, 9 | 10 | rejected : function(reason) { 11 | var defer = Vow.defer(); 12 | defer.reject(reason); 13 | return defer.promise(); 14 | }, 15 | 16 | deferred : function() { 17 | var defer = Vow.defer(); 18 | return { 19 | promise : defer.promise(), 20 | 21 | resolve : function(val) { 22 | defer.resolve(val); 23 | }, 24 | 25 | reject : function(reason) { 26 | defer.reject(reason); 27 | } 28 | }; 29 | } 30 | }; -------------------------------------------------------------------------------- /test/utils/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | defersToPromises : function(defers) { 3 | if(Array.isArray(defers)) { 4 | return defers.map(function(defer) { 5 | return defer instanceof Vow.Deferred? 6 | defer.promise() : 7 | defer; 8 | }); 9 | } 10 | 11 | var res = {}; 12 | Object.keys(defers).forEach(function(key) { 13 | res[key] = defers[key] instanceof Vow.Deferred? 14 | defers[key].promise() : 15 | defers[key]; 16 | }); 17 | return res; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /test/utils/runner.js: -------------------------------------------------------------------------------- 1 | global.Vow = require('../..'); 2 | 3 | var fs = require('fs'), 4 | path = require('path'), 5 | dir = path.join(__dirname, '..'); 6 | 7 | require('nodeunit').reporters.default.run( 8 | fs.readdirSync(dir) 9 | .filter(function(file){ 10 | return fs.statSync(path.join(dir, file)).isFile(); 11 | }) 12 | .map(function(file) { 13 | return path.join('test', file); 14 | }), 15 | null, 16 | function(err) { 17 | err? 18 | process.exit(1) : 19 | require('promises-aplus-tests')(require('./aplus-adapter'), function(err) { 20 | err && process.exit(1); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/vow.all.js: -------------------------------------------------------------------------------- 1 | var defersToPromises = require('./utils/helpers').defersToPromises; 2 | 3 | module.exports = { 4 | 'for Array' : { 5 | 'resulting defer should be fulfilled after all defers fulfilled' : function(test) { 6 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()]; 7 | 8 | Vow.all(defersToPromises(defers)).then(function(vals) { 9 | test.deepEqual(vals, [0, 1, 2]); 10 | test.done(); 11 | }); 12 | 13 | defers.forEach(function(defer, i) { 14 | defer.resolve(i); 15 | }); 16 | }, 17 | 18 | 'resulting defer should be rejected if any defer rejected' : function(test) { 19 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()], 20 | error = new Error('error'); 21 | 22 | Vow.all(defersToPromises(defers)).then(null, function(_error) { 23 | test.deepEqual(_error, error); 24 | test.done(); 25 | }); 26 | 27 | defers.forEach(function(defer, i) { 28 | i % 2? defer.resolve() : defer.reject(error); 29 | }); 30 | }, 31 | 32 | 'resulting defer should be fulfilled if argument is empty array' : function(test) { 33 | Vow.all([]).then(function(vals) { 34 | test.deepEqual(vals, []); 35 | test.done(); 36 | }); 37 | }, 38 | 39 | 'resulting defer should be notified if argument if any defer notified' : function(test) { 40 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()], 41 | i = 0; 42 | 43 | Vow.all(defersToPromises(defers)).progress(function(val) { 44 | test.equal(val, i); 45 | (++i === defers.length) && test.done(); 46 | }); 47 | 48 | defers.forEach(function(defer, i) { 49 | defer.notify(i); 50 | }); 51 | }, 52 | 53 | 'arguments can contains non-defer items' : function(test) { 54 | var defers = [0, Vow.defer(), Vow.defer(), 3, undefined]; 55 | 56 | Vow.all(defersToPromises(defers)).then(function(vals) { 57 | test.deepEqual(vals, [0, 1, 2, 3, undefined]); 58 | test.done(); 59 | }); 60 | 61 | defers[1].resolve(1); 62 | defers[2].resolve(2); 63 | } 64 | }, 65 | 66 | 'for Object' : { 67 | 'resulting defer should be fulfilled after all defers fulfilled' : function(test) { 68 | var defers = { a : Vow.defer(), b : Vow.defer(), c :Vow.defer() }; 69 | 70 | Vow.all(defersToPromises(defers)).then(function(vals) { 71 | test.deepEqual(vals, { a : 'a', b :'b', c :'c' }); 72 | test.done(); 73 | }); 74 | 75 | Object.keys(defers).forEach(function(key) { 76 | defers[key].resolve(key); 77 | }); 78 | }, 79 | 80 | 'resulting defer should be rejected if any defer rejected' : function(test) { 81 | var defers = { a : Vow.defer(), b : Vow.defer(), c : Vow.defer() }, 82 | error = new Error('error'); 83 | 84 | Vow.all(defersToPromises(defers)).fail(function(_error) { 85 | test.deepEqual(_error, error); 86 | test.done(); 87 | }); 88 | 89 | Object.keys(defers).forEach(function(key, i) { 90 | var p = defers[key]; 91 | i % 2? p.resolve() : p.reject(error); 92 | }); 93 | }, 94 | 95 | 'resulting defer should be fulfilled if argument is empty object' : function(test) { 96 | Vow.all({}).then(function(vals) { 97 | test.deepEqual(vals, {}); 98 | test.done(); 99 | }); 100 | }, 101 | 102 | 'object properties can contains non-defer items' : function(test) { 103 | var defers = { a : 'a', b : Vow.defer(), c : Vow.defer(), d : 3, e : undefined }; 104 | 105 | Vow.all(defersToPromises(defers)).then(function(vals) { 106 | test.deepEqual(vals, { a : 'a', b : 1, c : 2, d : 3, e : undefined}); 107 | test.done(); 108 | }); 109 | 110 | defers.b.resolve(1); 111 | defers.c.resolve(2); 112 | } 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /test/vow.allPatiently.js: -------------------------------------------------------------------------------- 1 | var defersToPromises = require('./utils/helpers').defersToPromises; 2 | 3 | module.exports = { 4 | 'for Array' : { 5 | 'resulting promise should be resolved after all promises resolved' : function(test) { 6 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()]; 7 | 8 | Vow.allPatiently(defersToPromises(defers)).then(function(vals) { 9 | test.deepEqual(vals, [0, 1, 2]); 10 | test.done(); 11 | }); 12 | 13 | defers.forEach(function(defer, i) { 14 | defer.resolve(i); 15 | }); 16 | }, 17 | 18 | 'resulting promise should be rejected if any promise rejected' : function(test) { 19 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()], 20 | error1 = new Error('error1'), 21 | error2 = new Error('error2'); 22 | 23 | Vow.allPatiently(defersToPromises(defers)).then(null, function(errors) { 24 | test.deepEqual(errors, [error1, error2]); 25 | test.done(); 26 | }); 27 | 28 | defers[0].reject(error1); 29 | defers[1].resolve('ok'); 30 | defers[2].reject(error2); 31 | }, 32 | 33 | 'resulting promise should be resolved if argument is empty array' : function(test) { 34 | Vow.allPatiently([]).then(function(vals) { 35 | test.deepEqual(vals, []); 36 | test.done(); 37 | }); 38 | }, 39 | 40 | 'resulting defer should be notified if argument if any defer notified' : function(test) { 41 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()], 42 | i = 0; 43 | 44 | Vow.allPatiently(defersToPromises(defers)).progress(function(val) { 45 | test.equal(val, i); 46 | (++i === defers.length) && test.done(); 47 | }); 48 | 49 | defers.forEach(function(defer, i) { 50 | defer.notify(i); 51 | }); 52 | }, 53 | 54 | 'arguments can contain non-promise items' : function(test) { 55 | var defers = [0, Vow.defer(), Vow.defer(), 3, undefined]; 56 | 57 | Vow.allPatiently(defersToPromises(defers)).then(function(vals) { 58 | test.deepEqual(vals, [0, 1, 2, 3, undefined]); 59 | test.done(); 60 | }); 61 | 62 | defers[1].resolve(1); 63 | defers[2].resolve(2); 64 | } 65 | }, 66 | 67 | 'for Object' : { 68 | 'resulting promise should be resolved after all promises resolved' : function(test) { 69 | var defers = { 70 | a : Vow.defer(), 71 | b : Vow.defer(), 72 | c : Vow.defer() 73 | }; 74 | 75 | Vow.allPatiently(defersToPromises(defers)).then(function(vals) { 76 | test.deepEqual(vals, { a : 'a', b : 'b', c :'c' }); 77 | test.done(); 78 | }); 79 | 80 | Object.keys(defers).forEach(function(key) { 81 | defers[key].resolve(key); 82 | }); 83 | }, 84 | 85 | 'resulting promise should be rejected if any promise rejected' : function(test) { 86 | var defers = { 87 | a : Vow.defer(), 88 | b : Vow.defer(), 89 | c : Vow.defer() 90 | }, 91 | errorB = new Error('errorB'), 92 | errorC = new Error('errorC'); 93 | 94 | Vow.allPatiently(defersToPromises(defers)).fail(function(errors) { 95 | test.deepEqual(errors, { b : errorB, c : errorC }); 96 | test.done(); 97 | }); 98 | 99 | defers.a.resolve('ok'); 100 | defers.b.reject(errorB); 101 | defers.c.reject(errorC); 102 | }, 103 | 104 | 'resulting promise should be resolved if argument is empty object' : function(test) { 105 | Vow.allPatiently({}).then(function(vals) { 106 | test.deepEqual(vals, {}); 107 | test.done(); 108 | }); 109 | }, 110 | 111 | 'object properties can contains non-promise items' : function(test) { 112 | var defers = { 113 | a : 'a', 114 | b : Vow.defer(), 115 | c : Vow.defer(), 116 | d : 3, 117 | e : undefined 118 | }; 119 | 120 | Vow.allPatiently(defersToPromises(defers)).then(function(vals) { 121 | test.deepEqual(vals, { a : 'a', b : 1, c : 2, d : 3, e : undefined }); 122 | test.done(); 123 | }); 124 | 125 | defers.b.resolve(1); 126 | defers.c.resolve(2); 127 | } 128 | } 129 | }; 130 | -------------------------------------------------------------------------------- /test/vow.allResolved.js: -------------------------------------------------------------------------------- 1 | var defersToPromises = require('./utils/helpers').defersToPromises; 2 | 3 | module.exports = { 4 | 'for Array' : { 5 | 'resulting promise should be fulfilled after all promises fulfilled or rejected' : function(test) { 6 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()]; 7 | 8 | Vow.allResolved(defersToPromises(defers)).then(function(promises) { 9 | test.deepEqual(defersToPromises(defers), promises); 10 | promises.forEach(function(promise, i) { 11 | test.ok(i % 2? promise.isFulfilled() : promise.isRejected()); 12 | }); 13 | test.done(); 14 | }); 15 | 16 | defers.forEach(function(promise, i) { 17 | i % 2? promise.resolve() : promise.reject(); 18 | }); 19 | }, 20 | 21 | 'resulting promise should be fulfilled if argument is empty array' : function(test) { 22 | Vow.allResolved([]).then(function(vals) { 23 | test.deepEqual(vals, []); 24 | test.done(); 25 | }); 26 | }, 27 | 28 | 'resulting defer should be notified if argument if any defer notified' : function(test) { 29 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()], 30 | i = 0; 31 | 32 | Vow.allResolved(defersToPromises(defers)).progress(function(val) { 33 | test.equal(val, i); 34 | (++i === defers.length) && test.done(); 35 | }); 36 | 37 | defers.forEach(function(defer, i) { 38 | defer.notify(i); 39 | }); 40 | } 41 | }, 42 | 43 | 'for Object' : { 44 | 'resulting promise should be fulfilled after all promises fulfilled or rejected' : function(test) { 45 | var defers = { 46 | a : Vow.defer(), 47 | b : Vow.defer(), 48 | c : Vow.defer() 49 | }; 50 | 51 | Vow.allResolved(defersToPromises(defers)).then(function(promises) { 52 | test.deepEqual(defersToPromises(defers), promises); 53 | Object.keys(promises).forEach(function(key, i) { 54 | test.ok(i % 2? promises[key].isFulfilled() : promises[key].isRejected()); 55 | }); 56 | test.done(); 57 | }); 58 | 59 | Object.keys(defers).forEach(function(key, i) { 60 | i % 2? defers[key].resolve() : defers[key].reject(); 61 | }); 62 | }, 63 | 64 | 'resulting promise should be fulfilled if argument is empty object' : function(test) { 65 | Vow.allResolved({}).then(function(vals) { 66 | test.deepEqual(vals, {}); 67 | test.done(); 68 | }); 69 | } 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /test/vow.allSettled.js: -------------------------------------------------------------------------------- 1 | var defersToPromises = require('./utils/helpers').defersToPromises; 2 | 3 | module.exports = { 4 | 'for Array' : { 5 | 'resulting promise should be fulfilled after all promises fulfilled or rejected' : function(test) { 6 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()]; 7 | 8 | Vow.allSettled(defersToPromises(defers)).then(function(res) { 9 | test.deepEqual( 10 | res, 11 | [ 12 | { status : 'fulfilled', value : 'ok0' }, 13 | { status : 'rejected', reason : 'err1' }, 14 | { status : 'fulfilled', value : 'ok2' } 15 | ]); 16 | test.done(); 17 | }); 18 | 19 | defers.forEach(function(promise, i) { 20 | i % 2? promise.reject('err' + i) : promise.resolve('ok' + i); 21 | }); 22 | }, 23 | 24 | 'resulting promise should be fulfilled if argument is empty array' : function(test) { 25 | Vow.allSettled([]).then(function(vals) { 26 | test.deepEqual(vals, []); 27 | test.done(); 28 | }); 29 | }, 30 | 31 | 'resulting defer should be notified if argument if any defer notified' : function(test) { 32 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()], 33 | i = 0; 34 | 35 | Vow.allSettled(defersToPromises(defers)).progress(function(val) { 36 | test.equal(val, i); 37 | (++i === defers.length) && test.done(); 38 | }); 39 | 40 | defers.forEach(function(defer, i) { 41 | defer.notify(i); 42 | }); 43 | } 44 | }, 45 | 46 | 'for Object' : { 47 | 'resulting promise should be fulfilled after all promises fulfilled or rejected' : function(test) { 48 | var defers = { 49 | a : Vow.defer(), 50 | b : Vow.defer(), 51 | c : Vow.defer() 52 | }; 53 | 54 | Vow.allSettled(defersToPromises(defers)).then(function(res) { 55 | test.deepEqual( 56 | res, 57 | { 58 | a : { status : 'fulfilled', value : 'oka' }, 59 | b : { status : 'rejected', reason : 'errb' }, 60 | c : { status : 'fulfilled', value : 'okc' } 61 | }); 62 | test.done(); 63 | }); 64 | 65 | Object.keys(defers).forEach(function(key, i) { 66 | i % 2? defers[key].reject('err' + key) : defers[key].resolve('ok' + key); 67 | }); 68 | }, 69 | 70 | 'resulting promise should be fulfilled if argument is empty object' : function(test) { 71 | Vow.allSettled({}).then(function(vals) { 72 | test.deepEqual(vals, {}); 73 | test.done(); 74 | }); 75 | } 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /test/vow.always.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'onResolved callback should be called when argument fulfilled' : function(test) { 3 | var defer = Vow.defer(); 4 | 5 | Vow.always(defer.promise(), function(promise) { 6 | test.ok(promise.isFulfilled()); 7 | test.strictEqual(promise.valueOf(), 'ok'); 8 | test.done(); 9 | }); 10 | 11 | defer.resolve('ok'); 12 | }, 13 | 14 | 'onResolved callback should be called when argument rejected' : function(test) { 15 | var defer = Vow.defer(); 16 | 17 | Vow.always(defer.promise(), function(promise) { 18 | test.ok(promise.isRejected()); 19 | test.strictEqual(promise.valueOf(), 'err'); 20 | test.done(); 21 | }); 22 | 23 | defer.reject('err'); 24 | }, 25 | 26 | 'onResolved callback should be called when argument is non-promise' : function(test) { 27 | Vow.always('ok', function(promise) { 28 | test.ok(promise.isFulfilled()); 29 | test.strictEqual(promise.valueOf(), 'ok'); 30 | test.done(); 31 | }); 32 | } 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /test/vow.any.js: -------------------------------------------------------------------------------- 1 | var defersToPromises = require('./utils/helpers').defersToPromises; 2 | 3 | module.exports = { 4 | 'resulting promise should be fulfilled after any item fulfilled' : function(test) { 5 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()]; 6 | 7 | Vow.any(defersToPromises(defers)).then(function(val) { 8 | test.strictEqual(val, 'val'); 9 | test.done(); 10 | }); 11 | 12 | defers[2].reject('val'); 13 | defers[1].resolve('val'); 14 | }, 15 | 16 | 'resulting promise should be rejected after all items rejected' : function(test) { 17 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()]; 18 | 19 | Vow.any(defersToPromises(defers)).then(null, function(error) { 20 | test.strictEqual(error, 'error2'); 21 | test.done(); 22 | }); 23 | 24 | defers[2].reject('error2'); 25 | defers[0].reject('error0'); 26 | defers[1].reject('error1'); 27 | }, 28 | 29 | 'resulting promise should be rejected if argument is empty array' : function(test) { 30 | Vow.any([]).then(null, function() { 31 | test.done(); 32 | }); 33 | }, 34 | 35 | 'resulting defer should be notified if argument if any defer notified' : function(test) { 36 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()], 37 | i = 0; 38 | 39 | Vow.any(defersToPromises(defers)).progress(function(val) { 40 | test.equal(val, i); 41 | (++i === defers.length) && test.done(); 42 | }); 43 | 44 | defers.forEach(function(defer, i) { 45 | defer.notify(i); 46 | }); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /test/vow.anyResolved.js: -------------------------------------------------------------------------------- 1 | var defersToPromises = require('./utils/helpers').defersToPromises; 2 | 3 | module.exports = { 4 | 'resulting promise should be fulfilled after any item fulfilled' : function(test) { 5 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()]; 6 | 7 | Vow.anyResolved(defersToPromises(defers)).then(function(val) { 8 | test.strictEqual(val, 'val'); 9 | test.done(); 10 | }); 11 | 12 | defers[1].resolve('val'); 13 | }, 14 | 15 | 'resulting promise should be rejected after the first item is rejected' : function(test) { 16 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()]; 17 | 18 | Vow.anyResolved(defersToPromises(defers)).then(null, function(error) { 19 | test.strictEqual(error, 'error'); 20 | test.done(); 21 | }); 22 | 23 | defers[1].reject('error'); 24 | }, 25 | 26 | 'resulting promise should be rejected if argument is empty array' : function(test) { 27 | Vow.anyResolved([]).then(null, function() { 28 | test.done(); 29 | }); 30 | }, 31 | 32 | 'resulting defer should be notified if argument if any defer notified' : function(test) { 33 | var defers = [Vow.defer(), Vow.defer(), Vow.defer()], 34 | i = 0; 35 | 36 | Vow.anyResolved(defersToPromises(defers)).progress(function(val) { 37 | test.equal(val, i); 38 | (++i === defers.length) && test.done(); 39 | }); 40 | 41 | defers.forEach(function(defer, i) { 42 | defer.notify(i); 43 | }); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /test/vow.cast.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'should return given value if value is a promise-like object' : function(test) { 3 | var promise = Vow.defer().promise(); 4 | test.strictEqual(Vow.cast(promise), promise); 5 | test.done(); 6 | }, 7 | 8 | 'should return new promise if givena value is not a promise-like object' : function(test) { 9 | test.ok(Vow.isPromise(Vow.cast(undefined))); 10 | test.ok(Vow.isPromise(Vow.cast('val'))); 11 | test.ok(Vow.isPromise(Vow.cast(5))); 12 | test.ok(Vow.isPromise(Vow.cast(true))); 13 | test.ok(Vow.isPromise(Vow.cast({}))); 14 | test.ok(Vow.isPromise(Vow.cast(null))); 15 | test.ok(Vow.isPromise(Vow.cast(function() {}))); 16 | test.done(); 17 | } 18 | }; -------------------------------------------------------------------------------- /test/vow.delay.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'resulting promise should be fulfilled with first argument if argument is not a promise' : function(test) { 3 | var resPromise = Vow.delay('ok', 10); 4 | setTimeout(function() { 5 | test.ok(resPromise.isFulfilled()); 6 | test.ok(resPromise.valueOf('ok')); 7 | test.done(); 8 | }, 20); 9 | } 10 | }; -------------------------------------------------------------------------------- /test/vow.done.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'exception should be throwed if argument is a promise' : function(test) { 3 | var defer = Vow.defer(), 4 | e = Error(); 5 | 6 | defer.reject(e); 7 | Vow.done(defer.promise()); 8 | 9 | process.once('uncaughtException', function(_e) { 10 | test.strictEqual(_e, e); 11 | test.done(); 12 | }); 13 | }, 14 | 15 | 'nothing to be happen if argument is not a promise' : function(test) { 16 | Vow.done('val'); 17 | test.done(); 18 | } 19 | }; -------------------------------------------------------------------------------- /test/vow.fail.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'onRejected callback should be called when argument rejected' : function(test) { 3 | var defer = Vow.defer(); 4 | 5 | Vow.fail(defer.promise(), function(error) { 6 | test.strictEqual(error, 'err'); 7 | test.done(); 8 | }); 9 | 10 | defer.reject('err'); 11 | } 12 | }; -------------------------------------------------------------------------------- /test/vow.fulfill.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'resulting promise should be immediately fulfilled if argument is not a promise' : function(test) { 3 | var promise = Vow.fulfill('val'); 4 | test.ok(promise.isFulfilled()); 5 | test.done(); 6 | }, 7 | 8 | 'resulting promise should be fulfilled with argument if argument is not a promise' : function(test) { 9 | Vow.fulfill('val').then(function(val) { 10 | test.strictEqual(val, 'val'); 11 | test.done(); 12 | }); 13 | }, 14 | 15 | 'resulting promise should be fulfilled if argument is fulfilled' : function(test) { 16 | var defer = Vow.defer(); 17 | 18 | Vow.fulfill(defer.promise()).then(function(val) { 19 | test.strictEqual(val, 'val'); 20 | test.done(); 21 | }); 22 | 23 | defer.resolve('val'); 24 | }, 25 | 26 | 'resulting promise should be fulfilled if argument is rejected' : function(test) { 27 | var defer = Vow.defer(); 28 | 29 | Vow.fulfill(defer.promise()).then(function(val) { 30 | test.strictEqual(val, 'error'); 31 | test.done(); 32 | }); 33 | 34 | defer.reject('error'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /test/vow.invoke.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'function should be called without arguments' : function(test) { 3 | Vow.invoke(function() { 4 | test.strictEqual(arguments.length, 0); 5 | test.done(); 6 | }); 7 | }, 8 | 9 | 'function should be called with passed arguments' : function(test) { 10 | Vow.invoke(function(arg1, arg2, arg3) { 11 | test.strictEqual(arg1, 1); 12 | test.strictEqual(arg2, '2'); 13 | test.strictEqual(arg3, true); 14 | test.done(); 15 | }, 1, '2', true); 16 | }, 17 | 18 | 'resulting promise should be fulfilled with function result' : function(test) { 19 | Vow.invoke(function() { return 'ok'; }).then(function(res) { 20 | test.strictEqual(res, 'ok'); 21 | test.done(); 22 | }); 23 | }, 24 | 25 | 'resulting promise should be rejected if function throw exception' : function(test) { 26 | var err = Error(); 27 | Vow.invoke(function() { throw err; }).fail(function(_err) { 28 | test.strictEqual(_err, err); 29 | test.done(); 30 | }); 31 | }, 32 | 33 | 'if function return promise then result should adopt it state' : function(test) { 34 | var defer = Vow.defer(); 35 | Vow.invoke(function() { return defer.promise(); }).then(function(val) { 36 | test.strictEqual(val, 'ok'); 37 | test.done(); 38 | }); 39 | defer.resolve('ok') 40 | } 41 | }; -------------------------------------------------------------------------------- /test/vow.isFulfilled.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'should return promise state if argument is promise' : function(test) { 3 | var pendingPromise = Vow.defer().promise(); 4 | test.ok(Vow.isFulfilled(pendingPromise) === pendingPromise.isFulfilled()); 5 | 6 | var fulfilledPromise = Vow.fulfill('val'); 7 | test.ok(Vow.isFulfilled(fulfilledPromise) === fulfilledPromise.isFulfilled()); 8 | 9 | var rejectedPromise = Vow.reject('error'); 10 | test.ok(Vow.isRejected(rejectedPromise) === rejectedPromise.isRejected()); 11 | 12 | test.done(); 13 | }, 14 | 15 | 'should be true if argument is non-promise' : function(test) { 16 | test.ok(Vow.isFulfilled('val')); 17 | test.done(); 18 | } 19 | }; -------------------------------------------------------------------------------- /test/vow.isPromise.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'should be true if argument is promise' : function(test) { 3 | test.ok(Vow.isPromise(new Vow.Promise())); 4 | test.done(); 5 | }, 6 | 7 | 'should be true if argument is thenable object' : function(test) { 8 | test.ok(Vow.isPromise({ then : function() { } })); 9 | test.done(); 10 | }, 11 | 12 | 'should be false if argument is non-promise' : function(test) { 13 | test.ok(!Vow.isPromise(undefined)); 14 | test.ok(!Vow.isPromise('val')); 15 | test.ok(!Vow.isPromise(5)); 16 | test.ok(!Vow.isPromise(true)); 17 | test.ok(!Vow.isPromise({})); 18 | test.ok(!Vow.isPromise(null)); 19 | test.ok(!Vow.isPromise(function() {})); 20 | test.done(); 21 | } 22 | }; -------------------------------------------------------------------------------- /test/vow.isRejected.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'should return promise state if argument is promise' : function(test) { 3 | var pendingPromise = Vow.defer().promise(); 4 | test.ok(Vow.isRejected(pendingPromise) === pendingPromise.isRejected()); 5 | 6 | var fulfilledPromise = Vow.fulfill('val'); 7 | test.ok(Vow.isRejected(fulfilledPromise) === fulfilledPromise.isRejected()); 8 | 9 | var rejectedPromise = Vow.reject('error'); 10 | test.ok(Vow.isRejected(rejectedPromise) === rejectedPromise.isRejected()); 11 | 12 | test.done(); 13 | }, 14 | 15 | 'should be false if argument is non-promise' : function(test) { 16 | test.ok(!Vow.isRejected('val')); 17 | test.done(); 18 | } 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /test/vow.isResolved.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'should return promise state if argument is promise' : function(test) { 3 | var pendingPromise = Vow.defer().promise(); 4 | test.ok(Vow.isResolved(pendingPromise) === pendingPromise.isResolved()); 5 | 6 | var fulfilledPromise = Vow.fulfill('val'); 7 | test.ok(Vow.isResolved(fulfilledPromise) === fulfilledPromise.isResolved()); 8 | 9 | var rejectedPromise = Vow.reject('error'); 10 | test.ok(Vow.isResolved(rejectedPromise) === rejectedPromise.isResolved()); 11 | 12 | test.done(); 13 | }, 14 | 15 | 'should be true if argument is non-promise' : function(test) { 16 | test.ok(Vow.isResolved('val')); 17 | test.done(); 18 | } 19 | }; -------------------------------------------------------------------------------- /test/vow.promise.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'promise should be fulfilled with passed value' : function(test) { 3 | new Vow 4 | .Promise(function(resolve) { 5 | resolve('val'); 6 | }) 7 | .then(function(val) { 8 | test.strictEqual(val, 'val'); 9 | test.done(); 10 | }); 11 | }, 12 | 13 | 'promise should be rejected with passed value' : function(test) { 14 | new Vow 15 | .Promise(function(_, reject) { 16 | reject('error'); 17 | }) 18 | .fail(function(val) { 19 | test.strictEqual(val, 'error'); 20 | test.done(); 21 | }); 22 | }, 23 | 24 | 'promise should be rejected if an error is thrown in the resolver function' : function(test) { 25 | var _error = new Error(); 26 | new Vow 27 | .Promise(function() { 28 | throw _error; 29 | }) 30 | .fail(function(error) { 31 | test.strictEqual(error, _error); 32 | test.done(); 33 | }); 34 | }, 35 | 36 | 'promise should be notified with passed value' : function(test) { 37 | var _notify; 38 | new Vow 39 | .Promise(function(_, _, notify) { 40 | _notify = notify; 41 | }) 42 | .progress(function(val) { 43 | test.strictEqual(val, 'val'); 44 | test.done(); 45 | }); 46 | _notify('val'); 47 | } 48 | }; -------------------------------------------------------------------------------- /test/vow.reject.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'resulting promise should be immediately rejected' : function(test) { 3 | var promise = Vow.reject('error'); 4 | test.ok(promise.isRejected()); 5 | test.done(); 6 | }, 7 | 8 | 'resulting promise should be rejected with argument if argument is not a promise' : function(test) { 9 | Vow.reject('error').fail(function(error) { 10 | test.strictEqual(error, 'error'); 11 | test.done(); 12 | }); 13 | }, 14 | 15 | 'resulting promise should be rejected if argument is rejected' : function(test) { 16 | var defer = Vow.defer(); 17 | 18 | Vow.reject(defer.promise()).fail(function(error) { 19 | test.strictEqual(error, 'error'); 20 | test.done(); 21 | }); 22 | 23 | defer.reject('error'); 24 | }, 25 | 26 | 'resulting promise should be rejected if argument is fulfilled' : function(test) { 27 | var defer = Vow.defer(); 28 | 29 | Vow.reject(defer.promise()).fail(function(error) { 30 | test.strictEqual(error, 'val'); 31 | test.done(); 32 | }); 33 | 34 | defer.resolve('val'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /test/vow.resolve.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'resulting promise should be fulfilled if argument is non-promise' : function(test) { 3 | Vow.resolve('val').then(function(val) { 4 | test.strictEqual(val, 'val'); 5 | test.done(); 6 | }); 7 | }, 8 | 9 | 'resulting promise should be fulfilled if argument is fulfilled' : function(test) { 10 | var defer = Vow.defer(); 11 | 12 | Vow.resolve(defer.promise()).then(function(val) { 13 | test.strictEqual(val, 'val'); 14 | test.done(); 15 | }); 16 | 17 | defer.resolve('val'); 18 | }, 19 | 20 | 'resulting promise should be rejected if argument is rejected' : function(test) { 21 | var defer = Vow.defer(); 22 | 23 | Vow.resolve(defer.promise()).then(null, function(val) { 24 | test.strictEqual(val, 'error'); 25 | test.done(); 26 | }); 27 | 28 | defer.reject('error'); 29 | } 30 | }; -------------------------------------------------------------------------------- /test/vow.spread.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'onFulfilled argument should be spreaded' : function(test) { 3 | Vow.spread([1, '2', true], function(arg1, arg2, arg3) { 4 | test.strictEqual(arguments.length, 3); 5 | test.strictEqual(arg1, 1); 6 | test.strictEqual(arg2, '2'); 7 | test.strictEqual(arg3, true); 8 | test.done(); 9 | }); 10 | } 11 | }; -------------------------------------------------------------------------------- /test/vow.timeout.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'resulting promise should not be rejected if argument is not a promise' : function(test) { 3 | var resPromise = Vow.timeout('a', 10); 4 | setTimeout(function() { 5 | test.ok(!resPromise.isRejected()); 6 | test.done(); 7 | }, 20); 8 | } 9 | }; -------------------------------------------------------------------------------- /test/vow.when.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'onFulfilled callback should be called when argument fulfilled' : function(test) { 3 | var defer = Vow.defer(); 4 | 5 | Vow.when(defer.promise(), function(val) { 6 | test.strictEqual(val, 'val'); 7 | test.done(); 8 | }); 9 | 10 | defer.resolve('val'); 11 | }, 12 | 13 | 'onRejected callback should be called when argument rejected' : function(test) { 14 | var defer = Vow.defer(); 15 | 16 | Vow.when(defer.promise(), null, function(error) { 17 | test.strictEqual(error, 'err'); 18 | test.done(); 19 | }); 20 | 21 | defer.reject('err'); 22 | }, 23 | 24 | 'onFulfilled callback should be called if argument is non-promise' : function(test) { 25 | Vow.when('val', function(val) { 26 | test.strictEqual(val, 'val'); 27 | test.done(); 28 | }); 29 | }, 30 | 31 | 'onProgress callback should be called when argument notified' : function(test) { 32 | var defer = Vow.defer();; 33 | 34 | Vow.when(defer.promise(), null, null, function(val) { 35 | test.strictEqual(val, 'val'); 36 | test.done(); 37 | }); 38 | 39 | defer.notify('val'); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /utils/gen-doc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var jsdoc = require('bem-jsd'), 4 | path = require('path'), 5 | fs = require('fs'), 6 | md = require('marked'), 7 | mdOptions = { 8 | highlight : function hilight(code, lang) { 9 | lang === 'js' && (lang = 'javascript'); 10 | return hljs.highlight(lang, code).value; 11 | } 12 | }, 13 | jspath = require('jspath'), 14 | hljs = require('highlight.js'), 15 | yate = require('yate'), 16 | processMarkdown = function(json) { 17 | jspath('..*{.description}', json).forEach(function(obj) { 18 | obj.description = md(obj.description, mdOptions); 19 | }); 20 | 21 | jspath('..*{.examples}', json).forEach(function(obj) { 22 | obj.examples = obj.examples.map(function(ex) { 23 | return md(ex, mdOptions); 24 | }); 25 | }); 26 | 27 | jspath('..*{.license}', json).forEach(function(obj) { 28 | obj.license = md(obj.license, mdOptions); 29 | }); 30 | 31 | return json; 32 | }; 33 | 34 | fs.writeFileSync( 35 | process.argv[3], 36 | yate.run( 37 | path.join(__dirname, 'jsdoc2html.yate'), 38 | { 39 | data : processMarkdown(jsdoc(fs.readFileSync(process.argv[2], 'utf-8'))) 40 | })); -------------------------------------------------------------------------------- /utils/jsdoc2html.yate: -------------------------------------------------------------------------------- 1 | match / { 2 | 3 | apply . head 4 | apply . body 5 | 6 | } 7 | 8 | match / head { 9 | 10 | 11 | 12 | 13 | } 14 | 15 | match / body { 16 | 17 | apply . toolbar 18 | apply .modules 19 | apply . scripts 20 | 21 | } 22 | 23 | match / toolbar { 24 |
25 | expand all 26 | " / " 27 | collapse all 28 |
29 | } 30 | 31 | match .modules { 32 |
33 |

34 | .name 35 |

36 | apply .description 37 |
38 |
version
39 |
40 | .version 41 |
42 |
43 |
44 |
author
45 |
46 | .author.name 47 | " " 48 | 49 | .author.email 50 | 51 |
52 |
53 |
54 |
license
55 |
56 | apply .license 57 |
58 |
59 |
60 |
exports
61 |
62 | apply .exports 63 |
64 |
65 |
66 | } 67 | 68 | match .*[.jsdocType == "type" && .jsType == "Object"] { 69 |
70 | Object 71 | "{{" 72 | apply .props 73 | "}}" 74 |
75 | } 76 | 77 | match .props { 78 |
79 |
80 | .key 81 |
82 |
83 | apply .val 84 |
85 |
86 | } 87 | 88 | match .*[.jsdocType == "type" && .jsType == "Function"] { 89 | has-details = boolean(.description | .params | .returns | .example) 90 |
91 | if has-details { 92 | @class += " details" 93 | } 94 | 95 | if has-details { 96 | @class += " details__opener" 97 | } 98 | "Function" 99 | 100 | " (" 101 | if .params { 102 | 103 | apply .params list 104 | 105 | } 106 | ")" 107 | if .returns { 108 | " → " 109 | 110 | .returns.jsType 111 | 112 | } 113 | if has-details { 114 |
115 | apply .description 116 | if .params { 117 |
118 |
arguments
119 |
120 | apply .params table 121 |
122 |
123 | } 124 | apply .returns 125 | apply . examples 126 |
127 | } 128 |
129 | } 130 | 131 | match .params list { 132 | 133 | if .isOptional { @class += " params__param_optional" } 134 | if index() > 0 { ", " } 135 | .name 136 | 137 | } 138 | 139 | match .params table { 140 |
141 | if .isOptional { @class += " params__param_optional" } 142 |
143 | .name 144 | if .isOptional { 145 | optional 146 | } 147 |
148 |
149 |
150 | apply .jsType 151 |
152 | apply .description 153 |
154 |
155 | } 156 | 157 | match .description | .examples | .license { 158 |
159 | html(.) 160 |
161 | } 162 | 163 | match .returns { 164 |
165 |
returns
166 |
167 |
168 | apply .jsType 169 |
170 | apply .description 171 |
172 |
173 | } 174 | 175 | match .*[.jsdocType == "class"] { 176 |
177 | Class 178 |
179 | apply .description 180 | apply .cons cons 181 | apply .proto proto 182 | apply .static static 183 |
184 |
185 | } 186 | 187 | match .cons cons { 188 |
189 |
constructor
190 |
191 | apply . 192 |
193 |
194 | } 195 | 196 | match .proto proto { 197 |
198 |
instance methods
199 |
200 | apply .props 201 |
202 |
203 | } 204 | 205 | match .static static { 206 |
207 |
class methods
208 |
209 | apply .props 210 |
211 |
212 | } 213 | 214 | match .jsType { 215 | if index() > 0 { " | " } 216 | . 217 | } 218 | 219 | match .*[.examples] examples { 220 |
221 |
222 | "example" 223 | if count(.examples) > 1 { 224 | "s" 225 | } 226 |
227 |
228 | apply .examples 229 |
230 |
231 | } 232 | 233 | match / scripts { 234 | 235 | 236 | 237 | } 238 | -------------------------------------------------------------------------------- /utils/update-gh-pages.sh: -------------------------------------------------------------------------------- 1 | if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 2 | echo "gh-pages is updating..." 3 | 4 | git config --global user.email "travis@travis-ci.org" 5 | git config --global user.name "Travis" 6 | 7 | git clone -b gh-pages https://${GH_TOKEN}@github.com/dfilatov/vow.git gh-pages 8 | ./utils/gen-doc.js lib/vow.js gh-pages/index.html 9 | cd gh-pages 10 | git add -A 11 | git commit -m "Travis build $TRAVIS_BUILD_NUMBER has been pushed to gh-pages" 12 | git push origin gh-pages 13 | cd .. 14 | rm -rf gh-pages 15 | 16 | echo "gh-pages has been updated successfully" 17 | fi -------------------------------------------------------------------------------- /vow.min.js: -------------------------------------------------------------------------------- 1 | !function(e){var t=function(){var t=[],n=function(e){return t.push(e),1===t.length},r=function(){var e=t,n=0,r=t.length;for(t=[];n1?function(e){t.isResolved()||t._reject(e)}:void 0,n>2?function(e){t.isResolved()||t._notify(e)}:void 0)}catch(e){this._reject(e)}}};_.prototype={valueOf:function(){return this._value},isResolved:function(){return this._status!==h},isFulfilled:function(){return this._status===v},isRejected:function(){return this._status===p},then:function(e,t,n,r){this._shouldEmitUnhandledRejection=!1;var i=new f;return this._addCallbacks(i,e,t,n,r),i.promise()},catch:function(e,t){return this.then(void 0,e,t)},fail:function(e,t){return this.then(void 0,e,t)},always:function(e,t){var n=this,r=function(){return e.call(this,n)};return this.then(r,r,t)},finally:function(e,t){var n=this,r=function(){return e.call(this)};return this.then(r,r,t).then(function(){return n})},progress:function(e,t){return this.then(void 0,void 0,e,t)},spread:function(e,t,n){return this.then(function(t){return e.apply(this,t)},t,n)},done:function(e,t,r,i){this.then(e,t,r,i).fail(n)},delay:function(e){var t,n=this.then(function(n){var r=new f;return t=setTimeout(function(){r.resolve(n)},e),r.promise()});return n.always(function(){clearTimeout(t)}),n},timeout:function(e){var t=new f,n=setTimeout(function(){t.reject(new b.TimedOutError("timed out"))},e);return this.then(function(e){t.resolve(e)},function(e){t.reject(e)}),t.promise().always(function(){clearTimeout(n)}),t.promise()},_vow:!0,_resolve:function(e){if(!(this._status>d))if(e!==this)if(this._status=d,e&&e._vow)e.isFulfilled()?this._fulfill(e.valueOf()):e.isRejected()?(e._shouldEmitUnhandledRejection=!1,this._reject(e.valueOf())):e.then(this._fulfill,this._reject,this._notify,this);else{if(i(e)||r(e)){var t;try{t=e.then}catch(e){return void this._reject(e)}if(r(t)){var n=this,o=!1;try{t.call(e,function(e){o||(o=!0,n._resolve(e))},function(e){o||(o=!0,n._reject(e))},function(e){n._notify(e)})}catch(e){o||this._reject(e)}return}}this._fulfill(e)}else this._reject(TypeError("Can't resolve promise with itself"))},_fulfill:function(e){this._status>d||(this._status=v,this._value=e,this._callCallbacks(this._fulfilledCallbacks,e),this._fulfilledCallbacks=this._rejectedCallbacks=this._progressCallbacks=void 0)},_reject:function(e){if(!(this._status>d)){if(this._status=p,this._value=e,this._callCallbacks(this._rejectedCallbacks,e),!this._rejectedCallbacks.length){var n=this;t(function(){n._shouldEmitUnhandledRejection&&a(e,n)})}this._fulfilledCallbacks=this._rejectedCallbacks=this._progressCallbacks=void 0}},_notify:function(e){this._callCallbacks(this._progressCallbacks,e)},_addCallbacks:function(e,t,n,i,o){var s;n&&!r(n)?(o=n,n=void 0):i&&!r(i)&&(o=i,i=void 0),n&&(this._shouldEmitUnhandledRejection=!1),this.isRejected()||(s={defer:e,fn:r(t)?t:void 0,ctx:o},this.isFulfilled()?this._callCallbacks([s],this._value):this._fulfilledCallbacks.push(s)),this.isFulfilled()||(s={defer:e,fn:n,ctx:o},this.isRejected()?this._callCallbacks([s],this._value):this._rejectedCallbacks.push(s)),this._status<=d&&this._progressCallbacks.push({defer:e,fn:i,ctx:o})},_callCallbacks:function(e,n){var r=e.length;if(r){this.isResolved();var i=this.isFulfilled(),o=this.isRejected();t(function(){for(var t,s,l,u=0;u