├── .gitattributes ├── .gitignore ├── media └── logo.png ├── .travis.yml ├── .editorconfig ├── appveyor.yml ├── package.json ├── license ├── readme.md ├── index.js └── test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/floatdrop/pinkie/HEAD/media/logo.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'stable' 5 | - '4' 6 | - 'iojs' 7 | - '0.12' 8 | - '0.10' 9 | after_success: npm run coverage 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '5' 4 | - nodejs_version: '4' 5 | - nodejs_version: '0.12' 6 | - nodejs_version: '0.10' 7 | install: 8 | - ps: Install-Product node $env:nodejs_version 9 | - set CI=true 10 | - npm -g install npm@latest || (timeout 30 && npm -g install npm@latest) 11 | - set PATH=%APPDATA%\npm;%PATH% 12 | - npm install || (timeout 30 && npm install) 13 | matrix: 14 | fast_finish: true 15 | build: off 16 | version: '{build}' 17 | shallow_clone: true 18 | clone_depth: 1 19 | test_script: 20 | - node --version 21 | - npm --version 22 | - npm run test || (timeout 30 && npm run test) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pinkie", 3 | "version": "2.0.4", 4 | "description": "Itty bitty little widdle twinkie pinkie ES2015 Promise implementation", 5 | "license": "MIT", 6 | "repository": "floatdrop/pinkie", 7 | "author": { 8 | "name": "Vsevolod Strukchinsky", 9 | "email": "floatdrop@gmail.com", 10 | "url": "github.com/floatdrop" 11 | }, 12 | "engines": { 13 | "node": ">=0.10.0" 14 | }, 15 | "scripts": { 16 | "test": "xo && nyc mocha", 17 | "coverage": "nyc report --reporter=text-lcov | coveralls" 18 | }, 19 | "files": [ 20 | "index.js" 21 | ], 22 | "keywords": [ 23 | "promise", 24 | "promises", 25 | "es2015", 26 | "es6" 27 | ], 28 | "devDependencies": { 29 | "core-assert": "^0.1.1", 30 | "coveralls": "^2.11.4", 31 | "mocha": "*", 32 | "nyc": "^3.2.2", 33 | "promises-aplus-tests": "*", 34 | "xo": "^0.10.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | pinkie 4 |
5 |
6 |

7 | 8 | > Itty bitty little widdle twinkie pinkie [ES2015 Promise](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects) implementation 9 | 10 | [![Build Status](https://travis-ci.org/floatdrop/pinkie.svg?branch=master)](https://travis-ci.org/floatdrop/pinkie) [![Coverage Status](https://coveralls.io/repos/floatdrop/pinkie/badge.svg?branch=master&service=github)](https://coveralls.io/github/floatdrop/pinkie?branch=master) 11 | 12 | There are [tons of Promise implementations](https://github.com/promises-aplus/promises-spec/blob/master/implementations.md#standalone) out there, but all of them focus on browser compatibility and are often bloated with functionality. 13 | 14 | This module is an exact Promise specification polyfill (like [native-promise-only](https://github.com/getify/native-promise-only)), but in Node.js land (it should be browserify-able though). 15 | 16 | 17 | ## Install 18 | 19 | ``` 20 | $ npm install --save pinkie 21 | ``` 22 | 23 | 24 | ## Usage 25 | 26 | ```js 27 | var fs = require('fs'); 28 | var Promise = require('pinkie'); 29 | 30 | new Promise(function (resolve, reject) { 31 | fs.readFile('foo.json', 'utf8', function (err, data) { 32 | if (err) { 33 | reject(err); 34 | return; 35 | } 36 | 37 | resolve(data); 38 | }); 39 | }); 40 | //=> Promise 41 | ``` 42 | 43 | 44 | ### API 45 | 46 | `pinkie` exports bare [ES2015 Promise](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects) implementation and polyfills [Node.js rejection events](https://nodejs.org/api/process.html#process_event_unhandledrejection). In case you forgot: 47 | 48 | #### new Promise(executor) 49 | 50 | Returns new instance of `Promise`. 51 | 52 | ##### executor 53 | 54 | *Required* 55 | Type: `function` 56 | 57 | Function with two arguments `resolve` and `reject`. The first argument fulfills the promise, the second argument rejects it. 58 | 59 | #### pinkie.all(promises) 60 | 61 | Returns a promise that resolves when all of the promises in the `promises` Array argument have resolved. 62 | 63 | #### pinkie.race(promises) 64 | 65 | Returns a promise that resolves or rejects as soon as one of the promises in the `promises` Array resolves or rejects, with the value or reason from that promise. 66 | 67 | #### pinkie.reject(reason) 68 | 69 | Returns a Promise object that is rejected with the given `reason`. 70 | 71 | #### pinkie.resolve(value) 72 | 73 | Returns a Promise object that is resolved with the given `value`. If the `value` is a thenable (i.e. has a then method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the `value`. 74 | 75 | 76 | ## Related 77 | 78 | - [pinkie-promise](https://github.com/floatdrop/pinkie-promise) - Returns the native Promise or this module 79 | 80 | 81 | ## License 82 | 83 | MIT © [Vsevolod Strukchinsky](http://github.com/floatdrop) 84 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PENDING = 'pending'; 4 | var SETTLED = 'settled'; 5 | var FULFILLED = 'fulfilled'; 6 | var REJECTED = 'rejected'; 7 | var NOOP = function () {}; 8 | var isNode = typeof global !== 'undefined' && typeof global.process !== 'undefined' && typeof global.process.emit === 'function'; 9 | 10 | var asyncSetTimer = typeof setImmediate === 'undefined' ? setTimeout : setImmediate; 11 | var asyncQueue = []; 12 | var asyncTimer; 13 | 14 | function asyncFlush() { 15 | // run promise callbacks 16 | for (var i = 0; i < asyncQueue.length; i++) { 17 | asyncQueue[i][0](asyncQueue[i][1]); 18 | } 19 | 20 | // reset async asyncQueue 21 | asyncQueue = []; 22 | asyncTimer = false; 23 | } 24 | 25 | function asyncCall(callback, arg) { 26 | asyncQueue.push([callback, arg]); 27 | 28 | if (!asyncTimer) { 29 | asyncTimer = true; 30 | asyncSetTimer(asyncFlush, 0); 31 | } 32 | } 33 | 34 | function invokeResolver(resolver, promise) { 35 | function resolvePromise(value) { 36 | resolve(promise, value); 37 | } 38 | 39 | function rejectPromise(reason) { 40 | reject(promise, reason); 41 | } 42 | 43 | try { 44 | resolver(resolvePromise, rejectPromise); 45 | } catch (e) { 46 | rejectPromise(e); 47 | } 48 | } 49 | 50 | function invokeCallback(subscriber) { 51 | var owner = subscriber.owner; 52 | var settled = owner._state; 53 | var value = owner._data; 54 | var callback = subscriber[settled]; 55 | var promise = subscriber.then; 56 | 57 | if (typeof callback === 'function') { 58 | settled = FULFILLED; 59 | try { 60 | value = callback(value); 61 | } catch (e) { 62 | reject(promise, e); 63 | } 64 | } 65 | 66 | if (!handleThenable(promise, value)) { 67 | if (settled === FULFILLED) { 68 | resolve(promise, value); 69 | } 70 | 71 | if (settled === REJECTED) { 72 | reject(promise, value); 73 | } 74 | } 75 | } 76 | 77 | function handleThenable(promise, value) { 78 | var resolved; 79 | 80 | try { 81 | if (promise === value) { 82 | throw new TypeError('A promises callback cannot return that same promise.'); 83 | } 84 | 85 | if (value && (typeof value === 'function' || typeof value === 'object')) { 86 | // then should be retrieved only once 87 | var then = value.then; 88 | 89 | if (typeof then === 'function') { 90 | then.call(value, function (val) { 91 | if (!resolved) { 92 | resolved = true; 93 | 94 | if (value === val) { 95 | fulfill(promise, val); 96 | } else { 97 | resolve(promise, val); 98 | } 99 | } 100 | }, function (reason) { 101 | if (!resolved) { 102 | resolved = true; 103 | 104 | reject(promise, reason); 105 | } 106 | }); 107 | 108 | return true; 109 | } 110 | } 111 | } catch (e) { 112 | if (!resolved) { 113 | reject(promise, e); 114 | } 115 | 116 | return true; 117 | } 118 | 119 | return false; 120 | } 121 | 122 | function resolve(promise, value) { 123 | if (promise === value || !handleThenable(promise, value)) { 124 | fulfill(promise, value); 125 | } 126 | } 127 | 128 | function fulfill(promise, value) { 129 | if (promise._state === PENDING) { 130 | promise._state = SETTLED; 131 | promise._data = value; 132 | 133 | asyncCall(publishFulfillment, promise); 134 | } 135 | } 136 | 137 | function reject(promise, reason) { 138 | if (promise._state === PENDING) { 139 | promise._state = SETTLED; 140 | promise._data = reason; 141 | 142 | asyncCall(publishRejection, promise); 143 | } 144 | } 145 | 146 | function publish(promise) { 147 | promise._then = promise._then.forEach(invokeCallback); 148 | } 149 | 150 | function publishFulfillment(promise) { 151 | promise._state = FULFILLED; 152 | publish(promise); 153 | } 154 | 155 | function publishRejection(promise) { 156 | promise._state = REJECTED; 157 | publish(promise); 158 | if (!promise._handled && isNode) { 159 | global.process.emit('unhandledRejection', promise._data, promise); 160 | } 161 | } 162 | 163 | function notifyRejectionHandled(promise) { 164 | global.process.emit('rejectionHandled', promise); 165 | } 166 | 167 | /** 168 | * @class 169 | */ 170 | function Promise(resolver) { 171 | if (typeof resolver !== 'function') { 172 | throw new TypeError('Promise resolver ' + resolver + ' is not a function'); 173 | } 174 | 175 | if (this instanceof Promise === false) { 176 | throw new TypeError('Failed to construct \'Promise\': Please use the \'new\' operator, this object constructor cannot be called as a function.'); 177 | } 178 | 179 | this._then = []; 180 | 181 | invokeResolver(resolver, this); 182 | } 183 | 184 | Promise.prototype = { 185 | constructor: Promise, 186 | 187 | _state: PENDING, 188 | _then: null, 189 | _data: undefined, 190 | _handled: false, 191 | 192 | then: function (onFulfillment, onRejection) { 193 | var subscriber = { 194 | owner: this, 195 | then: new this.constructor(NOOP), 196 | fulfilled: onFulfillment, 197 | rejected: onRejection 198 | }; 199 | 200 | if ((onRejection || onFulfillment) && !this._handled) { 201 | this._handled = true; 202 | if (this._state === REJECTED && isNode) { 203 | asyncCall(notifyRejectionHandled, this); 204 | } 205 | } 206 | 207 | if (this._state === FULFILLED || this._state === REJECTED) { 208 | // already resolved, call callback async 209 | asyncCall(invokeCallback, subscriber); 210 | } else { 211 | // subscribe 212 | this._then.push(subscriber); 213 | } 214 | 215 | return subscriber.then; 216 | }, 217 | 218 | catch: function (onRejection) { 219 | return this.then(null, onRejection); 220 | } 221 | }; 222 | 223 | Promise.all = function (promises) { 224 | if (!Array.isArray(promises)) { 225 | throw new TypeError('You must pass an array to Promise.all().'); 226 | } 227 | 228 | return new Promise(function (resolve, reject) { 229 | var results = []; 230 | var remaining = 0; 231 | 232 | function resolver(index) { 233 | remaining++; 234 | return function (value) { 235 | results[index] = value; 236 | if (!--remaining) { 237 | resolve(results); 238 | } 239 | }; 240 | } 241 | 242 | for (var i = 0, promise; i < promises.length; i++) { 243 | promise = promises[i]; 244 | 245 | if (promise && typeof promise.then === 'function') { 246 | promise.then(resolver(i), reject); 247 | } else { 248 | results[i] = promise; 249 | } 250 | } 251 | 252 | if (!remaining) { 253 | resolve(results); 254 | } 255 | }); 256 | }; 257 | 258 | Promise.race = function (promises) { 259 | if (!Array.isArray(promises)) { 260 | throw new TypeError('You must pass an array to Promise.race().'); 261 | } 262 | 263 | return new Promise(function (resolve, reject) { 264 | for (var i = 0, promise; i < promises.length; i++) { 265 | promise = promises[i]; 266 | 267 | if (promise && typeof promise.then === 'function') { 268 | promise.then(resolve, reject); 269 | } else { 270 | resolve(promise); 271 | } 272 | } 273 | }); 274 | }; 275 | 276 | Promise.resolve = function (value) { 277 | if (value && typeof value === 'object' && value.constructor === Promise) { 278 | return value; 279 | } 280 | 281 | return new Promise(function (resolve) { 282 | resolve(value); 283 | }); 284 | }; 285 | 286 | Promise.reject = function (reason) { 287 | return new Promise(function (resolve, reject) { 288 | reject(reason); 289 | }); 290 | }; 291 | 292 | module.exports = Promise; 293 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach, afterEach*/ 2 | 3 | 'use strict'; 4 | 5 | var assert = require('core-assert'); 6 | var Promise = require('./'); 7 | 8 | describe('Promise', function () { 9 | it('should throw without new', function () { 10 | assert.throws(function () { 11 | /* eslint-disable new-cap */ 12 | var promise = Promise(function () {}); 13 | /* eslint-enable new-cap */ 14 | assert.ok(promise); 15 | }, /Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function\./); 16 | }); 17 | 18 | it('should throw on invalid resolver type', function () { 19 | assert.throws(function () { 20 | var promise = new Promise('unicorns'); 21 | assert.ok(promise); 22 | }, /Promise resolver unicorns is not a function/); 23 | }); 24 | 25 | it('should reject on exception in resolver', function (done) { 26 | new Promise(function () { 27 | throw new Error('Bang!'); 28 | }) 29 | .catch(function (err) { 30 | assert.equal(err.message, 'Bang!'); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('should reject on exception in then', function (done) { 36 | Promise.resolve(1) 37 | .then(function () { 38 | throw new Error('Bang!'); 39 | }) 40 | .catch(function (err) { 41 | assert.equal(err.message, 'Bang!'); 42 | done(); 43 | }); 44 | }); 45 | 46 | it('should return Promise from resolve value', function (done) { 47 | Promise.resolve(Promise.resolve(1)) 48 | .then(function (value) { 49 | assert.equal(value, 1); 50 | done(); 51 | }); 52 | }); 53 | 54 | // Is it really so? Seems like a bug 55 | it('should resolve thenable in resolve', function (done) { 56 | var thenable = { 57 | then: function (cb) { 58 | cb(thenable); 59 | } 60 | }; 61 | 62 | Promise.resolve(thenable).then(function (v) { 63 | assert.equal(thenable, v); 64 | done(); 65 | }); 66 | }); 67 | }); 68 | 69 | describe('Promise.all', function () { 70 | it('should throw error on invalid argument', function () { 71 | assert.throws(function () { 72 | Promise.all('unicorns'); 73 | }, /You must pass an array to Promise.all()./); 74 | }); 75 | 76 | it('should resolve empty array to empty array', function (done) { 77 | Promise.all([]).then(function (value) { 78 | assert.deepEqual(value, []); 79 | done(); 80 | }); 81 | }); 82 | 83 | it('should resolve values to array', function (done) { 84 | Promise.all([1, 2, 3]).then(function (value) { 85 | assert.deepEqual(value, [1, 2, 3]); 86 | done(); 87 | }); 88 | }); 89 | 90 | it('should resolve promises to array', function (done) { 91 | Promise.all([1, 2, 3].map(Promise.resolve)).then(function (value) { 92 | assert.deepEqual(value, [1, 2, 3]); 93 | done(); 94 | }); 95 | }); 96 | 97 | it('should pass first rejected promise to onReject', function (done) { 98 | Promise.all([Promise.resolve(1), Promise.reject(2), Promise.reject(3)]).then(function () { 99 | done('onFullfil called'); 100 | }, function (reason) { 101 | assert.deepEqual(reason, 2); 102 | done(); 103 | }); 104 | }); 105 | }); 106 | 107 | function delayedResolve() { 108 | return new Promise(function (resolve) { 109 | setTimeout(resolve, 10); 110 | }); 111 | } 112 | 113 | describe('Promise.race', function () { 114 | it('should throw error on invalid argument', function () { 115 | assert.throws(function () { 116 | Promise.race('unicorns'); 117 | }, /You must pass an array to Promise.race()./); 118 | }); 119 | 120 | it('empty array should be pending', function (done) { 121 | var p = Promise.race([]); 122 | setTimeout(function () { 123 | assert.deepEqual(p._state, 'pending'); 124 | done(); 125 | }, 5); 126 | }); 127 | 128 | it('should resolve first value', function (done) { 129 | Promise.race([1, 2, 3]).then(function (value) { 130 | assert.deepEqual(value, 1); 131 | done(); 132 | }); 133 | }); 134 | 135 | it('should resolve first promise', function (done) { 136 | Promise.race([1, 2, 3].map(Promise.resolve)).then(function (value) { 137 | assert.deepEqual(value, 1); 138 | done(); 139 | }); 140 | }); 141 | 142 | it('should pass first rejected promise to onReject', function (done) { 143 | Promise.race([delayedResolve(), delayedResolve(), Promise.reject(3)]).then(function () { 144 | done('onFullfil called'); 145 | }, function (reason) { 146 | assert.deepEqual(reason, 3); 147 | done(); 148 | }); 149 | }); 150 | }); 151 | 152 | describe('unhandledRejection/rejectionHandled events', function () { 153 | var slice = Array.prototype.slice; 154 | var events; 155 | 156 | function onUnhandledRejection(reason) { 157 | var args = slice.call(arguments); 158 | if (reason && reason.message) { 159 | args[0] = reason.message; 160 | } 161 | events.push(['unhandledRejection', args]); 162 | } 163 | 164 | function onRejectionHandled() { 165 | events.push(['rejectionHandled', slice.call(arguments)]); 166 | } 167 | 168 | beforeEach(function () { 169 | events = []; 170 | process.on('unhandledRejection', onUnhandledRejection); 171 | process.on('rejectionHandled', onRejectionHandled); 172 | }); 173 | 174 | afterEach(function () { 175 | process.removeListener('unhandledRejection', onUnhandledRejection); 176 | process.removeListener('rejectionHandled', onRejectionHandled); 177 | }); 178 | 179 | it('should emit an unhandledRejection on the next turn', function (done) { 180 | var promise = Promise.reject(new Error('next')); 181 | assert.deepEqual(events, []); 182 | nextLoop(function () { 183 | assert.deepEqual(events, [ 184 | ['unhandledRejection', ['next', promise]] 185 | ]); 186 | done(); 187 | }); 188 | }); 189 | 190 | it('should not emit any events if handled before the next turn', function (done) { 191 | var promise = Promise.reject(new Error('handled immediately after rejection')); 192 | promise.catch(noop); 193 | nextLoop(function () { 194 | assert.deepEqual(events, []); 195 | done(); 196 | }); 197 | }); 198 | 199 | it('should emit a rejectionHandled event if handledLater', function (done) { 200 | var promise = Promise.reject(new Error('eventually handled')); 201 | nextLoop(function () { 202 | promise.catch(noop); 203 | nextLoop(function () { 204 | assert.deepEqual(events, [ 205 | ['unhandledRejection', ['eventually handled', promise]], 206 | ['rejectionHandled', [promise]] 207 | ]); 208 | done(); 209 | }); 210 | }); 211 | }); 212 | 213 | it('should not emit any events when handled by a chained promise', function (done) { 214 | var promise = Promise.reject(new Error('chained')); 215 | promise 216 | .then(noop) 217 | .then(noop) 218 | .then(noop) 219 | .catch(noop); 220 | later(function () { 221 | assert.deepStrictEqual(events, []); 222 | done(); 223 | }); 224 | }); 225 | 226 | it('catch() should only emit rejectionHandled one branch of a forked promise chain at a time', function (done) { 227 | var def = deferred(); 228 | var root = def.promise; 229 | 230 | // build the first branch 231 | root.then(noop).then(noop).catch(noop); 232 | 233 | // build the second branch 234 | var b1 = root.then(noop).then(noop); 235 | 236 | def.reject(new Error('branching')); 237 | 238 | var c; 239 | 240 | later(step1); 241 | 242 | function step1() { 243 | b1.catch(noop); 244 | c = root.then(noop); 245 | later(step2); 246 | } 247 | 248 | function step2() { 249 | assert.deepStrictEqual(events, [ 250 | ['unhandledRejection', ['branching', b1]], 251 | ['rejectionHandled', [b1]], 252 | ['unhandledRejection', ['branching', c]] 253 | ]); 254 | done(); 255 | } 256 | }); 257 | 258 | function noop() {} 259 | 260 | function nextLoop(fn) { 261 | setImmediate(fn); 262 | } 263 | 264 | function later(fn) { 265 | setTimeout(fn, 40); 266 | } 267 | }); 268 | 269 | function deferred() { 270 | var resolve; 271 | var reject; 272 | var promise = new Promise(function (res, rej) { 273 | resolve = res; 274 | reject = rej; 275 | }); 276 | 277 | return { 278 | promise: promise, 279 | resolve: resolve, 280 | reject: reject 281 | }; 282 | } 283 | 284 | describe('Promises/A+ Tests', function () { 285 | var adapter = { 286 | deferred: deferred 287 | }; 288 | 289 | require('promises-aplus-tests').mocha(adapter); 290 | }); 291 | --------------------------------------------------------------------------------