├── .jsinspectrc ├── test ├── mocha.opts ├── async-test.js ├── env-test.js ├── empty-test.js ├── aplus.js ├── TaskQueue-test.js ├── shim-test.js ├── of-test.js ├── reject-test.js ├── settle-test.js ├── toString-test.js ├── chain-test.js ├── ap-test.js ├── map-test.js ├── then-test.js ├── fulfill-test.js ├── iterable-test.js ├── merge-test.js ├── coroutine-test.js ├── never-test.js ├── any-test.js ├── resolve-test.js ├── lib │ └── test-util.js ├── race-test.js ├── timeout-test.js ├── delay-test.js ├── all-test.js ├── runNode-test.js ├── or-test.js ├── fromNode-test.js ├── unhandledRejection-node.js ├── ErrorHandler-test.js ├── Promise-test.js ├── bimap-test.js ├── runPromise-test.js ├── fantasyland-laws-test.js └── inspect-test.js ├── .gitignore ├── src ├── maybeThenable.js ├── state.js ├── tryCall.js ├── TimeoutError.js ├── Action.js ├── env.js ├── delay.js ├── map.js ├── bimap.js ├── TaskQueue.js ├── Race.js ├── Any.js ├── chain.js ├── node.js ├── timeout.js ├── runPromise.js ├── async.js ├── then.js ├── Settle.js ├── Merge.js ├── coroutine.js ├── ErrorHandler.js ├── inspect.js ├── emitError.js ├── iterable.js ├── main.js └── Promise.js ├── perf ├── lib │ ├── fakes.js │ ├── fakesC.js │ ├── fakesO.js │ ├── fakes-ctx.js │ ├── dummy.js │ ├── fakesSJS-src.sjs │ ├── catcher.js │ ├── promiseSupport.js │ ├── fakemaker.js │ ├── fakesObservable.js │ ├── fakesSJS-dst.js │ ├── fakesP-ctx.js │ ├── fakesP.js │ └── timers-ctx.js ├── madeup-parallel │ ├── promises-cujojs-when.js │ ├── promises-tildeio-rsvp.js │ ├── promises-bluebird.js │ ├── promises-cujojs-when-generator.js │ ├── callbacks-baseline.js │ ├── promises-bluebird-generator.js │ ├── promises-creed.js │ ├── promises-creed-generator.js │ ├── promises-ecmascript6-native.js │ └── callbacks-caolan-async-parallel.js ├── package.json ├── bench ├── LICENSE ├── doxbee-sequential │ ├── promises-bluebird-generator.js │ ├── promises-creed-generator.js │ ├── promises-cujojs-when-generator.js │ ├── generators-tj-co.js │ ├── promises-bluebird.js │ ├── promises-tildeio-rsvp.js │ ├── promises-creed.js │ ├── promises-creed-algebraic.js │ ├── promises-cujojs-when.js │ ├── promises-ecmascript6-native.js │ ├── callbacks-baseline.js │ └── callbacks-caolan-async-waterfall.js ├── doxbee-sequential-errors │ ├── promises-bluebird-generator.js │ ├── promises-creed-generator.js │ ├── promises-bluebird.js │ ├── promises-tildeio-rsvp.js │ ├── promises-cujojs-when.js │ ├── promises-creed.js │ ├── promises-creed-algebraic.js │ ├── callbacks-caolan-async-waterfall.js │ └── callbacks-baseline.js ├── README.md └── performance.js ├── .travis.yml ├── .jscsrc ├── .eslintrc ├── rollup.config.js ├── bower.json ├── LICENSE ├── package.json └── dist └── creed.min.js /.jsinspectrc: -------------------------------------------------------------------------------- 1 | { 2 | "threshold": 35 3 | } 4 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require buba/register 2 | --reporter dot 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | experiments/ 3 | node_modules/ 4 | coverage/ 5 | perf/logs/ 6 | .nyc_output/ -------------------------------------------------------------------------------- /src/maybeThenable.js: -------------------------------------------------------------------------------- 1 | // maybeThenable :: * -> boolean 2 | export default function maybeThenable (x) { 3 | return (typeof x === 'object' || typeof x === 'function') && x !== null 4 | } 5 | -------------------------------------------------------------------------------- /perf/lib/fakes.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var fakemaker = require('./fakemaker'), 4 | f = require('./dummy'); 5 | 6 | fakemaker(f.dummy, f.dummyt, function wrap_identity(f) { return f; }); 7 | 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | env: 3 | - CXX=g++-4.8 4 | node_js: 5 | - '6' 6 | branches: 7 | only: 8 | - master 9 | - /^greenkeeper-.*$/ 10 | after_script: "npm run coverage" 11 | -------------------------------------------------------------------------------- /test/async-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import makeAsync from '../src/async' 3 | 4 | describe('makeAsync', () => { 5 | it('should make a function that invokes later', done => { 6 | const async = makeAsync(done) 7 | async() 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /src/state.js: -------------------------------------------------------------------------------- 1 | /* eslint no-multi-spaces: 0 */ 2 | export const PENDING = 1 << 0 3 | export const FULFILLED = 1 << 1 4 | export const REJECTED = 1 << 2 5 | export const SETTLED = FULFILLED | REJECTED 6 | export const NEVER = 1 << 3 7 | 8 | export const HANDLED = 1 << 4 9 | -------------------------------------------------------------------------------- /src/tryCall.js: -------------------------------------------------------------------------------- 1 | export default function tryCall (f, x, handle, promise) { 2 | let result 3 | // test if `f` (and only it) throws 4 | try { 5 | result = f(x) 6 | } catch (e) { 7 | promise._reject(e) 8 | return 9 | } // else 10 | handle(promise, result) 11 | } 12 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "crockford", 3 | "esnext": true, 4 | "disallowDanglingUnderscores": null, 5 | "validateIndentation": "\t", 6 | "requireMultipleVarDecl": null, 7 | "requireSemicolons": true, 8 | "requireSpacesInAnonymousFunctionExpression": false 9 | } 10 | -------------------------------------------------------------------------------- /test/env-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { isNode } from '../src/env' 3 | import assert from 'assert' 4 | 5 | describe('env', () => { 6 | describe('isNode', () => { 7 | it('should be boolean', () => { 8 | assert(typeof isNode === 'boolean') 9 | }) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/empty-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { Future } from '../src/Promise' 3 | import { isNever } from '../src/inspect' 4 | import assert from 'assert' 5 | 6 | describe('empty', function () { 7 | it('should return never', () => { 8 | assert(isNever(Future.empty())) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "parserOptions": { 4 | "ecmaFeatures": { 5 | "experimentalObjectRestSpread": false, 6 | "jsx": false 7 | } 8 | }, 9 | "rules": { 10 | "complexity": [2, 3], 11 | "indent": [2, "tab", {"SwitchCase": 1}], 12 | "no-tabs": 0 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/TimeoutError.js: -------------------------------------------------------------------------------- 1 | export default class TimeoutError extends Error { 2 | constructor (message) { 3 | super() 4 | this.message = message 5 | this.name = TimeoutError.name 6 | /* istanbul ignore else */ 7 | if (typeof Error.captureStackTrace === 'function') { 8 | Error.captureStackTrace(this, TimeoutError) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Action.js: -------------------------------------------------------------------------------- 1 | export default class Action { 2 | constructor (promise) { 3 | this.promise = promise 4 | } 5 | 6 | // default onFulfilled action 7 | /* istanbul ignore next */ 8 | fulfilled (p) { 9 | this.promise._become(p) 10 | } 11 | 12 | // default onRejected action 13 | rejected (p) { 14 | this.promise._become(p) 15 | return false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /perf/lib/fakesC.js: -------------------------------------------------------------------------------- 1 | 2 | var co = require('co'); 3 | 4 | var f = require('./dummy'); 5 | 6 | var makefakes = require('./fakemaker'); 7 | 8 | // Continuable versions made with co.wrap 9 | function dummyC(n) { 10 | return co.wrap(f.dummy(n)); 11 | } 12 | function dummytC(n) { 13 | return co.wrap(f.dummyt(n)); 14 | } 15 | 16 | makefakes(dummyC, dummytC, co.wrap); 17 | 18 | 19 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import buble from 'rollup-plugin-buble' 2 | const pkg = require('./package.json') 3 | 4 | export default { 5 | entry: 'src/main.js', 6 | plugins: [buble()], 7 | targets: [ 8 | { 9 | format: 'umd', 10 | moduleName: 'creed', 11 | dest: pkg['main'] 12 | }, 13 | { 14 | format: 'es', 15 | dest: pkg['jsnext:main'] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/env.js: -------------------------------------------------------------------------------- 1 | /* global process,MutationObserver,WebKitMutationObserver */ 2 | 3 | const isNode = typeof process !== 'undefined' && 4 | Object.prototype.toString.call(process) === '[object process]' 5 | 6 | /* istanbul ignore next */ 7 | const MutationObs = (typeof MutationObserver === 'function' && MutationObserver) || 8 | (typeof WebKitMutationObserver === 'function' && WebKitMutationObserver) 9 | 10 | export { isNode, MutationObs } 11 | -------------------------------------------------------------------------------- /test/aplus.js: -------------------------------------------------------------------------------- 1 | require('buba/register') 2 | var lib = require('../src/main') 3 | 4 | process.addListener('unhandledRejection', function () {}) 5 | 6 | exports.resolved = lib.resolve 7 | exports.rejected = lib.reject 8 | exports.deferred = function () { 9 | return { 10 | resolve: function (x) { this.promise._resolve(x) }, 11 | reject: function (e) { this.promise._reject(e) }, 12 | promise: lib.runPromise(noop) 13 | } 14 | } 15 | 16 | function noop () {} 17 | -------------------------------------------------------------------------------- /src/delay.js: -------------------------------------------------------------------------------- 1 | import Action from './Action' 2 | 3 | export default function (ms, p, promise) { 4 | p._runAction(new Delay(ms, promise)) 5 | return promise 6 | } 7 | 8 | class Delay extends Action { 9 | constructor (time, promise) { 10 | super(promise) 11 | this.time = time 12 | } 13 | 14 | fulfilled (p) { 15 | /* global setTimeout */ 16 | setTimeout(become, this.time, p, this.promise) 17 | } 18 | } 19 | 20 | function become (p, promise) { 21 | promise._become(p) 22 | } 23 | -------------------------------------------------------------------------------- /src/map.js: -------------------------------------------------------------------------------- 1 | import Action from './Action' 2 | import tryCall from './tryCall' 3 | 4 | export default function (f, p, promise) { 5 | p._when(new Map(f, promise)) 6 | return promise 7 | } 8 | 9 | export class Map extends Action { 10 | constructor (f, promise) { 11 | super(promise) 12 | this.f = f 13 | } 14 | 15 | fulfilled (p) { 16 | tryCall(this.f, p.value, handleMap, this.promise) 17 | } 18 | } 19 | 20 | function handleMap (promise, result) { 21 | promise._fulfill(result) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/bimap.js: -------------------------------------------------------------------------------- 1 | import { Map } from './map' 2 | import tryCall from './tryCall' 3 | 4 | export default function (r, f, p, promise) { 5 | p._when(new Bimap(r, f, promise)) 6 | return promise 7 | } 8 | 9 | class Bimap extends Map { 10 | constructor (r, f, promise) { 11 | super(f, promise) 12 | this.r = r 13 | } 14 | 15 | rejected (p) { 16 | tryCall(this.r, p.value, handleMapRejected, this.promise) 17 | } 18 | } 19 | 20 | function handleMapRejected (promise, result) { 21 | promise._reject(result) 22 | } 23 | -------------------------------------------------------------------------------- /src/TaskQueue.js: -------------------------------------------------------------------------------- 1 | import makeAsync from './async' 2 | 3 | export default class TaskQueue { 4 | constructor () { 5 | this.tasks = new Array(2 << 15) 6 | this.length = 0 7 | this.drain = makeAsync(() => this._drain()) 8 | } 9 | 10 | add (task) { 11 | if (this.length === 0) { 12 | this.drain() 13 | } 14 | 15 | this.tasks[this.length++] = task 16 | } 17 | 18 | _drain () { 19 | const q = this.tasks 20 | for (let i = 0; i < this.length; ++i) { 21 | q[i].run() 22 | q[i] = void 0 23 | } 24 | this.length = 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/TaskQueue-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import TaskQueue from '../src/TaskQueue' 3 | import assert from 'assert' 4 | 5 | describe('TaskQueue', () => { 6 | describe('add', () => { 7 | it('should add task to execute later', done => { 8 | let i = 0 9 | function inc () { 10 | i++ 11 | } 12 | 13 | function verify () { 14 | assert.equal(i, 2) 15 | done() 16 | } 17 | 18 | let t = new TaskQueue() 19 | 20 | t.add({ run: inc }) 21 | t.add({ run: inc }) 22 | t.add({ run: verify }) 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /perf/madeup-parallel/promises-cujojs-when.js: -------------------------------------------------------------------------------- 1 | global.useWhen = true; 2 | 3 | var when = require('when'); 4 | 5 | require('../lib/fakesP'); 6 | 7 | module.exports = function upload(stream, idOrPath, tag, done) { 8 | var queries = new Array(global.parallelQueries); 9 | var tx = db.begin(); 10 | 11 | for( var i = 0, len = queries.length; i < len; ++i ) { 12 | queries[i] = FileVersion.insert({index: i}).execWithin(tx); 13 | } 14 | 15 | when.all(queries).then(function() { 16 | tx.commit(); 17 | done(); 18 | }, function(err) { 19 | tx.rollback(); 20 | done(err); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /perf/madeup-parallel/promises-tildeio-rsvp.js: -------------------------------------------------------------------------------- 1 | global.useRSVP = true; 2 | 3 | var RSVP = require('rsvp'); 4 | 5 | require('../lib/fakesP'); 6 | 7 | module.exports = function upload(stream, idOrPath, tag, done) { 8 | var queries = new Array(global.parallelQueries); 9 | var tx = db.begin(); 10 | 11 | for( var i = 0, len = queries.length; i < len; ++i ) { 12 | queries[i] = FileVersion.insert({index: i}).execWithin(tx); 13 | } 14 | 15 | RSVP.all(queries).then(function() { 16 | tx.commit(); 17 | done(); 18 | }, function(err) { 19 | tx.rollback(); 20 | done(err); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/Race.js: -------------------------------------------------------------------------------- 1 | import { silenceError } from './inspect' 2 | 3 | export default class Race { 4 | constructor (never) { 5 | this.never = never 6 | } 7 | 8 | valueAt (x, i, promise) { 9 | promise._fulfill(x) 10 | } 11 | 12 | fulfillAt (p, i, promise) { 13 | promise._become(p) 14 | } 15 | 16 | rejectAt (p, i, promise) { 17 | // In the case where the result promise has been resolved 18 | // need to silence all subsequently seen rejections 19 | promise._isResolved() ? silenceError(p) : promise._become(p) 20 | } 21 | 22 | complete (total, promise) { 23 | if (total === 0) { 24 | promise._become(this.never()) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /perf/madeup-parallel/promises-bluebird.js: -------------------------------------------------------------------------------- 1 | global.useBluebird = true; 2 | global.useQ = false; 3 | var Promise = require('bluebird'); 4 | require('../lib/fakesP'); 5 | 6 | module.exports = function upload(stream, idOrPath, tag, done) { 7 | var queries = new Array(global.parallelQueries); 8 | var tx = db.begin(); 9 | 10 | for( var i = 0, len = queries.length; i < len; ++i ) { 11 | queries[i] = FileVersion.insert({index: i}).execWithin(tx); 12 | } 13 | 14 | Promise.all(queries).then(function() { 15 | tx.commit(); 16 | done(); 17 | }, function(err) { 18 | tx.rollback(); 19 | done(err); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /perf/madeup-parallel/promises-cujojs-when-generator.js: -------------------------------------------------------------------------------- 1 | var when = require('when'); 2 | var g = require('when/generator'); 3 | require('../lib/fakesP'); 4 | 5 | module.exports = g.lift(function* upload(stream, idOrPath, tag, done) { 6 | var queries = new Array(global.parallelQueries); 7 | var tx = db.begin(); 8 | 9 | for( var i = 0, len = queries.length; i < len; ++i ) { 10 | queries[i] = FileVersion.insert({index: i}).execWithin(tx); 11 | } 12 | 13 | try { 14 | yield when.all(queries); 15 | tx.commit(); 16 | done(); 17 | } 18 | catch(e) { 19 | tx.rollback(); 20 | done(e); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /src/Any.js: -------------------------------------------------------------------------------- 1 | import { silenceError } from './inspect.js' 2 | 3 | export default class Any { 4 | constructor () { 5 | this.pending = 0 6 | } 7 | 8 | valueAt (x, i, promise) { 9 | promise._fulfill(x) 10 | } 11 | 12 | fulfillAt (p, i, promise) { 13 | promise._become(p) 14 | } 15 | 16 | rejectAt (p, i, promise) { 17 | silenceError(p) 18 | this.check(this.pending - 1, promise) 19 | } 20 | 21 | complete (total, promise) { 22 | this.check(this.pending + total, promise) 23 | } 24 | 25 | check (pending, promise) { 26 | this.pending = pending 27 | if (pending === 0) { 28 | promise._reject(new RangeError('No fulfilled promises in input')) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/chain.js: -------------------------------------------------------------------------------- 1 | import Action from './Action' 2 | import tryCall from './tryCall' 3 | import maybeThenable from './maybeThenable' 4 | 5 | export default function (f, p, promise) { 6 | p._when(new Chain(f, promise)) 7 | return promise 8 | } 9 | 10 | class Chain extends Action { 11 | constructor (f, promise) { 12 | super(promise) 13 | this.f = f 14 | } 15 | 16 | fulfilled (p) { 17 | tryCall(this.f, p.value, handleChain, this.promise) 18 | } 19 | } 20 | 21 | function handleChain (promise, result) { 22 | if (!(maybeThenable(result) && typeof result.then === 'function')) { 23 | promise._reject(new TypeError('f must return a promise')) 24 | } 25 | 26 | promise._resolve(result) 27 | } 28 | -------------------------------------------------------------------------------- /src/node.js: -------------------------------------------------------------------------------- 1 | export default function runNode (f, thisArg, args, promise) { 2 | /* eslint complexity:[2,5] */ 3 | function settleNode (e, x) { 4 | if (e) { 5 | promise._reject(e) 6 | } else { 7 | promise._fulfill(x) 8 | } 9 | } 10 | 11 | switch (args.length) { 12 | case 0: 13 | f.call(thisArg, settleNode) 14 | break 15 | case 1: 16 | f.call(thisArg, args[0], settleNode) 17 | break 18 | case 2: 19 | f.call(thisArg, args[0], args[1], settleNode) 20 | break 21 | case 3: 22 | f.call(thisArg, args[0], args[1], args[2], settleNode) 23 | break 24 | default: 25 | args.push(settleNode) 26 | f.apply(thisArg, args) 27 | } 28 | 29 | return promise 30 | } 31 | -------------------------------------------------------------------------------- /perf/madeup-parallel/callbacks-baseline.js: -------------------------------------------------------------------------------- 1 | require('../lib/fakes'); 2 | 3 | module.exports = function upload(stream, idOrPath, tag, done) { 4 | var tx = db.begin(); 5 | var current = 0; 6 | var total = global.parallelQueries; 7 | 8 | function callback(err) { 9 | if( err ) { 10 | tx.rollback(); 11 | done(err); 12 | } 13 | else { 14 | current++; 15 | if( current === total ) { 16 | tx.commit(); 17 | done(); 18 | } 19 | } 20 | } 21 | 22 | for( var i = 0; i < total; ++i ) { 23 | FileVersion.insert({index: i}).execWithin(tx, callback); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /perf/madeup-parallel/promises-bluebird-generator.js: -------------------------------------------------------------------------------- 1 | global.useBluebird = true; 2 | global.useQ = false; 3 | var bluebird = require('bluebird'); 4 | require('../lib/fakesP'); 5 | 6 | module.exports = bluebird.coroutine(function* upload(stream, idOrPath, tag, done) { 7 | var queries = new Array(global.parallelQueries); 8 | var tx = db.begin(); 9 | 10 | for( var i = 0, len = queries.length; i < len; ++i ) { 11 | queries[i] = FileVersion.insert({index: i}).execWithin(tx); 12 | } 13 | 14 | try { 15 | yield bluebird.all(queries); 16 | tx.commit(); 17 | done(); 18 | } 19 | catch(e) { 20 | tx.rollback(); 21 | done(e); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/timeout.js: -------------------------------------------------------------------------------- 1 | import Action from './Action' 2 | import TimeoutError from './TimeoutError' 3 | 4 | export default function (ms, p, promise) { 5 | const timer = setTimeout(rejectOnTimeout, ms, promise) 6 | p._runAction(new Timeout(timer, promise)) 7 | return promise 8 | } 9 | 10 | class Timeout extends Action { 11 | constructor (timer, promise) { 12 | super(promise) 13 | this.timer = timer 14 | } 15 | 16 | fulfilled (p) { 17 | clearTimeout(this.timer) 18 | this.promise._become(p) 19 | } 20 | 21 | rejected (p) { 22 | clearTimeout(this.timer) 23 | return super.rejected(p) 24 | } 25 | } 26 | 27 | function rejectOnTimeout (promise) { 28 | promise._reject(new TimeoutError('promise timeout')) 29 | } 30 | -------------------------------------------------------------------------------- /src/runPromise.js: -------------------------------------------------------------------------------- 1 | export default function runPromise (f, thisArg, args, promise) { 2 | /* eslint complexity:[2,5] */ 3 | function resolve (x) { 4 | promise._resolve(x) 5 | } 6 | 7 | function reject (e) { 8 | promise._reject(e) 9 | } 10 | 11 | switch (args.length) { 12 | case 0: 13 | f.call(thisArg, resolve, reject) 14 | break 15 | case 1: 16 | f.call(thisArg, args[0], resolve, reject) 17 | break 18 | case 2: 19 | f.call(thisArg, args[0], args[1], resolve, reject) 20 | break 21 | case 3: 22 | f.call(thisArg, args[0], args[1], args[2], resolve, reject) 23 | break 24 | default: 25 | args.push(resolve, reject) 26 | f.apply(thisArg, args) 27 | } 28 | 29 | return promise 30 | } 31 | -------------------------------------------------------------------------------- /test/shim-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { shim, Promise } from '../src/main' 3 | import assert from 'assert' 4 | 5 | /* global self */ 6 | let g = typeof self !== 'undefined' ? self 7 | : typeof global !== 'undefined' ? global 8 | : undefined 9 | 10 | describe('shim', () => { 11 | it('should return pre-existing Promise', () => { 12 | let prev = g.Promise 13 | try { 14 | assert.strictEqual(shim(), prev) 15 | } finally { 16 | g.Promise = prev 17 | } 18 | }) 19 | 20 | it('should set creed Promise', () => { 21 | let prev = void 0 22 | try { 23 | prev = shim() 24 | assert.strictEqual(Promise, g.Promise) 25 | } finally { 26 | g.Promise = prev 27 | } 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /perf/madeup-parallel/promises-creed.js: -------------------------------------------------------------------------------- 1 | global.useBluebird = false; 2 | global.useQ = false; 3 | global.useWhen = false; 4 | 5 | global.useCreed = true; 6 | 7 | var creed = require('../..'); 8 | 9 | require('../lib/fakesP'); 10 | 11 | module.exports = function upload(stream, idOrPath, tag, done) { 12 | var queries = new Array(global.parallelQueries); 13 | var tx = db.begin(); 14 | 15 | for( var i = 0, len = queries.length; i < len; ++i ) { 16 | queries[i] = FileVersion.insert({index: i}).execWithin(tx); 17 | } 18 | 19 | creed.all(queries).then(function() { 20 | tx.commit(); 21 | done(); 22 | }, function(err) { 23 | tx.rollback(); 24 | done(err); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /perf/madeup-parallel/promises-creed-generator.js: -------------------------------------------------------------------------------- 1 | global.useBluebird = false; 2 | global.useQ = false; 3 | global.useWhen = false; 4 | 5 | global.useCreed = true; 6 | var creed = require('../..'); 7 | require('../lib/fakesP'); 8 | 9 | module.exports = creed.coroutine(function* upload(stream, idOrPath, tag, done) { 10 | var queries = new Array(global.parallelQueries); 11 | var tx = db.begin(); 12 | 13 | for( var i = 0, len = queries.length; i < len; ++i ) { 14 | queries[i] = FileVersion.insert({index: i}).execWithin(tx); 15 | } 16 | 17 | try { 18 | yield creed.all(queries); 19 | tx.commit(); 20 | done(); 21 | } 22 | catch(e) { 23 | tx.rollback(); 24 | done(e); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /perf/lib/fakesO.js: -------------------------------------------------------------------------------- 1 | 2 | var Rx = require('rx'); 3 | var f = require('./dummy'); 4 | 5 | var dummy1 = f.dummy(1), 6 | dummyt1 = f.dummyt(1); 7 | 8 | // Observable wrapper 9 | function dummyObsWrap(fn) { 10 | return function() { 11 | return Rx.Observable.create(function(observer) { 12 | fn(function(err, res) { 13 | if(err) 14 | return observer.onError(err); 15 | observer.onNext(res); 16 | observer.onCompleted(); 17 | }); 18 | }); 19 | } 20 | } 21 | function dummyO() { 22 | return dummyObsWrap(dummy(1)); 23 | } 24 | function dummytO() { 25 | return dummyObsWrap(dummyt(1)); 26 | } 27 | 28 | makefakes(dummyO, dummytO, dummyObsWrap); 29 | 30 | -------------------------------------------------------------------------------- /perf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-compare", 3 | "version": "0.1.1", 4 | "description": "Compare the performance and code of multiple async patterns", 5 | "main": "perf.js", 6 | "dependencies": { 7 | "async": "^2.0.0", 8 | "bluebird": "^3.4.1", 9 | "co": "^4.6.0", 10 | "optimist": "~0.6.0", 11 | "rsvp": "^3.2.1", 12 | "text-table": "~0.2.0", 13 | "when": "^3.7.7" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "keywords": [ 20 | "generators", 21 | "fibers", 22 | "promises", 23 | "callbacks", 24 | "comparison", 25 | "compare", 26 | "async" 27 | ], 28 | "author": "spion", 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /src/async.js: -------------------------------------------------------------------------------- 1 | import { isNode, MutationObs } from './env' 2 | 3 | /* global process,document */ 4 | 5 | export default function (f) { 6 | return isNode ? createNodeScheduler(f) /* istanbul ignore next */ 7 | : MutationObs ? createBrowserScheduler(f) 8 | : createFallbackScheduler(f) 9 | } 10 | 11 | /* istanbul ignore next */ 12 | function createFallbackScheduler (f) { 13 | return () => setTimeout(f, 0) 14 | } 15 | 16 | function createNodeScheduler (f) { 17 | return () => process.nextTick(f) 18 | } 19 | 20 | /* istanbul ignore next */ 21 | function createBrowserScheduler (f) { 22 | const node = document.createTextNode('') 23 | new MutationObs(f).observe(node, { characterData: true }) 24 | 25 | let i = 0 26 | return () => { node.data = (i ^= 1) } 27 | } 28 | -------------------------------------------------------------------------------- /src/then.js: -------------------------------------------------------------------------------- 1 | import Action from './Action' 2 | import tryCall from './tryCall' 3 | 4 | export default function then (f, r, p, promise) { 5 | p._when(new Then(f, r, promise)) 6 | return promise 7 | } 8 | 9 | class Then extends Action { 10 | constructor (f, r, promise) { 11 | super(promise) 12 | this.f = f 13 | this.r = r 14 | } 15 | 16 | fulfilled (p) { 17 | this.runThen(this.f, p) 18 | } 19 | 20 | rejected (p) { 21 | return this.runThen(this.r, p) 22 | } 23 | 24 | runThen (f, p) { 25 | if (typeof f !== 'function') { 26 | this.promise._become(p) 27 | return false 28 | } 29 | tryCall(f, p.value, handleThen, this.promise) 30 | return true 31 | } 32 | } 33 | 34 | function handleThen (promise, result) { 35 | promise._resolve(result) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /perf/madeup-parallel/promises-ecmascript6-native.js: -------------------------------------------------------------------------------- 1 | global.useNative = true; 2 | 3 | try { 4 | if (Promise.race.toString() !== 'function race() { [native code] }') 5 | throw 0; 6 | } catch (e) { 7 | throw new Error("No ES6 promises available"); 8 | } 9 | 10 | require('../lib/fakesP'); 11 | 12 | module.exports = function upload(stream, idOrPath, tag, done) { 13 | var queries = new Array(global.parallelQueries); 14 | var tx = db.begin(); 15 | 16 | for( var i = 0, len = queries.length; i < len; ++i ) { 17 | queries[i] = FileVersion.insert({index: i}).execWithin(tx); 18 | } 19 | 20 | Promise.all(queries).then().then(function() { 21 | tx.commit(); 22 | done(); 23 | }, function(err) { 24 | tx.rollback(); 25 | done(err); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/of-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { Promise, reject } from '../src/main' 3 | import { silenceError, getValue } from '../src/inspect' 4 | import assert from 'assert' 5 | 6 | describe('of', () => { 7 | it('should wrap value', () => { 8 | const x = {} 9 | return Promise.of(x).then(y => assert.strictEqual(x, y)) 10 | }) 11 | 12 | it('should be immediately fulfilled', () => { 13 | const x = {} 14 | assert.strictEqual(x, getValue(Promise.of(x))) 15 | }) 16 | 17 | it('should wrap promise', () => { 18 | const x = Promise.of({}) 19 | return Promise.of(x).then(y => assert.strictEqual(x, y)) 20 | }) 21 | 22 | it('should wrap rejected promise', () => { 23 | const x = reject({}) 24 | silenceError(x) 25 | return Promise.of(x).then(y => assert.strictEqual(x, y)) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /perf/madeup-parallel/callbacks-caolan-async-parallel.js: -------------------------------------------------------------------------------- 1 | require('../lib/fakes'); 2 | var async = require('async'); 3 | 4 | function fileInsertFor(i, tx) { 5 | return function(callback) { 6 | FileVersion.insert({index: i}) 7 | .execWithin(tx, callback); 8 | }; 9 | } 10 | 11 | module.exports = function upload(stream, idOrPath, tag, done) { 12 | var queries = new Array(global.parallelQueries); 13 | var tx = db.begin(); 14 | 15 | for( var i = 0, len = queries.length; i < len; ++i ) { 16 | queries[i] = fileInsertFor(i, tx); 17 | } 18 | 19 | async.parallel(queries, function(err, callback) { 20 | if (err) { 21 | tx.rollback(); 22 | done(err); 23 | } 24 | else { 25 | tx.commit(); 26 | done(); 27 | } 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/Settle.js: -------------------------------------------------------------------------------- 1 | import { silenceError } from './inspect' 2 | 3 | export default class Settle { 4 | constructor (resolve, results) { 5 | this.pending = 0 6 | this.results = results 7 | this.resolve = resolve 8 | } 9 | 10 | valueAt (x, i, promise) { 11 | this.settleAt(this.resolve(x), i, promise) 12 | } 13 | 14 | fulfillAt (p, i, promise) { 15 | this.settleAt(p, i, promise) 16 | } 17 | 18 | rejectAt (p, i, promise) { 19 | silenceError(p) 20 | this.settleAt(p, i, promise) 21 | } 22 | 23 | settleAt (p, i, promise) { 24 | this.results[i] = p 25 | this.check(this.pending - 1, promise) 26 | } 27 | 28 | complete (total, promise) { 29 | this.check(this.pending + total, promise) 30 | } 31 | 32 | check (pending, promise) { 33 | this.pending = pending 34 | if (pending === 0) { 35 | promise._fulfill(this.results) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "creed", 3 | "homepage": "https://github.com/briancavalier/creed", 4 | "authors": [ 5 | "Brian Cavalier " 6 | ], 7 | "license": "MIT", 8 | "description": "Sophisticated and functionally-minded async with advanced features: coroutines, promises, ES2015 iterables, fantasy-land", 9 | "main": "dist/creed.js", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/briancavalier/creed.git" 13 | }, 14 | "moduleType": [ 15 | "amd", 16 | "globals", 17 | "node" 18 | ], 19 | "keywords": [ 20 | "promise", 21 | "promises", 22 | "promises/a+", 23 | "promises-aplus", 24 | "async" 25 | ], 26 | "ignore": [ 27 | "**/.*", 28 | "package.json", 29 | "src", 30 | "test", 31 | "node_modules", 32 | "bower_components" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /test/reject-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { fulfill, reject } from '../src/main' 3 | import { silenceError } from '../src/inspect' 4 | import assert from 'assert' 5 | 6 | describe('reject', () => { 7 | it('then should be identity without r callback', () => { 8 | const p = reject(true) 9 | silenceError(p) 10 | assert.strictEqual(p, p.then(assert.ifError)) 11 | }) 12 | 13 | it('map should be identity', () => { 14 | const p = reject(true) 15 | silenceError(p) 16 | assert.strictEqual(p, p.map(assert.ifError)) 17 | }) 18 | 19 | it('ap should be identity', () => { 20 | const p = reject(assert.ifError) 21 | silenceError(p) 22 | assert.strictEqual(p, p.ap(fulfill(true))) 23 | }) 24 | 25 | it('chain should be identity', () => { 26 | const p = reject() 27 | silenceError(p) 28 | assert.strictEqual(p, p.chain(fulfill)) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /test/settle-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { settle, resolve, reject } from '../src/main' 3 | import { isFulfilled, isRejected } from '../src/inspect' 4 | import { throwingIterable } from './lib/test-util' 5 | import assert from 'assert' 6 | 7 | describe('settle', () => { 8 | it('should reject if iterator throws', () => { 9 | let error = new Error() 10 | return settle(throwingIterable(error)) 11 | .then(assert.ifError, e => assert(e === error)) 12 | }) 13 | 14 | it('should settle empty iterable', () => { 15 | return settle(new Set()).then(a => { 16 | assert.equal(a.length, 0) 17 | }) 18 | }) 19 | 20 | it('should settle promises', () => { 21 | let s = new Set([1, resolve(2), reject(3)]) 22 | return settle(s).then(a => { 23 | assert.equal(a.length, s.size) 24 | assert(isFulfilled(a[0])) 25 | assert(isFulfilled(a[1])) 26 | assert(isRejected(a[2])) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /src/Merge.js: -------------------------------------------------------------------------------- 1 | import { silenceError } from './inspect' 2 | 3 | export default class Merge { 4 | constructor (mergeHandler, results) { 5 | this.pending = 0 6 | this.results = results 7 | this.mergeHandler = mergeHandler 8 | } 9 | 10 | valueAt (x, i, promise) { 11 | this.results[i] = x 12 | this.check(this.pending - 1, promise) 13 | } 14 | 15 | fulfillAt (p, i, promise) { 16 | this.valueAt(p.value, i, promise) 17 | } 18 | 19 | rejectAt (p, i, promise) { 20 | // In the case where the result promise has been resolved 21 | // need to silence all subsequently seen rejections 22 | promise._isResolved() ? silenceError(p) : promise._become(p) 23 | } 24 | 25 | complete (total, promise) { 26 | this.check(this.pending + total, promise) 27 | } 28 | 29 | check (pending, promise) { 30 | this.pending = pending 31 | if (pending === 0) { 32 | this.mergeHandler.merge(promise, this.results) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/toString-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { fulfill, reject, Future, never } from '../src/Promise' 3 | import { getValue, getReason } from '../src/inspect' 4 | import assert from 'assert' 5 | 6 | describe('toString', () => { 7 | it('should indicate fulfilled promise', () => { 8 | let p = fulfill('a') 9 | assert.equal(`[object Promise { fulfilled: ${getValue(p)} }]`, 10 | p.toString()) 11 | }) 12 | 13 | it('should indicate rejected promise', () => { 14 | let p = reject(new Error('a')) 15 | assert.equal(`[object Promise { rejected: ${getReason(p)} }]`, 16 | p.toString()) 17 | }) 18 | 19 | it('should indicate pending promise', () => { 20 | let p = new Future() 21 | assert.equal('[object Promise { pending }]', p.toString()) 22 | }) 23 | 24 | it('should indicate never', () => { 25 | let p = never() 26 | assert.equal('[object Promise { never }]', p.toString()) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/chain-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { fulfill, reject, delay } from '../src/main' 3 | import { assertSame } from './lib/test-util' 4 | import assert from 'assert' 5 | 6 | describe('chain', function () { 7 | it('should satisfy associativity', () => { 8 | const f = x => fulfill(x + 'f') 9 | const g = x => fulfill(x + 'g') 10 | 11 | const m = fulfill('m') 12 | 13 | return assertSame( 14 | m.chain(x => f(x).chain(g)), 15 | m.chain(f).chain(g) 16 | ) 17 | }) 18 | 19 | it('should reject if f returns a non-promise', () => { 20 | return fulfill(1).chain(x => x) 21 | .then( 22 | () => { throw new Error('should not fulfill') }, 23 | e => assert(e instanceof TypeError) 24 | ) 25 | }) 26 | 27 | it('should not map rejection', () => { 28 | const expected = {} 29 | return delay(1, expected).then(reject).chain(() => null) 30 | .then(assert.ifError, x => assert.strictEqual(x, expected)) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /test/ap-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { fulfill } from '../src/main' 3 | import { assertSame } from './lib/test-util' 4 | 5 | describe('ap', () => { 6 | it('should satisfy identity', () => { 7 | const v = fulfill({}) 8 | return assertSame(fulfill(x => x).ap(v), v) 9 | }) 10 | 11 | it('should satisfy composition', () => { 12 | const u = fulfill(x => 'u' + x) 13 | const v = fulfill(x => 'v' + x) 14 | const w = fulfill('w') 15 | 16 | return assertSame( 17 | fulfill(f => g => x => f(g(x))).ap(u).ap(v).ap(w), 18 | u.ap(v.ap(w)) 19 | ) 20 | }) 21 | 22 | it('should satisfy homomorphism', () => { 23 | const f = x => x + 'f' 24 | const x = 'x' 25 | return assertSame(fulfill(f).ap(fulfill(x)), fulfill(f(x))) 26 | }) 27 | 28 | it('should satisfy interchange', () => { 29 | const f = x => x + 'f' 30 | const u = fulfill(f) 31 | const y = 'y' 32 | 33 | return assertSame(u.ap(fulfill(y)), fulfill(f => f(y)).ap(u)) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /test/map-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { fulfill, delay, reject } from '../src/main' 3 | import { assertSame } from './lib/test-util' 4 | import assert from 'assert' 5 | 6 | describe('map', () => { 7 | it('should satisfy identity', () => { 8 | const u = fulfill({}) 9 | return assertSame(u.map(x => x), u) 10 | }) 11 | 12 | it('should satisfy composition', () => { 13 | const f = x => x + 'f' 14 | const g = x => x + 'g' 15 | const u = fulfill('e') 16 | 17 | return assertSame(u.map(x => f(g(x))), u.map(g).map(f)) 18 | }) 19 | 20 | it('should reject if f throws', () => { 21 | const expected = {} 22 | return delay(1).map(() => { throw expected }) 23 | .then(assert.ifError, x => assert.strictEqual(x, expected)) 24 | }) 25 | 26 | it('should not map rejection', () => { 27 | const expected = {} 28 | return delay(1, expected).then(reject).map(() => null) 29 | .then(assert.ifError, x => assert.strictEqual(x, expected)) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /test/then-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { delay, reject } from '../src/main' 3 | import assert from 'assert' 4 | 5 | describe('then', function () { 6 | it('should not change value when f is not a function', () => { 7 | let expected = {} 8 | return delay(1, expected).then() 9 | .then(x => assert.strictEqual(x, expected)) 10 | }) 11 | 12 | it('should not change reason when r is not a function', () => { 13 | let expected = {} 14 | return delay(1, expected).then(reject).then(x => null) 15 | .then(assert.ifError, x => assert.strictEqual(x, expected)) 16 | }) 17 | 18 | it('should reject if f throws', () => { 19 | let expected = {} 20 | return delay(1).then(() => { throw expected }) 21 | .then(assert.ifError, x => assert.strictEqual(x, expected)) 22 | }) 23 | 24 | it('should reject if r throws', () => { 25 | let expected = {} 26 | return delay(1).then(reject).then(null, () => { throw expected }) 27 | .then(assert.ifError, x => assert.strictEqual(x, expected)) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /perf/bench: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | benchmark=$1 3 | nodepath=${2:-node} 4 | shift 2; 5 | cwd=${PWD} 6 | 7 | trap 'cd "$cwd"' EXIT 8 | 9 | if [ "$benchmark" = "doxbee" ]; then 10 | npm install 11 | echo "Doxbee sequential" 12 | $nodepath performance.js --n 10000 --t 1 ./doxbee-sequential/*.js --harmony "$@" 13 | exit 0 14 | elif [ "$benchmark" = "doxbee-errors" ]; then 15 | npm install 16 | echo "Doxbee sequential with 10% errors" 17 | $nodepath performance.js --n 10000 --t 1 --e 0.1 ./doxbee-sequential-errors/*.js --harmony "$@" 18 | exit 0 19 | elif [ "$benchmark" = "parallel" ]; then 20 | npm install 21 | echo "Madeup parallel" 22 | $nodepath performance.js --n 10000 --t 1 --p 25 ./madeup-parallel/*.js --harmony "$@" 23 | exit 0 24 | elif [ "$benchmark" = "analysis" ]; then 25 | npm install 26 | echo "analysis" 27 | $nodepath performance.js --n 10000 --t 1 ./analysis/*.js --harmony "$@" 28 | exit 0 29 | else 30 | echo "Invalid benchmark name $benchmark" 31 | exit -1 32 | fi 33 | -------------------------------------------------------------------------------- /test/fulfill-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { fulfill, reject } from '../src/main' 3 | import { silenceError, getValue } from '../src/inspect' 4 | import assert from 'assert' 5 | 6 | describe('fulfill', () => { 7 | it('should wrap value', () => { 8 | const x = {} 9 | return fulfill(x).then(y => assert(x === y)) 10 | }) 11 | 12 | it('should be immediately fulfilled', () => { 13 | const x = {} 14 | assert.strictEqual(x, getValue(fulfill(x))) 15 | }) 16 | 17 | it('should wrap promise', () => { 18 | const x = fulfill({}) 19 | return fulfill(x).then(y => assert(x === y)) 20 | }) 21 | 22 | it('should wrap rejected promise', () => { 23 | const x = reject({}) 24 | silenceError(x) 25 | return fulfill(x).then(y => assert(x === y)) 26 | }) 27 | 28 | it('catch should be identity', () => { 29 | const p = fulfill(true) 30 | assert.strictEqual(p, p.catch(assert.ifError)) 31 | }) 32 | 33 | it('then should be identity without f callback', () => { 34 | const p = fulfill(true) 35 | assert.strictEqual(p, p.then()) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /src/coroutine.js: -------------------------------------------------------------------------------- 1 | import Action from './Action' 2 | 3 | export default function (resolve, iterator, promise) { 4 | new Coroutine(resolve, iterator, promise).run() 5 | return promise 6 | } 7 | 8 | class Coroutine extends Action { 9 | constructor (resolve, iterator, promise) { 10 | super(promise) 11 | this.resolve = resolve 12 | this.generator = iterator 13 | } 14 | 15 | run () { 16 | this.tryStep(this.generator.next, void 0) 17 | } 18 | 19 | tryStep (resume, x) { 20 | let result 21 | // test if `resume` (and only it) throws 22 | try { 23 | result = resume.call(this.generator, x) 24 | } catch (e) { 25 | this.promise._reject(e) 26 | return 27 | } // else 28 | this.handle(result) 29 | } 30 | 31 | handle (result) { 32 | if (result.done) { 33 | return this.promise._resolve(result.value) 34 | } 35 | 36 | this.resolve(result.value)._when(this) 37 | } 38 | 39 | fulfilled (ref) { 40 | this.tryStep(this.generator.next, ref.value) 41 | } 42 | 43 | rejected (ref) { 44 | this.tryStep(this.generator.throw, ref.value) 45 | return true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Brian Cavalier 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 | 23 | -------------------------------------------------------------------------------- /perf/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Petka Antonov 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 | -------------------------------------------------------------------------------- /perf/lib/fakes-ctx.js: -------------------------------------------------------------------------------- 1 | var timers = require('./timers-ctx'); 2 | 3 | var fakemaker = require('./fakemaker'); 4 | 5 | var f = {}; 6 | f.dummy = function dummy(n) { 7 | return function dummy_n() { 8 | var cb = arguments[n - 1], 9 | ctx = arguments[n]; 10 | //console.log(cb, ctx); 11 | 12 | timers.setTimeout(cb, ctx, global.asyncTime || 100); 13 | } 14 | } 15 | 16 | // A throwing callback function 17 | f.dummyt = function dummyt(n) { 18 | return function dummy_throwing_n() { 19 | var cb = arguments[n - 1], 20 | ctx = arguments[n]; 21 | if (global.testThrow) 22 | throw(new Error("Exception happened")); 23 | setTimeout(function throwTimeout() { 24 | if (global.testThrowAsync) { 25 | throw(new Error("Exception happened")); 26 | } else if (global.testError) { 27 | return cb.call(ctx, new Error("Error happened")); 28 | } 29 | else cb.call(ctx); 30 | }, global.asyncTime || 100); 31 | } 32 | } 33 | 34 | 35 | 36 | 37 | fakemaker(f.dummy, f.dummyt, function wrap_identity(f) { return f; }); 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/iterable-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { Future, resolve } from '../src/Promise' 3 | import { resolveIterable } from '../src/iterable' 4 | import { arrayIterable } from './lib/test-util' 5 | import assert from 'assert' 6 | 7 | describe('iterable', () => { 8 | it('should reject if itemHandler throws synchronously before resolution', () => { 9 | const error = new Error() 10 | const itemHandler = { 11 | valueAt () { 12 | throw error 13 | } 14 | } 15 | 16 | const iterable = arrayIterable([1, 2, 3]) 17 | return resolveIterable(resolve, itemHandler, iterable, new Future()) 18 | .then(assert.ifError, e => assert.strictEqual(error, e)) 19 | }) 20 | 21 | it('should not reject if itemHandler throws synchronously after resolution', () => { 22 | const error = new Error() 23 | const itemHandler = { 24 | valueAt () { 25 | throw error 26 | } 27 | } 28 | 29 | const iterable = arrayIterable([1, 2, 3]) 30 | const expected = {} 31 | const promise = new Future() 32 | promise._resolve(expected) 33 | 34 | return resolveIterable(resolve, itemHandler, iterable, promise) 35 | .then(x => assert.strictEqual(expected, x)) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /src/ErrorHandler.js: -------------------------------------------------------------------------------- 1 | import { silenceError, isHandled } from './inspect' 2 | 3 | const UNHANDLED_REJECTION = 'unhandledRejection' 4 | const HANDLED_REJECTION = 'rejectionHandled' 5 | 6 | export default class ErrorHandler { 7 | constructor (emitEvent, reportError) { 8 | this.rejections = [] 9 | this.emit = emitEvent 10 | this.reportError = reportError 11 | } 12 | 13 | track (rejected) { 14 | if (!this.emit(UNHANDLED_REJECTION, rejected, rejected.value)) { 15 | /* istanbul ignore else */ 16 | if (this.rejections.length === 0) { 17 | setTimeout(reportErrors, 1, this.reportError, this.rejections) 18 | } 19 | this.rejections.push(rejected) 20 | } 21 | } 22 | 23 | untrack (rejected) { 24 | silenceError(rejected) 25 | this.emit(HANDLED_REJECTION, rejected) 26 | } 27 | } 28 | 29 | function reportErrors (report, rejections) { 30 | try { 31 | reportAll(rejections, report) 32 | } finally { 33 | rejections.length = 0 34 | } 35 | } 36 | 37 | function reportAll (rejections, report) { 38 | for (let i = 0; i < rejections.length; ++i) { 39 | const rejected = rejections[i] 40 | /* istanbul ignore else */ 41 | if (!isHandled(rejected)) { 42 | report(rejected) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/inspect.js: -------------------------------------------------------------------------------- 1 | import { PENDING, FULFILLED, REJECTED, SETTLED, NEVER, HANDLED } from './state' 2 | 3 | export function isPending (p) { 4 | return (p.state() & PENDING) > 0 5 | } 6 | 7 | export function isFulfilled (p) { 8 | return (p.state() & FULFILLED) > 0 9 | } 10 | 11 | export function isRejected (p) { 12 | return (p.state() & REJECTED) > 0 13 | } 14 | 15 | export function isSettled (p) { 16 | return (p.state() & SETTLED) > 0 17 | } 18 | 19 | export function isNever (p) { 20 | return (p.state() & NEVER) > 0 21 | } 22 | 23 | export function isHandled (p) { 24 | return (p.state() & HANDLED) > 0 25 | } 26 | 27 | export function getValue (p) { 28 | const n = p.near() 29 | if (!isFulfilled(n)) { 30 | throw new TypeError('getValue called on ' + p) 31 | } 32 | 33 | return n.value 34 | } 35 | 36 | export function getReason (p) { 37 | const n = p.near() 38 | if (!isRejected(n)) { 39 | throw new TypeError('getReason called on ' + p) 40 | } 41 | 42 | silenceError(n) 43 | return n.value 44 | } 45 | 46 | export function silenceError (p) { 47 | p._runAction(silencer) 48 | } 49 | 50 | // implements Action 51 | const silencer = { 52 | fulfilled () {}, 53 | rejected (p) { 54 | p._state |= HANDLED 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/merge-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { merge, resolve, reject } from '../src/main' 3 | import assert from 'assert' 4 | 5 | describe('merge', () => { 6 | it('should call merge function later', () => { 7 | let ok = false 8 | let p = merge(() => assert(ok)) 9 | ok = true 10 | 11 | return p 12 | }) 13 | 14 | it('should call merge function with values', () => { 15 | return merge((x, y) => { 16 | assert.equal(x, 1) 17 | assert.equal(y, 2) 18 | return x + y 19 | }, 1, 2).then(a => assert.equal(a, 3)) 20 | }) 21 | 22 | it('should call merge function with fulfilled values', () => { 23 | return merge((x, y) => { 24 | assert.equal(x, 1) 25 | assert.equal(y, 2) 26 | return x + y 27 | }, resolve(1), resolve(2)).then(a => assert.equal(a, 3)) 28 | }) 29 | 30 | it('should reject if input contains rejection', () => { 31 | return merge(() => assert(false), 1, reject(2)) 32 | .catch(x => assert.equal(x, 2)) 33 | }) 34 | 35 | it('should reject if merge function throws', () => { 36 | const expected = {} 37 | return merge(() => { 38 | throw expected 39 | }, resolve(1), resolve(2)) 40 | .then(assert.ifError, e => assert.strictEqual(expected, e)) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /perf/lib/dummy.js: -------------------------------------------------------------------------------- 1 | // A typical node callback function 2 | // with the callback at the Nth position 3 | exports.dummy = function dummy(n) { 4 | return function dummy_n() { 5 | var cb = arguments[n - 1]; 6 | if (global.asyncTime) { 7 | // Some tests seem not to provide cb, and node 6 throws 8 | // an error. So, for now, do nothing if it's not a function 9 | if (typeof cb === 'function') { 10 | setTimeout(cb, global.asyncTime || 100); 11 | } 12 | } 13 | else 14 | process.nextTick(cb); 15 | } 16 | } 17 | 18 | // A throwing callback function 19 | exports.dummyt = function dummyt(n) { 20 | return function dummy_throwing_n() { 21 | var cb = arguments[n - 1]; 22 | if (global.testThrow) 23 | throw(new Error("Exception happened")); 24 | setTimeout(function throwTimeout() { 25 | if (global.testThrowAsync) { 26 | throw(new Error("Exception happened")); 27 | } else if (global.testError) { 28 | return cb(new Error("Error happened")); 29 | } 30 | else cb(); 31 | }, global.asyncTime || 100); 32 | } 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/coroutine-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { fulfill, reject, delay, coroutine } from '../src/main' 3 | import assert from 'assert' 4 | 5 | describe('coroutine', function () { 6 | it('should allow parameters', () => { 7 | const f = coroutine(function *(a, b) { 8 | assert.equal(a, 'a') 9 | assert.equal(b, 'b') 10 | }) 11 | 12 | return f('a', 'b') 13 | }) 14 | 15 | it('should continue on fulfilled promises', () => { 16 | const f = coroutine(function *(a, b) { 17 | return (yield delay(1, a)) + (yield fulfill(b)) 18 | }) 19 | 20 | return f('a', 'b').then(x => assert.equal(x, 'ab')) 21 | }) 22 | 23 | it('should throw on rejected promises', () => { 24 | const expected = new Error() 25 | const f = coroutine(function *(a) { 26 | try { 27 | yield reject(a) 28 | } catch (e) { 29 | return e 30 | } 31 | }) 32 | 33 | return f(expected) 34 | .then(x => assert.strictEqual(x, expected)) 35 | }) 36 | 37 | it('should reject on uncaught exception', () => { 38 | const expected = new Error() 39 | const f = coroutine(function *(a) { 40 | yield reject(a) 41 | }) 42 | 43 | return f(expected) 44 | .then(assert.ifError, e => assert.strictEqual(e, expected)) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /test/never-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { never, fulfill } from '../src/main' 3 | import assert from 'assert' 4 | 5 | describe('never', () => { 6 | it('then should be identity', () => { 7 | var p = never() 8 | assert.strictEqual(p, p.then(assert.ifError, assert.ifError)) 9 | }) 10 | 11 | it('catch should be identity', () => { 12 | var p = never() 13 | assert.strictEqual(p, p.catch(assert.ifError)) 14 | }) 15 | 16 | it('map should be identity', () => { 17 | var p = never() 18 | assert.strictEqual(p, p.map(assert.ifError)) 19 | }) 20 | 21 | it('bimap should be identity', () => { 22 | var p = never() 23 | assert.strictEqual(p, p.bimap(assert.ifError, assert.ifError)) 24 | }) 25 | 26 | it('ap should be identity', () => { 27 | var p = never() 28 | assert.strictEqual(p, p.ap(fulfill())) 29 | }) 30 | 31 | it('chain should be identity', () => { 32 | var p = never() 33 | assert.strictEqual(p, p.chain(fulfill)) 34 | }) 35 | 36 | it('_when should not call action', () => { 37 | let fail = () => { throw new Error('never._when called action') } 38 | let action = { 39 | fulfilled: fail, 40 | rejected: fail 41 | } 42 | 43 | assert.strictEqual(void 0, never()._when(action)) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /perf/lib/fakesSJS-src.sjs: -------------------------------------------------------------------------------- 1 | var f = require('./dummy.js'); 2 | 3 | var makefakes = require('./fakemaker.js'); 4 | 5 | function wrap(f) { 6 | return function(x, y) { 7 | waitfor(var err, val) { 8 | f(x, y, resume); 9 | } 10 | if (err) throw err; 11 | return val; 12 | }; 13 | } 14 | 15 | function dummySJS0() { 16 | var inner = f.dummy(1); 17 | return function() { 18 | waitfor (var err, val) { 19 | inner(resume); 20 | } 21 | if (err) throw err; 22 | return val; 23 | } 24 | } 25 | 26 | function dummySJS1() { 27 | var inner = f.dummy(2); 28 | return function(x) { 29 | waitfor (var err, val) { 30 | inner(x, resume); 31 | } 32 | if (err) throw err; 33 | return val; 34 | } 35 | } 36 | 37 | function dummySJS(n) { 38 | if (n === 1) return dummySJS0(); 39 | if (n === 2) return dummySJS1(); 40 | } 41 | 42 | function dummytSJS(n) { 43 | var inner = f.dummyt(n); 44 | return function() { 45 | waitfor(var err, val) { 46 | var args = Array.prototype.slice.apply(arguments); 47 | args.push(resume); 48 | inner.apply(this, args); 49 | } 50 | 51 | if (err) throw err; 52 | return val; 53 | } 54 | } 55 | 56 | makefakes(dummySJS, dummytSJS, wrap, global); 57 | -------------------------------------------------------------------------------- /test/any-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { any, resolve, reject } from '../src/main' 3 | import { throwingIterable, arrayIterable } from './lib/test-util' 4 | import assert from 'assert' 5 | 6 | describe('any', () => { 7 | it('should reject if iterator throws', () => { 8 | const error = new Error() 9 | return any(throwingIterable(error)) 10 | .then(assert.ifError, e => assert(e === error)) 11 | }) 12 | 13 | it('should reject with RangeError for empty iterable', () => { 14 | return any(new Set()).catch(e => assert(e instanceof RangeError)) 15 | }) 16 | 17 | it('should resolve a value', () => { 18 | const a = [1, 2, 3] 19 | const s = arrayIterable(a) 20 | return any(s).then(x => assert(a.indexOf(x) >= 0)) 21 | }) 22 | 23 | it('should resolve a promise', () => { 24 | const a = [1, 2, 3] 25 | const s = arrayIterable(a.map(resolve)) 26 | return any(s).then(x => assert(a.indexOf(x) >= 0)) 27 | }) 28 | 29 | it('should resolve if at least one input resolves', () => { 30 | const s = arrayIterable([reject(1), reject(2), resolve(3)]) 31 | return any(s).then(x => assert.equal(x, 3)) 32 | }) 33 | 34 | it('should reject if all inputs reject', () => { 35 | const s = arrayIterable([1, 2, 3].map(reject)) 36 | return any(s).catch(() => assert(true)) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /test/resolve-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { resolve, Future } from '../src/Promise' 3 | import assert from 'assert' 4 | 5 | describe('resolve', () => { 6 | it('should reject promise cycle', () => { 7 | let p = new Future() 8 | p._resolve(p) 9 | return p.then(assert.ifError, e => assert(e instanceof TypeError)) 10 | }) 11 | 12 | describe('thenables', () => { 13 | it('should resolve fulfilled thenable', () => { 14 | let expected = {} 15 | return resolve({ then: f => f(expected) }) 16 | .then(x => assert.strictEqual(expected, x)) 17 | }) 18 | 19 | it('should resolve rejected thenable', () => { 20 | let expected = {} 21 | return resolve({ then: (f, r) => r(expected) }) 22 | .then(assert.ifError, e => assert.strictEqual(expected, e)) 23 | }) 24 | 25 | it('should reject if thenable.then throws', () => { 26 | let expected = {} 27 | return resolve({ then: () => { throw expected } }) 28 | .then(assert.ifError, e => assert.strictEqual(expected, e)) 29 | }) 30 | 31 | it('should reject if accessing thenable.then throws', () => { 32 | let expected = {} 33 | let thenable = { 34 | get then () { throw expected } 35 | } 36 | 37 | return resolve(thenable) 38 | .then(assert.ifError, e => assert.strictEqual(expected, e)) 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /test/lib/test-util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import assert from 'assert' 4 | 5 | export function assertSame (ap, bp) { 6 | return ap.then(a => bp.then(b => assert.strictEqual(a, b)), 7 | a => bp.then(x => { throw x }, b => assert.strictEqual(a, b))) 8 | } 9 | 10 | export function assertSameRejected (ap, bp) { 11 | return ap.then(assert.ifError, 12 | a => bp.then(assert.ifError, b => assert(a === b))) 13 | } 14 | 15 | export function throwingIterable (e) { 16 | return new FakeIterable(new ThrowingIterator(e)) 17 | } 18 | 19 | export function arrayIterable (array) { 20 | return new FakeIterable(new ArrayIterator(array)) 21 | } 22 | 23 | class FakeIterable { 24 | constructor (iterator) { 25 | this.iterator = iterator 26 | } 27 | 28 | [Symbol.iterator] () { 29 | return this.iterator 30 | } 31 | } 32 | 33 | class ArrayIterator { 34 | constructor (array) { 35 | this.array = array 36 | this.index = 0 37 | } 38 | 39 | next () { 40 | return this.index < this.array.length 41 | ? { done: false, value: this.array[this.index++] } 42 | : { done: true, value: void 0 } 43 | } 44 | 45 | throw (e) { 46 | throw e 47 | } 48 | 49 | return () {} 50 | } 51 | 52 | class ThrowingIterator { 53 | constructor (error) { 54 | this.error = error 55 | } 56 | 57 | next () { 58 | throw this.error 59 | } 60 | 61 | throw (e) { 62 | throw e 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/race-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { race, resolve, reject, never } from '../src/main' 3 | import { isNever } from '../src/inspect' 4 | import { throwingIterable } from './lib/test-util' 5 | import assert from 'assert' 6 | 7 | describe('race', () => { 8 | it('should reject if iterator throws', () => { 9 | let error = new Error() 10 | return race(throwingIterable(error)) 11 | .then(assert.ifError, e => assert(e === error)) 12 | }) 13 | 14 | it('should return never when input is empty', () => { 15 | assert(isNever(race([]))) 16 | }) 17 | 18 | it('should reject with a TypeError when passed non-iterable', () => { 19 | return race(123).then(assert.ifError, e => assert(e instanceof TypeError)) 20 | }) 21 | 22 | it('should be identity for 1 element when value', () => { 23 | return race(new Set([1])) 24 | .then(x => assert.equal(x, 1)) 25 | }) 26 | 27 | it('should be identity for 1 element when fulfilled', () => { 28 | return race(new Set([resolve(1)])) 29 | .then(x => assert.equal(x, 1)) 30 | }) 31 | 32 | it('should be identity for 1 element when rejected', () => { 33 | return race(new Set([reject(1)])) 34 | .catch(x => assert.equal(x, 1)) 35 | }) 36 | 37 | it('should fulfill when winner fulfills', () => { 38 | return race([resolve(), never()]) 39 | }) 40 | 41 | it('should reject when winner rejects', () => { 42 | return race([reject(1), never()]) 43 | .then(assert.ifError, x => assert.equal(x, 1)) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /test/timeout-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { timeout, delay } from '../src/main' 3 | import TimeoutError from '../src/TimeoutError' 4 | import { Future, reject, fulfill } from '../src/Promise' 5 | import { silenceError } from '../src/inspect' 6 | import assert from 'assert' 7 | 8 | function delayReject (ms, e) { 9 | let p = new Future() 10 | setTimeout(e => p._reject(e), ms, e) 11 | return p 12 | } 13 | 14 | describe('timeout', function () { 15 | it('should be identity for fulfilled', () => { 16 | let p = fulfill() 17 | assert.strictEqual(p, timeout(0, p)) 18 | }) 19 | 20 | it('should be identity for rejected', () => { 21 | let p = reject() 22 | silenceError(p) 23 | assert.strictEqual(p, timeout(0, p)) 24 | }) 25 | 26 | it('should reject if timeout is earlier than fulfill', () => { 27 | return timeout(1, delay(10, true)) 28 | .then(assert.ifError, assert) 29 | }) 30 | 31 | it('should fulfill if timeout is later than fulfill', () => { 32 | let x = {} 33 | return timeout(10, delay(1, x)) 34 | .then(a => assert.strictEqual(x, a)) 35 | }) 36 | 37 | it('should reject if timeout is earlier than reject', () => { 38 | return timeout(1, delayReject(10, {})) 39 | .then(assert.ifError, e => assert(e instanceof TimeoutError)) 40 | }) 41 | 42 | it('should reject if timeout is later than reject', () => { 43 | let x = {} 44 | return timeout(10, delayReject(1, x)) 45 | .then(assert.ifError, e => assert.strictEqual(x, e)) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /perf/lib/catcher.js: -------------------------------------------------------------------------------- 1 | 2 | exports.longStackSupport = global.longStackSupport; 3 | 4 | 5 | function invoke(ctx, cb, value, myhandler) { 6 | try { 7 | cb.call(ctx, value); // no error 8 | } catch (e) { 9 | if (myhandler) 10 | myhandler.call(ctx, e); 11 | else 12 | console.error(e); 13 | } 14 | } 15 | 16 | module.exports = function() { 17 | var self = {}; 18 | var notCaught = true, myhandler; 19 | self.try = function $try(cb) { 20 | if (exports.longStackSupport) { 21 | var ex = {}; 22 | Error.captureStackTrace(ex); 23 | } 24 | return function wrapper(err, value) { 25 | if (err) { 26 | if (notCaught) { 27 | notCaught = false; 28 | if (err.stack && ex) { 29 | var asyncStackRaw = 30 | ex.stack.substr(ex.stack.indexOf('\n')); 31 | err.stack += '\nFrom previous event:' 32 | + asyncStackRaw; 33 | } 34 | if (myhandler) myhandler(err); 35 | else console.error(err); 36 | } 37 | } 38 | else if (myhandler) 39 | invoke(this, cb, value, myhandler); 40 | else cb(value); 41 | } 42 | } 43 | self.catch = function $catch(handler) { 44 | myhandler = handler 45 | }; 46 | return self; 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /perf/lib/promiseSupport.js: -------------------------------------------------------------------------------- 1 | var when = require('when'); 2 | var fn = require('when/function'); 3 | 4 | 5 | 6 | exports.ternary = fn.lift(function(truthy, iftrue, iffalse) { 7 | return truthy ? iftrue: iffalse; 8 | }) 9 | 10 | exports.not = function not(truthyP) { 11 | return when(truthyP).then(function(truthyVal) { 12 | return !truthyVal; 13 | }); 14 | } 15 | 16 | exports.allObject = function allObject(objWithPromises) { 17 | return when(objWithPromises).then(function(objWithPromises) { 18 | var keys = Object.keys(objWithPromises); 19 | return when.all(keys.map(function(key) { 20 | return objWithPromise; 21 | })).then(function(vals) { 22 | var init = {}; 23 | for (var k = 0; k < keys.length; ++k) { 24 | init[keys[k]] = vals[k]; 25 | } 26 | return init; 27 | }); 28 | }); 29 | } 30 | 31 | exports.set = fn.lift(function(obj, values) { 32 | for (var key in values) 33 | obj[key] = values[key]; 34 | return obj; 35 | }); 36 | 37 | exports.if = function ifP (truthyP, fnTrue, fnFalse) { 38 | return truthyP.then(function(truthy) { 39 | if (truthy) return fnTrue(); 40 | else return fnFalse(); 41 | }); 42 | } 43 | 44 | exports.get = fn.lift(function (obj, key) { 45 | return obj[key]; 46 | }); 47 | 48 | exports.eventuallyCall = fn.lift(function(obj, fnkey) { 49 | var args = [].slice.call(arguments, 2); 50 | obj[fnkey].apply(obj, args); 51 | }); 52 | -------------------------------------------------------------------------------- /src/emitError.js: -------------------------------------------------------------------------------- 1 | import { isNode } from './env' 2 | 3 | const UNHANDLED_REJECTION = 'unhandledRejection' 4 | 5 | export default function () { 6 | /* global process, self, CustomEvent */ 7 | // istanbul ignore else */ 8 | if (isNode && typeof process.emit === 'function') { 9 | // Returning falsy here means to call the default reportRejection API. 10 | // This is safe even in browserify since process.emit always returns 11 | // falsy in browserify: 12 | // https://github.com/defunctzombie/node-process/blob/master/browser.js#L40-L46 13 | return function (type, error) { 14 | return type === UNHANDLED_REJECTION 15 | ? process.emit(type, error.value, error) 16 | : process.emit(type, error) 17 | } 18 | } else if (typeof self !== 'undefined' && typeof CustomEvent === 'function') { 19 | return (function (noop, self, CustomEvent) { 20 | var hasCustomEvent 21 | try { 22 | hasCustomEvent = new CustomEvent(UNHANDLED_REJECTION) instanceof CustomEvent 23 | } catch (e) { 24 | hasCustomEvent = false 25 | } 26 | 27 | return !hasCustomEvent ? noop : function (type, error) { 28 | const ev = new CustomEvent(type, { 29 | detail: { 30 | reason: error.value, 31 | promise: error 32 | }, 33 | bubbles: false, 34 | cancelable: true 35 | }) 36 | 37 | return !self.dispatchEvent(ev) 38 | } 39 | }(noop, self, CustomEvent)) 40 | } 41 | 42 | // istanbul ignore next */ 43 | return noop 44 | } 45 | 46 | // istanbul ignore next */ 47 | function noop () {} 48 | -------------------------------------------------------------------------------- /test/delay-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { delay } from '../src/main' 3 | import { Future, never, reject, fulfill } from '../src/Promise' 4 | import { silenceError, isNever, isPending } from '../src/inspect' 5 | import { assertSame } from './lib/test-util' 6 | import assert from 'assert' 7 | 8 | const lte = (a, b) => (a - 1) <= b 9 | 10 | describe('delay', function () { 11 | it('should be identity for 0 ms', () => { 12 | const p = fulfill() 13 | assert.strictEqual(p, delay(0, p)) 14 | }) 15 | 16 | it('should be identity for rejected', () => { 17 | const p = reject() 18 | silenceError(p) 19 | assert.strictEqual(p, delay(1, p)) 20 | }) 21 | 22 | it('should not delay rejected', () => { 23 | const p = new Future() 24 | const d = delay(1, p) 25 | 26 | assert(isPending(d)) 27 | 28 | const x = {} 29 | p._reject(x) 30 | 31 | return d.then(assert.ifError, e => assert.strictEqual(x, e)) 32 | }) 33 | 34 | it('should return never for never', () => { 35 | assert(isNever(delay(0, never()))) 36 | }) 37 | 38 | it('should delay value', () => { 39 | const x = {} 40 | const t = 10 41 | const p = delay(t, x) 42 | 43 | const now = Date.now() 44 | return assertSame(fulfill(x), p) 45 | .then(() => assert(lte(t, Date.now() - now))) 46 | }) 47 | 48 | it('should delay fulfilled', () => { 49 | const x = {} 50 | const t = 10 51 | const p = delay(t, fulfill(x)) 52 | 53 | const now = Date.now() 54 | return assertSame(fulfill(x), p) 55 | .then(() => assert(lte(t, Date.now() - now))) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /test/all-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { Future, all, resolve } from '../src/Promise' 3 | import { throwingIterable, arrayIterable } from './lib/test-util' 4 | import assert from 'assert' 5 | 6 | describe('all', () => { 7 | it('should reject if iterator throws', () => { 8 | const error = new Error() 9 | return all(throwingIterable(error)) 10 | .then(assert.ifError, e => assert(e === error)) 11 | }) 12 | 13 | it('should resolve empty iterable', () => { 14 | return all([]).then(a => assert.equal(a.length, 0)) 15 | }) 16 | 17 | it('should resolve values', () => { 18 | const expected = [1, 2, 3] 19 | return all(arrayIterable(expected)) 20 | .then(a => assert.deepEqual(a, expected)) 21 | }) 22 | 23 | it('should resolve promises', () => { 24 | const p = new Future() 25 | setTimeout(p => p._resolve(3), 0, p) 26 | return all(arrayIterable([resolve(1), 2, p])) 27 | .then(a => assert.deepEqual(a, [1, 2, 3])) 28 | }) 29 | 30 | it('should reject if input contains rejection', () => { 31 | const p = new Future() 32 | setTimeout(p => p._reject(2), 0, p) 33 | return all(arrayIterable([1, p, 3])) 34 | .catch(x => assert.equal(x, 2)) 35 | }) 36 | 37 | describe('when input contains thenables', () => { 38 | it('should resolve thenables', () => { 39 | const expected = {} 40 | const thenable = { 41 | then (f) { 42 | f(expected) 43 | } 44 | } 45 | 46 | return all(arrayIterable([thenable])).then(a => { 47 | assert.strictEqual(expected, a[0]) 48 | assert.equal(1, a.length) 49 | }) 50 | }) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /test/runNode-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { runNode, all } from '../src/main' 3 | import assert from 'assert' 4 | 5 | function runFn (...args) { 6 | return runNode(function(...args) { 7 | let last = args.length - 1 8 | let cb = args[last] 9 | let a = args.slice(0, last) 10 | 11 | cb(null, a.reduce(append)) 12 | }, ...args) 13 | } 14 | 15 | const append = (a, b) => a + b 16 | 17 | describe('run', function () { 18 | it('should fulfill on success', () => { 19 | let expected = {} 20 | return runNode((a, cb) => cb(null, a), expected) 21 | .then(x => assert.strictEqual(x, expected)) 22 | }) 23 | 24 | it('should reject on failure', () => { 25 | let expected = new Error() 26 | return runNode((a, cb) => cb(a), expected) 27 | .then(assert.ifError, e => assert.strictEqual(e, expected)) 28 | }) 29 | 30 | it('should reject if function throws synchronously', () => { 31 | let expected = new Error() 32 | return runNode(a => { throw a }, expected) 33 | .then(assert.ifError, e => assert.strictEqual(e, expected)) 34 | }) 35 | 36 | it('should accept zero args', () => { 37 | return runNode(cb => cb(null, true)).then(assert) 38 | }) 39 | 40 | it('should accept multiple args', () => { 41 | let eq = a => b => assert.equal(a, b) 42 | let a = [] 43 | 44 | a.push(runFn('a').then(eq('a'))) 45 | a.push(runFn('a', 'b').then(eq('ab'))) 46 | a.push(runFn('a', 'b', 'c').then(eq('abc'))) 47 | a.push(runFn('a', 'b', 'c', 'd').then(eq('abcd'))) 48 | a.push(runFn('a', 'b', 'c', 'd', 'e').then(eq('abcde'))) 49 | 50 | return all(a) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /test/or-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { fulfill, delay, reject, never } from '../src/main' 3 | import { silenceError } from '../src/inspect' 4 | import { assertSame } from './lib/test-util' 5 | import assert from 'assert' 6 | 7 | describe('concat', function () { 8 | it('should be identity for fulfill', () => { 9 | const p = fulfill() 10 | assert.strictEqual(p, p.or(fulfill())) 11 | }) 12 | 13 | it('should be identity for reject', () => { 14 | const p = reject() 15 | silenceError(p) 16 | assert.strictEqual(p, p.or(fulfill())) 17 | }) 18 | 19 | it('should return other for never', () => { 20 | const p1 = never() 21 | const p2 = fulfill() 22 | assert.strictEqual(p2, p1.or(p2)) 23 | }) 24 | 25 | it('should behave like earlier future', () => { 26 | const expected = {} 27 | const p = delay(1, expected).or(delay(10)) 28 | return assertSame(p, fulfill(expected)) 29 | }) 30 | 31 | it('should behave like other earlier future', () => { 32 | const expected = {} 33 | const p = delay(10).or(delay(1, expected)) 34 | return assertSame(p, fulfill(expected)) 35 | }) 36 | 37 | it('should return other with fulfilled', () => { 38 | const expected = {} 39 | const p = fulfill(expected) 40 | return assert.strictEqual(delay(10).or(p), p) 41 | }) 42 | 43 | it('should return other with rejected', () => { 44 | const expected = {} 45 | const p = reject(expected) 46 | silenceError(p) 47 | return assert.strictEqual(delay(10).or(p), p) 48 | }) 49 | 50 | it('should be identity with never', () => { 51 | const p2 = never() 52 | const p1 = delay(10) 53 | return assert.strictEqual(p1.or(p2), p1) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/promises-bluebird-generator.js: -------------------------------------------------------------------------------- 1 | global.useBluebird = true; 2 | global.useQ = false; 3 | var bluebird = require('bluebird'); 4 | require('../lib/fakesP'); 5 | 6 | module.exports = bluebird.coroutine(function* upload(stream, idOrPath, tag, done) { 7 | try { 8 | var blob = blobManager.create(account); 9 | var tx = db.begin(); 10 | var blobId = yield blob.put(stream); 11 | var file = yield self.byUuidOrPath(idOrPath).get(); 12 | 13 | var previousId = file ? file.version : null; 14 | version = { 15 | userAccountId: userAccount.id, 16 | date: new Date(), 17 | blobId: blobId, 18 | creatorId: userAccount.id, 19 | previousId: previousId, 20 | }; 21 | version.id = Version.createHash(version); 22 | yield Version.insert(version).execWithin(tx); 23 | if (!file) { 24 | var splitPath = idOrPath.split('/'); 25 | var fileName = splitPath[splitPath.length - 1]; 26 | file = { 27 | id: uuid.v1(), 28 | userAccountId: userAccount.id, 29 | name: fileName, 30 | version: version.id 31 | } 32 | var query = yield self.createQuery(idOrPath, file); 33 | yield query.execWithin(tx); 34 | } 35 | yield FileVersion.insert({fileId: file.id, versionId: version.id}) 36 | .execWithin(tx); 37 | yield File.whereUpdate({id: file.id}, {version: version.id}) 38 | .execWithin(tx); 39 | tx.commit(); 40 | done(); 41 | } catch (err) { 42 | tx.rollback(); 43 | done(err); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /test/fromNode-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { fromNode, all } from '../src/main' 3 | import assert from 'assert' 4 | 5 | function makefn () { 6 | return fromNode(function (...args) { 7 | const last = args.length - 1 8 | const cb = args[last] 9 | const a = args.slice(0, last) 10 | 11 | cb(null, a.reduce(append)) 12 | }) 13 | } 14 | 15 | const append = (a, b) => a + b 16 | 17 | describe('fromNode', function () { 18 | it('should fulfill on success', () => { 19 | const expected = {} 20 | const f = fromNode((a, cb) => cb(null, a)) 21 | 22 | return f(expected).then(x => assert.strictEqual(x, expected)) 23 | }) 24 | 25 | it('should reject on failure', () => { 26 | const expected = new Error() 27 | const f = fromNode((a, cb) => cb(a)) 28 | 29 | return f(expected) 30 | .then(assert.ifError, e => assert.strictEqual(e, expected)) 31 | }) 32 | 33 | it('should reject if function throws synchronously', () => { 34 | const expected = new Error() 35 | const f = fromNode(a => { throw a }) 36 | 37 | return f(expected) 38 | .then(assert.ifError, e => assert.strictEqual(e, expected)) 39 | }) 40 | 41 | it('should accept zero args', () => { 42 | const f = fromNode(cb => cb(null, true)) 43 | 44 | return f().then(assert) 45 | }) 46 | 47 | it('should accept multiple args', () => { 48 | const eq = a => b => assert.equal(a, b) 49 | const a = [] 50 | 51 | a.push(makefn()('a').then(eq('a'))) 52 | a.push(makefn()('a', 'b').then(eq('ab'))) 53 | a.push(makefn()('a', 'b', 'c').then(eq('abc'))) 54 | a.push(makefn()('a', 'b', 'c', 'd').then(eq('abcd'))) 55 | a.push(makefn()('a', 'b', 'c', 'd', 'e').then(eq('abcde'))) 56 | 57 | return all(a) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/promises-creed-generator.js: -------------------------------------------------------------------------------- 1 | global.useBluebird = false; 2 | global.useQ = false; 3 | global.useCreed = true; 4 | var creed = require('../..'); 5 | require('../lib/fakesP'); 6 | 7 | module.exports = creed.coroutine(function* upload(stream, idOrPath, tag, done) { 8 | try { 9 | var blob = blobManager.create(account); 10 | var tx = db.begin(); 11 | var blobId = yield blob.put(stream); 12 | var file = yield self.byUuidOrPath(idOrPath).get(); 13 | 14 | var previousId = file ? file.version : null; 15 | version = { 16 | userAccountId: userAccount.id, 17 | date: new Date(), 18 | blobId: blobId, 19 | creatorId: userAccount.id, 20 | previousId: previousId, 21 | }; 22 | version.id = Version.createHash(version); 23 | yield Version.insert(version).execWithin(tx); 24 | if (!file) { 25 | var splitPath = idOrPath.split('/'); 26 | var fileName = splitPath[splitPath.length - 1]; 27 | file = { 28 | id: uuid.v1(), 29 | userAccountId: userAccount.id, 30 | name: fileName, 31 | version: version.id 32 | } 33 | var query = yield self.createQuery(idOrPath, file); 34 | yield query.execWithin(tx); 35 | } 36 | yield FileVersion.insert({fileId: file.id, versionId: version.id}) 37 | .execWithin(tx); 38 | yield File.whereUpdate({id: file.id}, {version: version.id}) 39 | .execWithin(tx); 40 | tx.commit(); 41 | done(); 42 | } catch (err) { 43 | tx.rollback(); 44 | done(err); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/promises-cujojs-when-generator.js: -------------------------------------------------------------------------------- 1 | var when = require('when'), 2 | g = require('when/generator'), 3 | p = require('../lib/promiseSupport.js'); 4 | 5 | require('../lib/fakesP'); 6 | 7 | module.exports = g.lift(function* upload(stream, idOrPath, tag, done) { 8 | try { 9 | var blob = blobManager.create(account); 10 | var tx = db.begin(); 11 | var blobId = yield blob.put(stream); 12 | var file = yield self.byUuidOrPath(idOrPath).get(); 13 | 14 | var previousId = file ? file.version : null; 15 | version = { 16 | userAccountId: userAccount.id, 17 | date: new Date(), 18 | blobId: blobId, 19 | creatorId: userAccount.id, 20 | previousId: previousId, 21 | }; 22 | version.id = Version.createHash(version); 23 | yield Version.insert(version).execWithin(tx); 24 | if (!file) { 25 | var splitPath = idOrPath.split('/'); 26 | var fileName = splitPath[splitPath.length - 1]; 27 | file = { 28 | id: uuid.v1(), 29 | userAccountId: userAccount.id, 30 | name: fileName, 31 | version: version.id 32 | } 33 | var query = yield self.createQuery(idOrPath, file); 34 | yield query.execWithin(tx); 35 | } 36 | yield FileVersion.insert({fileId: file.id, versionId: version.id}) 37 | .execWithin(tx); 38 | yield File.whereUpdate({id: file.id}, {version: version.id}) 39 | .execWithin(tx); 40 | tx.commit(); 41 | done(); 42 | } catch (err) { 43 | tx.rollback(); 44 | done(err); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /test/unhandledRejection-node.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { race, reject, all, future } from '../src/main' 3 | import { isHandled } from '../src/inspect' 4 | 5 | const delayRejectWith = (ms, e) => { 6 | const { resolve, promise } = future() 7 | setTimeout((resolve, e) => resolve(reject(e)), ms, resolve, e) 8 | return promise 9 | } 10 | 11 | const expectOne = aggregate => done => { 12 | const expected = new Error('expected') 13 | 14 | // Arrange for 2 delayed rejections. The first will cause the promise 15 | // returned by aggregate() to reject, and the second should be silenced. 16 | // After the first rejects, arrange to intercept the second via unhandledRejection. 17 | // After another delay, giving the second promise enough time to have been silenced, 18 | // check to see if it was indeed silenced. 19 | aggregate([delayRejectWith(10, expected), delayRejectWith(15, new Error('Unsilenced rejection'))]) 20 | .catch(() => { 21 | const rejections = [] 22 | const unhandledRejection = (e, p) => rejections.push(p) 23 | const rejectionHandled = p => rejections.splice(rejections.indexOf(p), 1) 24 | 25 | process.on('unhandledRejection', unhandledRejection) 26 | process.on('rejectionHandled', rejectionHandled) 27 | 28 | setTimeout(() => { 29 | process.removeListener('unhandledRejection', unhandledRejection) 30 | process.removeListener('rejectionHandled', rejectionHandled) 31 | 32 | const remaining = rejections.filter(r => !isHandled(r)) 33 | if(remaining.length > 0) { 34 | done(remaining.pop().value) 35 | } else { 36 | done() 37 | } 38 | }, 50) 39 | }) 40 | } 41 | 42 | describe('unhandledRejection-node', () => { 43 | it('race should emit 1 unhandledRejection', expectOne(race)) 44 | it('all should emit 1 unhandledRejection', expectOne(all)) 45 | }) 46 | -------------------------------------------------------------------------------- /perf/doxbee-sequential-errors/promises-bluebird-generator.js: -------------------------------------------------------------------------------- 1 | global.useBluebird = true; 2 | global.useQ = false; 3 | var bluebird = require('bluebird'); 4 | require('../lib/fakesP'); 5 | 6 | module.exports = bluebird.coroutine(function* upload(stream, idOrPath, tag, done) { 7 | try { 8 | var blob = blobManager.create(account); 9 | var tx = db.begin(); 10 | var blobId = yield blob.put(stream); 11 | var file = yield self.byUuidOrPath(idOrPath).get(); 12 | 13 | var previousId = file ? file.version : null; 14 | version = { 15 | userAccountId: userAccount.id, 16 | date: new Date(), 17 | blobId: blobId, 18 | creatorId: userAccount.id, 19 | previousId: previousId, 20 | }; 21 | version.id = Version.createHash(version); 22 | yield Version.insert(version).execWithin(tx); 23 | triggerIntentionalError(); 24 | if (!file) { 25 | var splitPath = idOrPath.split('/'); 26 | var fileName = splitPath[splitPath.length - 1]; 27 | file = { 28 | id: uuid.v1(), 29 | userAccountId: userAccount.id, 30 | name: fileName, 31 | version: version.id 32 | } 33 | var query = yield self.createQuery(idOrPath, file); 34 | yield query.execWithin(tx); 35 | triggerIntentionalError(); 36 | } 37 | yield FileVersion.insert({fileId: file.id, versionId: version.id}) 38 | .execWithin(tx); 39 | triggerIntentionalError(); 40 | yield File.whereUpdate({id: file.id}, {version: version.id}) 41 | .execWithin(tx); 42 | triggerIntentionalError(); 43 | tx.commit(); 44 | done(); 45 | } catch (err) { 46 | tx.rollback(); 47 | done(err); 48 | } 49 | }); -------------------------------------------------------------------------------- /perf/doxbee-sequential-errors/promises-creed-generator.js: -------------------------------------------------------------------------------- 1 | global.useCreed = true; 2 | global.useQ = false; 3 | global.useBluebird = false; 4 | 5 | var creed = require('../..'); 6 | 7 | require('../lib/fakesP'); 8 | 9 | module.exports = creed.coroutine(function* upload(stream, idOrPath, tag, done) { 10 | try { 11 | var blob = blobManager.create(account); 12 | var tx = db.begin(); 13 | var blobId = yield blob.put(stream); 14 | var file = yield self.byUuidOrPath(idOrPath).get(); 15 | 16 | var previousId = file ? file.version : null; 17 | version = { 18 | userAccountId: userAccount.id, 19 | date: new Date(), 20 | blobId: blobId, 21 | creatorId: userAccount.id, 22 | previousId: previousId, 23 | }; 24 | version.id = Version.createHash(version); 25 | yield Version.insert(version).execWithin(tx); 26 | triggerIntentionalError(); 27 | if (!file) { 28 | var splitPath = idOrPath.split('/'); 29 | var fileName = splitPath[splitPath.length - 1]; 30 | file = { 31 | id: uuid.v1(), 32 | userAccountId: userAccount.id, 33 | name: fileName, 34 | version: version.id 35 | } 36 | var query = yield self.createQuery(idOrPath, file); 37 | yield query.execWithin(tx); 38 | triggerIntentionalError(); 39 | } 40 | yield FileVersion.insert({fileId: file.id, versionId: version.id}) 41 | .execWithin(tx); 42 | triggerIntentionalError(); 43 | yield File.whereUpdate({id: file.id}, {version: version.id}) 44 | .execWithin(tx); 45 | triggerIntentionalError(); 46 | tx.commit(); 47 | done(); 48 | } catch (err) { 49 | tx.rollback(); 50 | done(err); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/generators-tj-co.js: -------------------------------------------------------------------------------- 1 | global.useNative = true; 2 | 3 | try { 4 | if (Promise.race.toString() !== 'function race() { [native code] }') 5 | throw 0; 6 | } catch (e) { 7 | throw new Error("No ES6 promises available"); 8 | } 9 | var co = require("co"); 10 | require('../lib/fakesP'); 11 | 12 | module.exports = function upload(stream, idOrPath, tag, done) { 13 | co(function* () { 14 | try { 15 | var blob = blobManager.create(account); 16 | var tx = db.begin(); 17 | var blobId = yield blob.put(stream); 18 | var file = yield self.byUuidOrPath(idOrPath).get(); 19 | 20 | var previousId = file ? file.version : null; 21 | version = { 22 | userAccountId: userAccount.id, 23 | date: new Date(), 24 | blobId: blobId, 25 | creatorId: userAccount.id, 26 | previousId: previousId, 27 | }; 28 | version.id = Version.createHash(version); 29 | yield Version.insert(version).execWithin(tx); 30 | if (!file) { 31 | var splitPath = idOrPath.split('/'); 32 | var fileName = splitPath[splitPath.length - 1]; 33 | file = { 34 | id: uuid.v1(), 35 | userAccountId: userAccount.id, 36 | name: fileName, 37 | version: version.id 38 | } 39 | var query = yield self.createQuery(idOrPath, file); 40 | yield query.execWithin(tx); 41 | } 42 | yield FileVersion.insert({fileId: file.id, versionId: version.id}) 43 | .execWithin(tx); 44 | yield File.whereUpdate({id: file.id}, {version: version.id}) 45 | .execWithin(tx); 46 | tx.commit(); 47 | done(); 48 | } catch (err) { 49 | tx.rollback(); 50 | done(err); 51 | } 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /src/iterable.js: -------------------------------------------------------------------------------- 1 | import { isFulfilled, isRejected, silenceError } from './inspect' 2 | import Action from './Action' 3 | import maybeThenable from './maybeThenable' 4 | 5 | export function resultsArray (iterable) { 6 | return Array.isArray(iterable) ? new Array(iterable.length) : [] 7 | } 8 | 9 | export function resolveIterable (resolve, handler, promises, promise) { 10 | const run = Array.isArray(promises) ? runArray : runIterable 11 | try { 12 | run(resolve, handler, promises, promise) 13 | } catch (e) { 14 | promise._reject(e) 15 | } 16 | return promise.near() 17 | } 18 | 19 | function runArray (resolve, handler, promises, promise) { 20 | let i = 0 21 | 22 | for (; i < promises.length; ++i) { 23 | handleItem(resolve, handler, promises[i], i, promise) 24 | } 25 | 26 | handler.complete(i, promise) 27 | } 28 | 29 | function runIterable (resolve, handler, promises, promise) { 30 | let i = 0 31 | const iter = promises[Symbol.iterator]() 32 | 33 | while (true) { 34 | const step = iter.next() 35 | if (step.done) { 36 | break 37 | } 38 | handleItem(resolve, handler, step.value, i++, promise) 39 | } 40 | 41 | handler.complete(i, promise) 42 | } 43 | 44 | function handleItem (resolve, handler, x, i, promise) { 45 | /* eslint complexity:[1,6] */ 46 | if (!maybeThenable(x)) { 47 | handler.valueAt(x, i, promise) 48 | return 49 | } 50 | 51 | const p = resolve(x) 52 | 53 | if (promise._isResolved()) { 54 | if (!isFulfilled(p)) { 55 | silenceError(p) 56 | } 57 | } else if (isFulfilled(p)) { 58 | handler.fulfillAt(p, i, promise) 59 | } else if (isRejected(p)) { 60 | handler.rejectAt(p, i, promise) 61 | } else { 62 | p._runAction(new Indexed(handler, i, promise)) 63 | } 64 | } 65 | 66 | class Indexed extends Action { 67 | constructor (handler, i, promise) { 68 | super(promise) 69 | this.i = i 70 | this.handler = handler 71 | } 72 | 73 | fulfilled (p) { 74 | this.handler.fulfillAt(p, this.i, this.promise) 75 | } 76 | 77 | rejected (p) { 78 | return this.handler.rejectAt(p, this.i, this.promise) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "creed", 3 | "version": "1.0.4", 4 | "description": "Sophisticated and functionally-minded async with advanced features: coroutines, promises, ES2015 iterables, fantasy-land", 5 | "main": "dist/creed.js", 6 | "jsnext:main": "dist/creed.es.js", 7 | "files": [ 8 | "dist/creed.js", 9 | "dist/creed.es.js" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/briancavalier/creed.git" 14 | }, 15 | "author": "brian@hovercraftstudios.com", 16 | "license": "MIT", 17 | "keywords": [ 18 | "promise", 19 | "promises", 20 | "promises/a+", 21 | "promises-aplus", 22 | "future", 23 | "fantasyland", 24 | "monad", 25 | "applicative", 26 | "async" 27 | ], 28 | "scripts": { 29 | "build-dist": "mkdirp dist && rollup -c", 30 | "build-es6": "mkdirp dist && rollup -f cjs -o dist/creed.js src/main.js", 31 | "build": "npm run build-dist && uglifyjs -c \"warnings=false\" -m -o dist/creed.min.js -- dist/creed.js", 32 | "preversion": "npm run build", 33 | "lint": "jsinspect src && eslint src", 34 | "pretest": "npm run lint", 35 | "test": "nyc --check-coverage --statements 100 --branches 89 --lines 100 --functions 100 mocha", 36 | "coverage": "nyc report --reporter=text-lcov | coveralls", 37 | "posttest": "npm run test-aplus", 38 | "test-aplus": "promises-aplus-tests test/aplus.js --reporter dot" 39 | }, 40 | "devDependencies": { 41 | "assert": "^1.3.0", 42 | "babel-eslint": "^7.0.0", 43 | "buba": "^4.0.1", 44 | "buble": "^0.14.0", 45 | "coveralls": "^2.11.14", 46 | "eslint": "^3.8.1", 47 | "eslint-config-standard": "^6.2.0", 48 | "eslint-plugin-promise": "^3.3.0", 49 | "eslint-plugin-standard": "^2.0.1", 50 | "jsinspect": "^0.8.0", 51 | "mkdirp": "^0.5.1", 52 | "mocha": "^3.1.2", 53 | "nyc": "^9.0.1", 54 | "promises-aplus-tests": "^2.1.1", 55 | "rollup": "^0.36.3", 56 | "rollup-plugin-buble": "^0.14.0", 57 | "uglify-js": "^2.7.3" 58 | }, 59 | "dependencies": { 60 | "fantasy-land": "^2.1.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/promises-bluebird.js: -------------------------------------------------------------------------------- 1 | global.useBluebird = true; 2 | global.useQ = false; 3 | var bluebird = require('bluebird'); 4 | require('../lib/fakesP'); 5 | 6 | module.exports = function upload(stream, idOrPath, tag, done) { 7 | var blob = blobManager.create(account); 8 | var tx = db.begin(); 9 | var blobIdP = blob.put(stream); 10 | var fileP = self.byUuidOrPath(idOrPath).get(); 11 | var version, fileId, file; 12 | 13 | bluebird.join(blobIdP, fileP, function(blobId, fileV) { 14 | file = fileV; 15 | var previousId = file ? file.version : null; 16 | version = { 17 | userAccountId: userAccount.id, 18 | date: new Date(), 19 | blobId: blobId, 20 | creatorId: userAccount.id, 21 | previousId: previousId, 22 | }; 23 | version.id = Version.createHash(version); 24 | return Version.insert(version).execWithin(tx); 25 | }).then(function() { 26 | if (!file) { 27 | var splitPath = idOrPath.split('/'); 28 | var fileName = splitPath[splitPath.length - 1]; 29 | var newId = uuid.v1(); 30 | return self.createQuery(idOrPath, { 31 | id: newId, 32 | userAccountId: userAccount.id, 33 | name: fileName, 34 | version: version.id 35 | }).then(function(q) { 36 | return q.execWithin(tx); 37 | }).then(function() { 38 | return newId; 39 | }); 40 | } else { 41 | return file.id; 42 | } 43 | }).then(function(fileIdV) { 44 | fileId = fileIdV; 45 | return FileVersion.insert({ 46 | fileId: fileId, 47 | versionId: version.id 48 | }).execWithin(tx); 49 | }).then(function() { 50 | return File.whereUpdate({id: fileId}, {version: version.id}) 51 | .execWithin(tx); 52 | }).then(function() { 53 | tx.commit(); 54 | return done(); 55 | }, function(err) { 56 | tx.rollback(); 57 | return done(err); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/promises-tildeio-rsvp.js: -------------------------------------------------------------------------------- 1 | global.useRSVP = true; 2 | 3 | var rsvp = require('rsvp'); 4 | 5 | require('../lib/fakesP'); 6 | 7 | module.exports = function upload(stream, idOrPath, tag, done) { 8 | var blob = blobManager.create(account); 9 | var tx = db.begin(); 10 | var blobIdP = blob.put(stream); 11 | var fileP = self.byUuidOrPath(idOrPath).get(); 12 | var version, fileId, file; 13 | rsvp.all([blobIdP, fileP]).then(function(all) { 14 | var blobId = all[0], fileV = all[1]; 15 | file = fileV; 16 | var previousId = file ? file.version : null; 17 | version = { 18 | userAccountId: userAccount.id, 19 | date: new Date(), 20 | blobId: blobId, 21 | creatorId: userAccount.id, 22 | previousId: previousId, 23 | }; 24 | version.id = Version.createHash(version); 25 | return Version.insert(version).execWithin(tx); 26 | }).then(function() { 27 | if (!file) { 28 | var splitPath = idOrPath.split('/'); 29 | var fileName = splitPath[splitPath.length - 1]; 30 | var newId = uuid.v1(); 31 | return self.createQueryCtxless(idOrPath, { 32 | id: newId, 33 | userAccountId: userAccount.id, 34 | name: fileName, 35 | version: version.id 36 | }).then(function(q) { 37 | return q.execWithin(tx); 38 | }).then(function() { 39 | return newId; 40 | }); 41 | } else { 42 | return file.id; 43 | } 44 | }).then(function(fileIdV) { 45 | fileId = fileIdV; 46 | return FileVersion.insert({ 47 | fileId: fileId, 48 | versionId: version.id 49 | }).execWithin(tx); 50 | }).then(function() { 51 | return File.whereUpdate({id: fileId}, {version: version.id}) 52 | .execWithin(tx); 53 | }).then(function() { 54 | tx.commit(); 55 | return done(); 56 | }, function(err) { 57 | tx.rollback(); 58 | return done(err); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /test/ErrorHandler-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import ErrorHandler from '../src/ErrorHandler' 3 | import assert from 'assert' 4 | import { HANDLED } from '../src/state' 5 | 6 | function fakeError (value) { 7 | return { 8 | value: value, 9 | _state: 0, 10 | state () { return this._state }, 11 | _runAction () { this._state |= HANDLED } 12 | } 13 | } 14 | 15 | describe('ErrorHandler', () => { 16 | describe('track', () => { 17 | it('should emit event immediately', () => { 18 | const value = {} 19 | const expected = fakeError(value) 20 | function fail (e) { 21 | assert.fail(e, expected, 'should not call reportError') 22 | } 23 | 24 | function verify (event, e, error) { 25 | assert.strictEqual(e, expected) 26 | assert.strictEqual(error, value) 27 | return true 28 | } 29 | 30 | const eh = new ErrorHandler(verify, fail) 31 | eh.track(expected) 32 | }) 33 | 34 | it('should report error later', done => { 35 | const value = {} 36 | const expected = fakeError(value) 37 | function verify (e) { 38 | assert.strictEqual(e, expected) 39 | assert.strictEqual(e.value, value) 40 | done() 41 | } 42 | 43 | const eh = new ErrorHandler(() => false, verify) 44 | eh.track(expected) 45 | }) 46 | }) 47 | 48 | describe('untrack', () => { 49 | it('should emit event immediately', () => { 50 | const value = {} 51 | const expected = fakeError(value) 52 | function fail (e) { 53 | assert.fail(e, expected, 'should not call reportError') 54 | } 55 | 56 | function verify (event, e) { 57 | assert.strictEqual(e, expected) 58 | assert.strictEqual(e.value, value) 59 | return true 60 | } 61 | 62 | const eh = new ErrorHandler(verify, fail) 63 | eh.untrack(expected) 64 | }) 65 | 66 | it('should silence error', () => { 67 | const value = {} 68 | const expected = fakeError(value) 69 | function fail (e) { 70 | assert.fail(e, expected, 'should not call reportError') 71 | } 72 | 73 | const eh = new ErrorHandler(() => true, fail) 74 | eh.untrack(expected) 75 | 76 | assert.equal(expected.state(), HANDLED) 77 | }) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/promises-creed.js: -------------------------------------------------------------------------------- 1 | global.useCreed = true; 2 | global.useQ = false; 3 | global.useBluebird = false; 4 | 5 | var creed = require('../..'); 6 | 7 | require('../lib/fakesP'); 8 | 9 | module.exports = function upload(stream, idOrPath, tag, done) { 10 | var blob = blobManager.create(account); 11 | var tx = db.begin(); 12 | var blobIdP = blob.put(stream); 13 | var fileP = self.byUuidOrPath(idOrPath).get(); 14 | var version, fileId, file; 15 | 16 | creed.merge(function(blobId, fileV) { 17 | file = fileV; 18 | var previousId = file ? file.version : null; 19 | version = { 20 | userAccountId: userAccount.id, 21 | date: new Date(), 22 | blobId: blobId, 23 | creatorId: userAccount.id, 24 | previousId: previousId, 25 | }; 26 | version.id = Version.createHash(version); 27 | return Version.insert(version).execWithin(tx); 28 | }, blobIdP, fileP).then(function() { 29 | if (!file) { 30 | var splitPath = idOrPath.split('/'); 31 | var fileName = splitPath[splitPath.length - 1]; 32 | var newId = uuid.v1(); 33 | return self.createQueryCtxless(idOrPath, { 34 | id: newId, 35 | userAccountId: userAccount.id, 36 | name: fileName, 37 | version: version.id 38 | }).then(function(q) { 39 | return q.execWithin(tx); 40 | }).then(function() { 41 | return newId; 42 | }); 43 | } else { 44 | return file.id; 45 | } 46 | }).then(function(fileIdV) { 47 | fileId = fileIdV; 48 | return FileVersion.insert({ 49 | fileId: fileId, 50 | versionId: version.id 51 | }).execWithin(tx); 52 | }).then(function() { 53 | return File.whereUpdate({id: fileId}, {version: version.id}) 54 | .execWithin(tx); 55 | }).then(function() { 56 | tx.commit(); 57 | return done(); 58 | }, function(err) { 59 | tx.rollback(); 60 | return done(err); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/promises-creed-algebraic.js: -------------------------------------------------------------------------------- 1 | global.useCreed = true; 2 | global.useQ = false; 3 | global.useBluebird = false; 4 | 5 | var creed = require('../..'); 6 | 7 | require('../lib/fakesP'); 8 | 9 | module.exports = function upload(stream, idOrPath, tag, done) { 10 | var blob = blobManager.create(account); 11 | var tx = db.begin(); 12 | var blobIdP = blob.put(stream); 13 | var fileP = self.byUuidOrPath(idOrPath).get(); 14 | var version, fileId, file; 15 | 16 | creed.merge(function(blobId, fileV) { 17 | file = fileV; 18 | var previousId = file ? file.version : null; 19 | version = { 20 | userAccountId: userAccount.id, 21 | date: new Date(), 22 | blobId: blobId, 23 | creatorId: userAccount.id, 24 | previousId: previousId, 25 | }; 26 | version.id = Version.createHash(version); 27 | return Version.insert(version).execWithin(tx); 28 | }, blobIdP, fileP).chain(function() { 29 | if (!file) { 30 | var splitPath = idOrPath.split('/'); 31 | var fileName = splitPath[splitPath.length - 1]; 32 | var newId = uuid.v1(); 33 | return self.createQueryCtxless(idOrPath, { 34 | id: newId, 35 | userAccountId: userAccount.id, 36 | name: fileName, 37 | version: version.id 38 | }).chain(function(q) { 39 | return q.execWithin(tx); 40 | }).map(function() { 41 | return newId; 42 | }); 43 | } else { 44 | return creed.fulfill(file.id); 45 | } 46 | }).chain(function(fileIdV) { 47 | fileId = fileIdV; 48 | return FileVersion.insert({ 49 | fileId: fileId, 50 | versionId: version.id 51 | }).execWithin(tx); 52 | }).chain(function() { 53 | return File.whereUpdate({id: fileId}, {version: version.id}) 54 | .execWithin(tx); 55 | }).then(function() { 56 | tx.commit(); 57 | return done(); 58 | }, function(err) { 59 | tx.rollback(); 60 | return done(err); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/promises-cujojs-when.js: -------------------------------------------------------------------------------- 1 | var when = require('when'), 2 | p = require('../lib/promiseSupport.js'); 3 | 4 | require('../lib/fakesP'); 5 | 6 | module.exports = function upload(stream, idOrPath, tag, done) { 7 | var blob = blobManager.create(account); 8 | var tx = db.begin(); 9 | var blobIdP = blob.put(stream); 10 | var fileP = self.byUuidOrPath(idOrPath).get(); 11 | var version, fileId, file; 12 | when.try(function(blobId, fileV) { 13 | // when.all([blobIdP, fileP]).then(function(a) { 14 | // var blobId = a[0], fileV = a[1]; 15 | file = fileV; 16 | var previousId = file ? file.version : null; 17 | version = { 18 | userAccountId: userAccount.id, 19 | date: new Date(), 20 | blobId: blobId, 21 | creatorId: userAccount.id, 22 | previousId: previousId, 23 | }; 24 | version.id = Version.createHash(version); 25 | return Version.insert(version).execWithin(tx); 26 | }, blobIdP, fileP).then(function() { 27 | // }).then(function() { 28 | if (!file) { 29 | var splitPath = idOrPath.split('/'); 30 | var fileName = splitPath[splitPath.length - 1]; 31 | var newId = uuid.v1(); 32 | return self.createQuery(idOrPath, { 33 | id: newId, 34 | userAccountId: userAccount.id, 35 | name: fileName, 36 | version: version.id 37 | }).then(function(q) { 38 | return q.execWithin(tx); 39 | }).then(function() { 40 | return newId; 41 | }); 42 | } else { 43 | return file.id; 44 | } 45 | }).then(function(fileIdV) { 46 | fileId = fileIdV; 47 | return FileVersion.insert({ 48 | fileId: fileId, 49 | versionId: version.id 50 | }).execWithin(tx); 51 | }).then(function() { 52 | return File.whereUpdate({id: fileId}, {version: version.id}) 53 | .execWithin(tx); 54 | }).then(function() { 55 | tx.commit(); 56 | return done(); 57 | }, function(err) { 58 | tx.rollback(); 59 | return done(err); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /perf/lib/fakemaker.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function fakemaker(dummy, dummyt, wrap) { 3 | 4 | var dummy_2 = dummy(2), 5 | dummy_1 = dummy(1); 6 | 7 | var dummyt_2, dummyt_1; 8 | 9 | if (global.testError || global.testThrow 10 | || global.testThrowAsync) { 11 | dummyt_2 = dummyt(2); 12 | dummyt_1 = dummyt(1); 13 | } else { 14 | dummyt_2 = dummy_2; 15 | dummyt_1 = dummy_1; 16 | } 17 | 18 | // a queryish object with all 19 | // kinds of functions 20 | function queryish() { 21 | return { 22 | execWithin: dummy_2, 23 | exec: dummy_1, 24 | get: dummy_1, 25 | all: dummy_1, 26 | }; 27 | } 28 | 29 | // a queryish object with functions 30 | // that throw 31 | function queryisht() { 32 | return { 33 | execWithin: dummyt_2, 34 | exec: dummyt_1, 35 | get: dummyt_1, 36 | all: dummyt_1, 37 | }; 38 | } 39 | 40 | global.uuid = { v1: function v1() {} }; 41 | 42 | global.userAccount = { }; 43 | 44 | global.account = { }; 45 | 46 | global.blobManager = { 47 | create: function create() { 48 | return { 49 | put: dummy_2, 50 | } 51 | } 52 | }; 53 | 54 | var cqQueryish = queryish(); 55 | 56 | global.self = { 57 | byUuidOrPath: queryish, 58 | createQuery: wrap(function createQuery(x, y, cb, ctx) { 59 | cb.call(ctx, null, cqQueryish); 60 | }), 61 | createQueryCtxless: wrap(function createQuery(x, y, cb) { 62 | cb.call(this, null, cqQueryish); 63 | }) 64 | }; 65 | 66 | 67 | global.File = { 68 | insert: queryish, 69 | whereUpdate: queryish 70 | }; 71 | 72 | global.FileVersion = { 73 | insert: queryisht 74 | }; 75 | 76 | global.Version = { 77 | createHash: function createHash(v) { return 1; }, 78 | insert: queryish 79 | }; 80 | 81 | global.db = { 82 | begin: function begin() { 83 | return { 84 | commit: dummy_1, 85 | rollback: dummy_1, 86 | }; 87 | } 88 | }; 89 | 90 | }; 91 | 92 | 93 | -------------------------------------------------------------------------------- /perf/doxbee-sequential-errors/promises-bluebird.js: -------------------------------------------------------------------------------- 1 | global.useBluebird = true; 2 | global.useQ = false; 3 | var bluebird = require('bluebird'); 4 | require('../lib/fakesP'); 5 | 6 | module.exports = function upload(stream, idOrPath, tag, done) { 7 | var blob = blobManager.create(account); 8 | var tx = db.begin(); 9 | var blobIdP = blob.put(stream); 10 | var fileP = self.byUuidOrPath(idOrPath).get(); 11 | var version, fileId, file; 12 | 13 | bluebird.join(blobIdP, fileP, function(blobId, fileV) { 14 | file = fileV; 15 | var previousId = file ? file.version : null; 16 | version = { 17 | userAccountId: userAccount.id, 18 | date: new Date(), 19 | blobId: blobId, 20 | creatorId: userAccount.id, 21 | previousId: previousId, 22 | }; 23 | version.id = Version.createHash(version); 24 | return Version.insert(version).execWithin(tx); 25 | }).then(function() { 26 | triggerIntentionalError(); 27 | if (!file) { 28 | var splitPath = idOrPath.split('/'); 29 | var fileName = splitPath[splitPath.length - 1]; 30 | var newId = uuid.v1(); 31 | return self.createQuery(idOrPath, { 32 | id: newId, 33 | userAccountId: userAccount.id, 34 | name: fileName, 35 | version: version.id 36 | }).then(function(q) { 37 | return q.execWithin(tx); 38 | }).then(function() { 39 | return newId; 40 | }); 41 | } else { 42 | return file.id; 43 | } 44 | }).then(function(fileIdV) { 45 | triggerIntentionalError(); 46 | fileId = fileIdV; 47 | return FileVersion.insert({ 48 | fileId: fileId, 49 | versionId: version.id 50 | }).execWithin(tx); 51 | }).then(function() { 52 | triggerIntentionalError(); 53 | return File.whereUpdate({id: fileId}, {version: version.id}) 54 | .execWithin(tx); 55 | }).then(function() { 56 | triggerIntentionalError(); 57 | tx.commit(); 58 | return done(); 59 | }).then(null, function(err) { 60 | tx.rollback(); 61 | return done(err); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /perf/doxbee-sequential-errors/promises-tildeio-rsvp.js: -------------------------------------------------------------------------------- 1 | global.useRSVP = true; 2 | 3 | var rsvp = require('rsvp'); 4 | 5 | require('../lib/fakesP'); 6 | 7 | module.exports = function upload(stream, idOrPath, tag, done) { 8 | var blob = blobManager.create(account); 9 | var tx = db.begin(); 10 | var blobIdP = blob.put(stream); 11 | var fileP = self.byUuidOrPath(idOrPath).get(); 12 | var version, fileId, file; 13 | rsvp.all([blobIdP, fileP]).then(function(all) { 14 | var blobId = all[0], fileV = all[1]; 15 | file = fileV; 16 | var previousId = file ? file.version : null; 17 | version = { 18 | userAccountId: userAccount.id, 19 | date: new Date(), 20 | blobId: blobId, 21 | creatorId: userAccount.id, 22 | previousId: previousId, 23 | }; 24 | version.id = Version.createHash(version); 25 | return Version.insert(version).execWithin(tx); 26 | }).then(function() { 27 | triggerIntentionalError(); 28 | if (!file) { 29 | var splitPath = idOrPath.split('/'); 30 | var fileName = splitPath[splitPath.length - 1]; 31 | var newId = uuid.v1(); 32 | return self.createQueryCtxless(idOrPath, { 33 | id: newId, 34 | userAccountId: userAccount.id, 35 | name: fileName, 36 | version: version.id 37 | }).then(function(q) { 38 | return q.execWithin(tx); 39 | }).then(function() { 40 | return newId; 41 | }); 42 | } else { 43 | return file.id; 44 | } 45 | }).then(function(fileIdV) { 46 | triggerIntentionalError(); 47 | fileId = fileIdV; 48 | return FileVersion.insert({ 49 | fileId: fileId, 50 | versionId: version.id 51 | }).execWithin(tx); 52 | }).then(function() { 53 | triggerIntentionalError(); 54 | return File.whereUpdate({id: fileId}, {version: version.id}) 55 | .execWithin(tx); 56 | }).then(function() { 57 | triggerIntentionalError(); 58 | tx.commit(); 59 | return done(); 60 | }).then(null, function(err) { 61 | tx.rollback(); 62 | return done(err); 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /perf/doxbee-sequential-errors/promises-cujojs-when.js: -------------------------------------------------------------------------------- 1 | var when = require('when'), 2 | fn = require('when/function'), 3 | p = require('../lib/promiseSupport.js'); 4 | 5 | require('../lib/fakesP'); 6 | 7 | module.exports = function upload(stream, idOrPath, tag, done) { 8 | var blob = blobManager.create(account); 9 | var tx = db.begin(); 10 | var blobIdP = blob.put(stream); 11 | var fileP = self.byUuidOrPath(idOrPath).get(); 12 | var version, fileId, file; 13 | when([blobIdP, fileP]).spread(function(blobId, fileV) { 14 | file = fileV; 15 | var previousId = file ? file.version : null; 16 | version = { 17 | userAccountId: userAccount.id, 18 | date: new Date(), 19 | blobId: blobId, 20 | creatorId: userAccount.id, 21 | previousId: previousId, 22 | }; 23 | version.id = Version.createHash(version); 24 | return Version.insert(version).execWithin(tx); 25 | }).then(function() { 26 | triggerIntentionalError(); 27 | if (!file) { 28 | var splitPath = idOrPath.split('/'); 29 | var fileName = splitPath[splitPath.length - 1]; 30 | var newId = uuid.v1(); 31 | return self.createQuery(idOrPath, { 32 | id: newId, 33 | userAccountId: userAccount.id, 34 | name: fileName, 35 | version: version.id 36 | }).then(function(q) { 37 | return q.execWithin(tx); 38 | }).then(function() { 39 | return newId; 40 | }); 41 | } else { 42 | return file.id; 43 | } 44 | }).then(function(fileIdV) { 45 | triggerIntentionalError(); 46 | fileId = fileIdV; 47 | return FileVersion.insert({ 48 | fileId: fileId, 49 | versionId: version.id 50 | }).execWithin(tx); 51 | }).then(function() { 52 | triggerIntentionalError(); 53 | return File.whereUpdate({id: fileId}, {version: version.id}) 54 | .execWithin(tx); 55 | }).then(function() { 56 | triggerIntentionalError(); 57 | tx.commit(); 58 | return done(); 59 | }).then(null, function(err) { 60 | tx.rollback(); 61 | return done(err); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /perf/doxbee-sequential-errors/promises-creed.js: -------------------------------------------------------------------------------- 1 | global.useCreed = true; 2 | global.useQ = false; 3 | global.useBluebird = false; 4 | 5 | var creed = require('../..'); 6 | 7 | require('../lib/fakesP'); 8 | 9 | module.exports = function upload(stream, idOrPath, tag, done) { 10 | var blob = blobManager.create(account); 11 | var tx = db.begin(); 12 | var blobIdP = blob.put(stream); 13 | var fileP = self.byUuidOrPath(idOrPath).get(); 14 | var version, fileId, file; 15 | 16 | creed.merge(function(blobId, fileV) { 17 | file = fileV; 18 | var previousId = file ? file.version : null; 19 | version = { 20 | userAccountId: userAccount.id, 21 | date: new Date(), 22 | blobId: blobId, 23 | creatorId: userAccount.id, 24 | previousId: previousId, 25 | }; 26 | version.id = Version.createHash(version); 27 | return Version.insert(version).execWithin(tx); 28 | }, blobIdP, fileP).then(function() { 29 | triggerIntentionalError(); 30 | if (!file) { 31 | var splitPath = idOrPath.split('/'); 32 | var fileName = splitPath[splitPath.length - 1]; 33 | var newId = uuid.v1(); 34 | return self.createQuery(idOrPath, { 35 | id: newId, 36 | userAccountId: userAccount.id, 37 | name: fileName, 38 | version: version.id 39 | }).then(function(q) { 40 | return q.execWithin(tx); 41 | }).then(function() { 42 | return newId; 43 | }); 44 | } else { 45 | return file.id; 46 | } 47 | }).then(function(fileIdV) { 48 | triggerIntentionalError(); 49 | fileId = fileIdV; 50 | return FileVersion.insert({ 51 | fileId: fileId, 52 | versionId: version.id 53 | }).execWithin(tx); 54 | }).then(function() { 55 | triggerIntentionalError(); 56 | return File.whereUpdate({id: fileId}, {version: version.id}) 57 | .execWithin(tx); 58 | }).then(function() { 59 | triggerIntentionalError(); 60 | tx.commit(); 61 | return done(); 62 | }).then(null, function(err) { 63 | tx.rollback(); 64 | return done(err); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/promises-ecmascript6-native.js: -------------------------------------------------------------------------------- 1 | global.useNative = true; 2 | 3 | try { 4 | if (Promise.race.toString() !== 'function race() { [native code] }') 5 | throw 0; 6 | } catch (e) { 7 | throw new Error("No ES6 promises available"); 8 | } 9 | 10 | require('../lib/fakesP'); 11 | 12 | module.exports = function upload(stream, idOrPath, tag, done) { 13 | var blob = blobManager.create(account); 14 | var tx = db.begin(); 15 | var blobIdP = blob.put(stream); 16 | var fileP = self.byUuidOrPath(idOrPath).get(); 17 | var version, fileId, file; 18 | 19 | Promise.all([blobIdP, fileP]).then(function(result) { 20 | var blobId = result[0]; 21 | var fileV = result[1]; 22 | file = fileV; 23 | var previousId = file ? file.version : null; 24 | version = { 25 | userAccountId: userAccount.id, 26 | date: new Date(), 27 | blobId: blobId, 28 | creatorId: userAccount.id, 29 | previousId: previousId, 30 | }; 31 | version.id = Version.createHash(version); 32 | return Version.insert(version).execWithin(tx); 33 | }).then(function() { 34 | if (!file) { 35 | var splitPath = idOrPath.split('/'); 36 | var fileName = splitPath[splitPath.length - 1]; 37 | var newId = uuid.v1(); 38 | return self.createQuery(idOrPath, { 39 | id: newId, 40 | userAccountId: userAccount.id, 41 | name: fileName, 42 | version: version.id 43 | }).then(function(q) { 44 | return q.execWithin(tx); 45 | }).then(function() { 46 | return newId; 47 | }); 48 | } else { 49 | return file.id; 50 | } 51 | }).then(function(fileIdV) { 52 | fileId = fileIdV; 53 | return FileVersion.insert({ 54 | fileId: fileId, 55 | versionId: version.id 56 | }).execWithin(tx); 57 | }).then(function() { 58 | return File.whereUpdate({id: fileId}, {version: version.id}) 59 | .execWithin(tx); 60 | }).then(function() { 61 | tx.commit(); 62 | return done(); 63 | }, function(err) { 64 | tx.rollback(); 65 | return done(err); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /perf/doxbee-sequential-errors/promises-creed-algebraic.js: -------------------------------------------------------------------------------- 1 | global.useCreed = true; 2 | global.useQ = false; 3 | global.useBluebird = false; 4 | 5 | var creed = require('../..'); 6 | 7 | require('../lib/fakesP'); 8 | 9 | module.exports = function upload(stream, idOrPath, tag, done) { 10 | var blob = blobManager.create(account); 11 | var tx = db.begin(); 12 | var blobIdP = blob.put(stream); 13 | var fileP = self.byUuidOrPath(idOrPath).get(); 14 | var version, fileId, file; 15 | 16 | creed.merge(function(blobId, fileV) { 17 | file = fileV; 18 | var previousId = file ? file.version : null; 19 | version = { 20 | userAccountId: userAccount.id, 21 | date: new Date(), 22 | blobId: blobId, 23 | creatorId: userAccount.id, 24 | previousId: previousId, 25 | }; 26 | version.id = Version.createHash(version); 27 | return Version.insert(version).execWithin(tx); 28 | }, blobIdP, fileP).chain(function() { 29 | triggerIntentionalError(); 30 | if (!file) { 31 | var splitPath = idOrPath.split('/'); 32 | var fileName = splitPath[splitPath.length - 1]; 33 | var newId = uuid.v1(); 34 | return self.createQuery(idOrPath, { 35 | id: newId, 36 | userAccountId: userAccount.id, 37 | name: fileName, 38 | version: version.id 39 | }).chain(function(q) { 40 | return q.execWithin(tx); 41 | }).map(function() { 42 | return newId; 43 | }); 44 | } else { 45 | return creed.fulfill(file.id); 46 | } 47 | }).chain(function(fileIdV) { 48 | triggerIntentionalError(); 49 | fileId = fileIdV; 50 | return FileVersion.insert({ 51 | fileId: fileId, 52 | versionId: version.id 53 | }).execWithin(tx); 54 | }).chain(function() { 55 | triggerIntentionalError(); 56 | return File.whereUpdate({id: fileId}, {version: version.id}) 57 | .execWithin(tx); 58 | }).map(function() { 59 | triggerIntentionalError(); 60 | tx.commit(); 61 | return done(); 62 | }).then(null, function(err) { 63 | tx.rollback(); 64 | return done(err); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /perf/lib/fakesObservable.js: -------------------------------------------------------------------------------- 1 | var lifter, fromNodeCallback; 2 | if (global.useRx) { 3 | lifter = require("rx").Observable.fromNodeCallback; 4 | } else if (global.useBacon) { 5 | fromNodeCallback = require("baconjs").fromNodeCallback; 6 | } else if (global.useKefir) { 7 | fromNodeCallback = require("kefir").Kefir.fromNodeCallback; 8 | lifter = function(nodeFn) { 9 | return function() { 10 | var args = [].slice.call(arguments); 11 | function thunk(callback) { 12 | args.push(callback); 13 | nodeFn.apply(null, args); 14 | args.pop(); 15 | } 16 | return fromNodeCallback(thunk); 17 | } 18 | }; 19 | } else if (global.useHighland) { 20 | lifter = require("highland").wrapCallback; 21 | } else if (global.useMost) { 22 | var most = require('most'); 23 | var wn = require('when/node'); 24 | lifter = function(nodeFn) { 25 | var lifted = wn.lift(nodeFn); 26 | return function() { 27 | return most.fromPromise(lifted.apply(this, arguments)); 28 | }; 29 | }; 30 | // lifter = function(nodeFn) { 31 | // return function() { 32 | // var self = this; 33 | // var l = arguments.length; 34 | // var a = new Array(l); 35 | // for(var i=0; i { 6 | it('should call resolver synchronously', () => { 7 | let called = false 8 | const p = new Promise((resolve, reject) => { 9 | called = true 10 | resolve() 11 | }) 12 | assert(called) 13 | return p 14 | }) 15 | 16 | it('should reject if resolver throws synchronously', () => { 17 | const expected = new Error() 18 | return new Promise(() => { throw expected }) 19 | .then(assert.ifError, x => assert.strictEqual(expected, x)) 20 | }) 21 | 22 | describe('resolvers', () => { 23 | it('should fulfill with value', () => { 24 | const expected = {} 25 | return new Promise(resolve => resolve(expected)) 26 | .then(x => assert.strictEqual(expected, x)) 27 | }) 28 | 29 | it('should resolve to fulfilled promise', () => { 30 | const expected = {} 31 | return new Promise(resolve => resolve(fulfill(expected))) 32 | .then(x => assert.strictEqual(expected, x)) 33 | }) 34 | 35 | it('should resolve to rejected promise', () => { 36 | const expected = new Error() 37 | return new Promise(resolve => resolve(reject(expected))) 38 | .then(assert.ifError, x => assert.strictEqual(expected, x)) 39 | }) 40 | 41 | it('should reject with value', () => { 42 | const expected = new Error() 43 | return new Promise((resolve, reject) => reject(expected)) 44 | .then(assert.ifError, x => assert.strictEqual(expected, x)) 45 | }) 46 | 47 | it('should asynchronously fulfill with value', () => { 48 | const expected = {} 49 | return new Promise(resolve => setTimeout(resolve, 1, expected)) 50 | .then(x => assert.strictEqual(expected, x)) 51 | }) 52 | 53 | it('should asynchronously resolve to fulfilled promise', () => { 54 | const expected = {} 55 | return new Promise(resolve => setTimeout(resolve, 1, fulfill(expected))) 56 | .then(x => assert.strictEqual(expected, x)) 57 | }) 58 | 59 | it('should asynchronously resolve to rejected promise', () => { 60 | const expected = new Error() 61 | return new Promise(resolve => setTimeout(resolve, 1, reject(expected))) 62 | .then(assert.ifError, x => assert.strictEqual(expected, x)) 63 | }) 64 | 65 | it('should asynchronously reject with value', () => { 66 | const expected = new Error() 67 | return new Promise((resolve, reject) => setTimeout(reject, 1, reject(expected))) 68 | .then(assert.ifError, x => assert.strictEqual(expected, x)) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /perf/lib/fakesP-ctx.js: -------------------------------------------------------------------------------- 1 | var timers = require('./timers-ctx'); 2 | 3 | var fakemaker = require('./fakemaker'); 4 | 5 | var f = {}; 6 | f.dummy = function dummy(n) { 7 | return function dummy_n() { 8 | var cb = arguments[n - 1], 9 | ctx = arguments[n]; 10 | timers.setTimeout(cb, ctx, global.asyncTime || 100); 11 | } 12 | } 13 | 14 | // A throwing callback function 15 | f.dummyt = function dummyt(n) { 16 | return function dummy_throwing_n() { 17 | var cb = arguments[n - 1], 18 | ctx = arguments[n]; 19 | if (global.testThrow) 20 | throw(new Error("Exception happened")); 21 | setTimeout(function throwTimeout() { 22 | if (global.testThrowAsync) { 23 | throw(new Error("Exception happened")); 24 | } else if (global.testError) { 25 | return cb.call(ctx, new Error("Error happened")); 26 | } 27 | else cb.call(ctx); 28 | }, global.asyncTime || 100); 29 | } 30 | } 31 | 32 | 33 | 34 | //Currently promisifies only Node style callbacks 35 | //var lifter = require('bluebird').promisify; 36 | 37 | var Promise = require('bluebird'); 38 | 39 | function nodeback(err, result) { 40 | if (err == null) this.fulfill(result); 41 | else this.reject(err); 42 | } 43 | 44 | function lifter(f) { 45 | return function lifted(a1, a2, a3, a4, a5) { 46 | "use strict"; 47 | var len = arguments.length; 48 | var deferred = Promise.pending(); 49 | try { 50 | switch(len) { 51 | case 1: f(a1, nodeback, deferred); break; 52 | case 2: f(a1, a2, nodeback, deferred); break; 53 | case 0: f(nodeback, deferred); break; 54 | case 3: f(a1, a2, a3, nodeback, deferred); break; 55 | case 4: f(a1, a2, a3, a4, nodeback, deferred); break; 56 | case 5: f(a1, a2, a3, a4, a5, nodeback, deferred); break; 57 | } 58 | } catch (err) { deferred.reject(err); } 59 | return deferred.promise; 60 | } 61 | } 62 | 63 | // A function taking n values or promises and returning 64 | // a promise 65 | function dummyP(n) { 66 | return function lifted() { 67 | var deferred = Promise.pending(); 68 | timers.setTimeout(nodeback, deferred, global.asyncTime || 100); 69 | return deferred.promise; 70 | } 71 | } 72 | 73 | // Throwing version of above 74 | function dummytP(n) { 75 | return lifter(f.dummyt(n)); 76 | } 77 | 78 | fakemaker(dummyP, dummytP, lifter); 79 | 80 | -------------------------------------------------------------------------------- /test/bimap-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import assert from 'assert' 3 | import { fulfill, reject, future, delay, never } from '../src/main' 4 | import { assertSame, assertSameRejected } from './lib/test-util' 5 | 6 | const id = x => x 7 | 8 | const f = x => x + 'f' 9 | const g = x => x + 'g' 10 | const h = x => x + 'h' 11 | const i = x => x + 'i' 12 | 13 | describe('bimap', () => { 14 | it('should satisfy identity for fulfilled', () => { 15 | const p = fulfill({}) 16 | return assertSame(p.bimap(id, id), p) 17 | }) 18 | 19 | it('should satisfy identity for rejected', () => { 20 | const p = reject({}) 21 | return assertSameRejected(p.bimap(id, id), p) 22 | }) 23 | 24 | it('should satisfy composition for fulfilled', () => { 25 | const p = fulfill('e') 26 | return assertSame(p.bimap(a => f(g(a)), b => h(i(b))), p.bimap(g, i).bimap(f, h)) 27 | }) 28 | 29 | it('should satisfy composition for rejected', () => { 30 | const p = reject('e') 31 | return assertSameRejected(p.bimap(a => f(g(a)), b => h(i(b))), p.bimap(g, i).bimap(f, h)) 32 | }) 33 | 34 | it('should map future fulfilled value', () => { 35 | const { resolve, promise } = future() 36 | resolve(delay(1, 1)) 37 | return promise.bimap(assert.ifError, x => x + 1) 38 | .then(x => assert.strictEqual(x, 2)) 39 | }) 40 | 41 | it('should map future rejected value', () => { 42 | const { resolve, promise } = future() 43 | resolve(delay(1, 1).then(reject)) 44 | return promise.bimap(x => x + 1, id) 45 | .then(assert.ifError, x => assert.strictEqual(x, 2)) 46 | }) 47 | 48 | it('should reject if f throws', () => { 49 | const p = fulfill() 50 | const expected = {} 51 | const fail = {} 52 | return p.bimap(e => fail, () => { throw expected }) 53 | .then(assert.ifError, x => assert.strictEqual(expected, x)) 54 | }) 55 | 56 | it('should reject if r throws', () => { 57 | const p = reject() 58 | const expected = {} 59 | return p.bimap(() => { throw expected }, id) 60 | .then(assert.ifError, x => assert.strictEqual(expected, x)) 61 | }) 62 | 63 | it('should fulfill with returned promise verbatim', () => { 64 | const p = fulfill() 65 | const expected = never() 66 | return p.bimap(assert.ifError, () => expected) 67 | .then(x => assert.strictEqual(expected, x)) 68 | }) 69 | 70 | it('should reject with returned promise verbatim', () => { 71 | const p = reject() 72 | const expected = never() 73 | return p.bimap(() => expected, id) 74 | .then(assert.ifError, x => assert.strictEqual(expected, x)) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /perf/doxbee-sequential/callbacks-caolan-async-waterfall.js: -------------------------------------------------------------------------------- 1 | require('../lib/fakes'); 2 | var async = require('async'); 3 | 4 | module.exports = function upload(stream, idOrPath, tag, done) { 5 | var blob = blobManager.create(account); 6 | var tx = db.begin(); 7 | var blobId, file, version, fileId; 8 | async.waterfall([ 9 | function writeBlob(callback) { 10 | blob.put(stream, callback); 11 | }, 12 | function afterBlobWritten(callback) { 13 | blobId = undefined // iBlobId; 14 | self.byUuidOrPath(idOrPath).get(callback); 15 | }, 16 | function afterFileFetched(callback) { 17 | file = undefined; //iFile; 18 | var previousId = file ? file.version : null; 19 | version = { 20 | userAccountId: userAccount.id, 21 | date: new Date(), 22 | blobId: blobId, 23 | creatorId: userAccount.id, 24 | previousId: previousId, 25 | mergedId: null, 26 | mergeType: 'mine', 27 | comment: '', 28 | tag: tag 29 | }; 30 | version.id = Version.createHash(version); 31 | Version.insert(version).execWithin(tx, callback); 32 | }, 33 | function afterVersionInserted(callback) { 34 | if (!file) { 35 | var splitPath = idOrPath.split('/'); 36 | var fileName = splitPath[splitPath.length - 1]; 37 | var newId = uuid.v1(); 38 | self.createQuery(idOrPath, { 39 | id: newId, 40 | userAccountId: userAccount.id, 41 | type: 'file', 42 | name: fileName, 43 | version: version.id 44 | }, function (err, q) { 45 | if (err) return backoff(err); 46 | q.execWithin(tx, function (err) { 47 | callback(err, newId); 48 | }); 49 | 50 | }) 51 | } 52 | else return callback(null, file.id); 53 | }, 54 | function afterFileExists(iFileId, callback) { 55 | fileId = iFileId; 56 | FileVersion.insert({fileId: fileId, versionId: version.id}) 57 | .execWithin(tx, callback); 58 | }, 59 | function afterFileVersionInserted(callback) { 60 | File.whereUpdate({id: fileId}, { version: version.id }) 61 | .execWithin(tx, callback); 62 | }, 63 | function afterFileUpdated(callback) { 64 | tx.commit(callback); 65 | } 66 | ], 67 | function (err) { 68 | if (err) tx.rollback(); 69 | done(err); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /perf/doxbee-sequential-errors/callbacks-caolan-async-waterfall.js: -------------------------------------------------------------------------------- 1 | require('../lib/fakes'); 2 | var async = require('async'); 3 | 4 | module.exports = function upload(stream, idOrPath, tag, done) { 5 | var blob = blobManager.create(account); 6 | var tx = db.begin(); 7 | var blobId, file, version, fileId; 8 | async.waterfall([ 9 | function writeBlob(callback) { 10 | blob.put(stream, callback); 11 | }, 12 | function afterBlobWritten(callback) { 13 | blobId = undefined // iBlobId; 14 | self.byUuidOrPath(idOrPath).get(callback); 15 | }, 16 | function afterFileFetched(callback) { 17 | file = undefined; //iFile; 18 | var previousId = file ? file.version : null; 19 | version = { 20 | userAccountId: userAccount.id, 21 | date: new Date(), 22 | blobId: blobId, 23 | creatorId: userAccount.id, 24 | previousId: previousId, 25 | mergedId: null, 26 | mergeType: 'mine', 27 | comment: '', 28 | tag: tag 29 | }; 30 | version.id = Version.createHash(version); 31 | Version.insert(version).execWithin(tx, callback); 32 | }, 33 | function afterVersionInserted(callback) { 34 | if (!file) { 35 | var splitPath = idOrPath.split('/'); 36 | var fileName = splitPath[splitPath.length - 1]; 37 | var newId = uuid.v1(); 38 | self.createQuery(idOrPath, { 39 | id: newId, 40 | userAccountId: userAccount.id, 41 | type: 'file', 42 | name: fileName, 43 | version: version.id 44 | }, function (err, q) { 45 | if (err) return backoff(err); 46 | q.execWithin(tx, function (err) { 47 | callback(err, newId); 48 | }); 49 | 50 | }) 51 | } 52 | else return callback(null, file.id); 53 | }, 54 | function afterFileExists(iFileId, callback) { 55 | fileId = iFileId; 56 | FileVersion.insert({fileId: fileId, versionId: version.id}) 57 | .execWithin(tx, callback); 58 | }, 59 | function afterFileVersionInserted(callback) { 60 | File.whereUpdate({id: fileId}, { version: version.id }) 61 | .execWithin(tx, callback); 62 | }, 63 | function afterFileUpdated(callback) { 64 | tx.commit(callback); 65 | } 66 | ], 67 | function (err) { 68 | if (err) tx.rollback(); 69 | done(err); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /perf/doxbee-sequential-errors/callbacks-baseline.js: -------------------------------------------------------------------------------- 1 | require('../lib/fakes'); 2 | 3 | module.exports = function upload(stream, idOrPath, tag, done) { 4 | var blob = blobManager.create(account); 5 | var tx = db.begin(); 6 | function backoff(err) { 7 | tx.rollback(); 8 | return done(err); 9 | } 10 | var intentionalErrorShouldBeTriggered = function(){ 11 | return LIKELIHOOD_OF_REJECTION && Math.random() <= LIKELIHOOD_OF_REJECTION; 12 | } 13 | 14 | blob.put(stream, function (err, blobId) { 15 | if (err) return done(err); 16 | self.byUuidOrPath(idOrPath).get(function (err, file) { 17 | if (err) return done(err); 18 | var previousId = file ? file.version : null; 19 | var version = { 20 | userAccountId: userAccount.id, 21 | date: new Date(), 22 | blobId: blobId, 23 | creatorId: userAccount.id, 24 | previousId: previousId, 25 | }; 26 | version.id = Version.createHash(version); 27 | Version.insert(version).execWithin(tx, function (err) { 28 | if (err) return backoff(err); 29 | if(intentionalErrorShouldBeTriggered()) return done(new Error("intentional failure")); 30 | if (!file) { 31 | var splitPath = idOrPath.split('/'); 32 | var fileName = splitPath[splitPath.length - 1]; 33 | var newId = uuid.v1(); 34 | self.createQuery(idOrPath, { 35 | id: newId, 36 | userAccountId: userAccount.id, 37 | name: fileName, 38 | version: version.id 39 | }, function (err, q) { 40 | if (err) return backoff(err); 41 | if(intentionalErrorShouldBeTriggered()) return done(new Error("intentional failure")); 42 | q.execWithin(tx, function (err) { 43 | afterFileExists(err, newId); 44 | }); 45 | 46 | }) 47 | } 48 | else return afterFileExists(null, file.id); 49 | }); 50 | function afterFileExists(err, fileId) { 51 | if (err) return backoff(err); 52 | if(intentionalErrorShouldBeTriggered()) return done(new Error("intentional failure")); 53 | FileVersion.insert({fileId: fileId,versionId: version.id}) 54 | .execWithin(tx, function (err) { 55 | if (err) return backoff(err); 56 | if(intentionalErrorShouldBeTriggered()) return done(new Error("intentional failure")); 57 | File.whereUpdate({id: fileId}, { 58 | version: version.id 59 | }).execWithin(tx, function (err) { 60 | if (err) return backoff(err); 61 | if(intentionalErrorShouldBeTriggered()) return done(new Error("intentional failure")); 62 | tx.commit(done); 63 | }); 64 | }) 65 | } 66 | }); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /test/runPromise-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { runPromise, resolve } from '../src/main' 3 | import assert from 'assert' 4 | 5 | let fail = x => { throw x } 6 | 7 | describe('runPromise', () => { 8 | it('should throw synchronously when function not provided', () => { 9 | assert.throws(runPromise, TypeError) 10 | }) 11 | 12 | it('should reject if resolver throws', () => { 13 | let x = {} 14 | return runPromise(fail, x).then(fail, e => assert(x === e)) 15 | }) 16 | 17 | it('should reject', () => { 18 | let x = {} 19 | return runPromise((_, reject) => { 20 | reject(x) 21 | }).then(fail, e => assert(x === e)) 22 | }) 23 | 24 | it('should resolve', () => { 25 | let x = {} 26 | return runPromise(resolve => resolve(x)) 27 | .then(a => assert(x === a)) 28 | }) 29 | 30 | describe('when rejected explicitly', () => { 31 | it('should ignore subsequent throw', () => { 32 | let x = {} 33 | return runPromise((_, reject) => { 34 | reject(x) 35 | throw new Error() 36 | }).then(fail, e => assert(x === e)) 37 | }) 38 | 39 | it('should ignore subsequent reject', () => { 40 | let x = {} 41 | let y = {} 42 | return runPromise((_, reject) => { 43 | reject(x) 44 | reject(y) 45 | }).then(fail, e => assert(x === e)) 46 | }) 47 | 48 | it('should ignore subsequent resolve', () => { 49 | let x = {} 50 | return runPromise((_, reject) => { 51 | reject(x) 52 | resolve() 53 | }).then(fail, e => assert(x === e)) 54 | }) 55 | }) 56 | 57 | describe('when resolved explicitly', () => { 58 | it('should ignore subsequent throw', () => { 59 | let x = {} 60 | return runPromise(resolve => { 61 | resolve(x) 62 | throw new Error() 63 | }).then(a => assert(x === a)) 64 | }) 65 | 66 | it('should ignore subsequent reject', () => { 67 | let x = {} 68 | let y = {} 69 | return runPromise((resolve, reject) => { 70 | resolve(x) 71 | reject(y) 72 | }).then(a => assert(x === a)) 73 | }) 74 | 75 | it('should ignore subsequent resolve', () => { 76 | let x = {} 77 | return runPromise(resolve => { 78 | resolve(x) 79 | resolve() 80 | }).then(a => assert(x === a)) 81 | }) 82 | }) 83 | 84 | describe('should pass arguments to resolver', () => { 85 | it('for 1 argument', () => { 86 | let a = {} 87 | return runPromise((w, resolve) => { 88 | assert(w === a) 89 | resolve() 90 | }, a) 91 | }) 92 | 93 | it('for 2 arguments', () => { 94 | let a = {} 95 | let b = {} 96 | return runPromise((w, x, resolve) => { 97 | assert(w === a) 98 | assert(x === b) 99 | resolve() 100 | }, a, b) 101 | }) 102 | 103 | it('for 3 arguments', () => { 104 | let a = {} 105 | let b = {} 106 | let c = {} 107 | return runPromise((w, x, y, resolve) => { 108 | assert(w === a) 109 | assert(x === b) 110 | assert(y === c) 111 | resolve() 112 | }, a, b, c) 113 | }) 114 | 115 | it('for 4 or more arguments', () => { 116 | let a = {} 117 | let b = {} 118 | let c = {} 119 | let d = {} 120 | return runPromise((w, x, y, z, resolve) => { 121 | assert(w === a) 122 | assert(x === b) 123 | assert(y === c) 124 | assert(z === d) 125 | resolve() 126 | }, a, b, c, d) 127 | }) 128 | }) 129 | }) 130 | -------------------------------------------------------------------------------- /perf/lib/fakesP.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | if (global.useQ) 4 | var lifter = require('q').denodeify; 5 | else if (global.useBluebird) 6 | //Currently promisifies only Node style callbacks 7 | var lifter = require('bluebird').promisify; 8 | else if (global.useKew) { 9 | var q = require('kew'); 10 | var slicer = [].slice; 11 | var lifter = function lifter(nodefn) { 12 | return function() { 13 | var p = q.defer(); 14 | arguments[arguments.length++] = function(err, res) { 15 | if (err) p.reject(err); 16 | else p.resolve(res) 17 | }; 18 | try { 19 | nodefn.apply(this, arguments); 20 | } 21 | catch (e) { 22 | p.reject(e); 23 | } 24 | return p; 25 | } 26 | } 27 | } 28 | else if(global.useLie) { 29 | var Lie = require('lie'); 30 | var lifter = function(nodefn) { 31 | return function() { 32 | var self = this; 33 | var l = arguments.length; 34 | var args = new Array(l + 1); 35 | for (var i = 0; i < l; ++i) { 36 | args[i] = arguments[i]; 37 | } 38 | return new Lie(function(resolve, reject) { 39 | args[l] = function(err, val) { 40 | if (err) reject(err); 41 | else resolve(val); 42 | }; 43 | nodefn.apply(self, args); 44 | }); 45 | }; 46 | }; 47 | } 48 | else if(global.useThenPromise) { 49 | var lifter = require("promise").denodeify; 50 | } 51 | else if( global.useRSVP ) { 52 | var lifter = require("rsvp").denodeify; 53 | } 54 | else if( global.useDeferred) { 55 | var lifter = require("deferred").promisify; 56 | } 57 | else if( global.useDavy) { 58 | var lifter = require("davy").wrap; 59 | } 60 | else if (global.useNative) { 61 | try { 62 | if (Promise.race.toString() !== 'function race() { [native code] }') 63 | throw 0; 64 | } catch (e) { 65 | throw new Error("No ES6 promises available"); 66 | } 67 | var lifter = function(nodefn) { 68 | return function() { 69 | var self = this; 70 | var l = arguments.length; 71 | var args = new Array(l + 1); 72 | for (var i = 0; i < l; ++i) { 73 | args[i] = arguments[i]; 74 | } 75 | return new Promise(function(resolve, reject) { 76 | args[l] = function(err, val) { 77 | if (err) reject(err); 78 | else resolve(val); 79 | }; 80 | nodefn.apply(self, args); 81 | }); 82 | }; 83 | }; 84 | } 85 | else if (global.useCreed) { 86 | var lifter = require('../..').fromNode; 87 | } 88 | else { 89 | var lifter = require('when/node').lift; 90 | } 91 | 92 | var f = require('./dummy'); 93 | 94 | var makefakes = require('./fakemaker'); 95 | 96 | // A function taking n values or promises and returning 97 | // a promise 98 | function dummyP(n) { 99 | return lifter(f.dummy(n)); 100 | } 101 | 102 | // Throwing version of above 103 | function dummytP(n) { 104 | return lifter(f.dummyt(n)); 105 | } 106 | 107 | makefakes(dummyP, dummytP, lifter); 108 | -------------------------------------------------------------------------------- /perf/README.md: -------------------------------------------------------------------------------- 1 | Latest results, using latest versions of modules: 2 | 3 | ├── async@2.0.0 4 | ├── bluebird@3.4.1 5 | ├── co@4.6.0 6 | ├── rsvp@3.2.1 7 | └── when@3.7.7 8 | 9 | bench doxbee-sequential 10 | 11 | results for 10000 parallel executions, 1 ms per I/O op 12 | 13 | file time(ms) memory(MB) 14 | callbacks-baseline.js 103 30.72 15 | promises-creed-generator.js 196 33.27 16 | promises-creed.js 239 47.87 17 | promises-creed-algebraic.js 240 49.70 18 | promises-bluebird-generator.js 247 37.48 19 | callbacks-caolan-async-waterfall.js 268 48.66 20 | promises-bluebird.js 276 48.66 21 | promises-cujojs-when-generator.js 278 39.13 22 | promises-cujojs-when.js 344 62.57 23 | promises-tildeio-rsvp.js 426 74.50 24 | generators-tj-co.js 968 134.20 25 | promises-ecmascript6-native.js 1064 183.98 26 | 27 | Platform info: 28 | Darwin 15.5.0 x64 29 | Node.JS 6.3.0 30 | V8 5.0.71.52 31 | Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz × 8 32 | 33 | bench parallel (`--p 25`) 34 | 35 | results for 10000 parallel executions, 1 ms per I/O op 36 | 37 | file time(ms) memory(MB) 38 | promises-creed.js 401 118.38 39 | promises-creed-generator.js 456 116.59 40 | promises-bluebird.js 484 101.09 41 | promises-bluebird-generator.js 532 102.83 42 | promises-cujojs-when.js 753 160.36 43 | promises-cujojs-when-generator.js 816 163.04 44 | callbacks-caolan-async-parallel.js 825 201.21 45 | callbacks-baseline.js 911 43.76 46 | promises-tildeio-rsvp.js 1546 386.39 47 | promises-ecmascript6-native.js 2107 454.37 48 | 49 | Platform info: 50 | Darwin 15.5.0 x64 51 | Node.JS 6.3.0 52 | V8 5.0.71.52 53 | Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz × 8 54 | 55 | bench doxbee-errors 56 | 57 | results for 10000 parallel executions, 1 ms per I/O op 58 | Likelihood of rejection: 0.1 59 | 60 | file time(ms) memory(MB) 61 | callbacks-baseline.js 128 30.35 62 | promises-creed-generator.js 221 32.26 63 | promises-creed.js 277 48.73 64 | promises-bluebird-generator.js 282 36.62 65 | callbacks-caolan-async-waterfall.js 283 48.82 66 | promises-creed-algebraic.js 297 49.88 67 | promises-bluebird.js 345 52.77 68 | promises-cujojs-when.js 357 65.69 69 | promises-tildeio-rsvp.js 476 87.50 70 | 71 | Platform info: 72 | Darwin 15.5.0 x64 73 | Node.JS 6.3.0 74 | V8 5.0.71.52 75 | Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz × 8 76 | 77 | --- 78 | 79 | ``` 80 | The MIT License (MIT) 81 | 82 | Copyright (c) 2014 Petka Antonov 83 | 84 | Permission is hereby granted, free of charge, to any person obtaining a copy 85 | of this software and associated documentation files (the "Software"), to deal 86 | in the Software without restriction, including without limitation the rights 87 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 88 | copies of the Software, and to permit persons to whom the Software is 89 | furnished to do so, subject to the following conditions: 90 | 91 | The above copyright notice and this permission notice shall be included in 92 | all copies or substantial portions of the Software. 93 | 94 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 95 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 96 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 97 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 98 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 99 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 100 | THE SOFTWARE. 101 | ``` 102 | -------------------------------------------------------------------------------- /test/fantasyland-laws-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import assert from 'assert' 3 | import { fulfill, reject, Promise } from '../src/main' 4 | import { isNever } from '../src/inspect' 5 | import { assertSame } from './lib/test-util' 6 | import * as Functor from 'fantasy-land/laws/functor' 7 | import * as Bifunctor from 'fantasy-land/laws/bifunctor' 8 | import * as Chain from 'fantasy-land/laws/chain' 9 | import * as Apply from 'fantasy-land/laws/apply' 10 | import * as Applicative from 'fantasy-land/laws/applicative' 11 | import * as Semigroup from 'fantasy-land/laws/semigroup' 12 | import * as Monoid from 'fantasy-land/laws/monoid' 13 | import * as Alt from 'fantasy-land/laws/alt' 14 | import * as Plus from 'fantasy-land/laws/plus' 15 | import * as Alternative from 'fantasy-land/laws/alternative' 16 | 17 | const assertIsNever = x => assert(isNever(x)) 18 | 19 | describe('fantasyland laws', () => { 20 | describe('functor', () => { 21 | it('should satisfy identity', () => { 22 | return Functor.identity(fulfill)(assertSame)({}) 23 | }) 24 | 25 | it('should satisfy composition', () => { 26 | const f = x => x + 'f' 27 | const g = x => x + 'g' 28 | return Functor.composition(fulfill)(assertSame)(f)(g)('x') 29 | }) 30 | }) 31 | 32 | describe('bifunctor', () => { 33 | it('should satisfy identity for fulfill', () => { 34 | return Bifunctor.identity(fulfill)(assertSame)({}) 35 | }) 36 | 37 | it('should satisfy identity for reject', () => { 38 | return Bifunctor.identity(reject)(assertSame)({}) 39 | }) 40 | 41 | it('should satisfy composition for fulfill', () => { 42 | return Bifunctor.composition(fulfill)(assertSame)({}) 43 | }) 44 | 45 | it('should satisfy composition for reject', () => { 46 | return Bifunctor.composition(reject)(assertSame)(new Error()) 47 | }) 48 | }) 49 | 50 | describe('apply', () => { 51 | it('should satisfy composition', () => { 52 | return Apply.composition(Promise)(assertSame)({}) 53 | }) 54 | }) 55 | 56 | describe('applicative', () => { 57 | it('should satisfy identity', () => { 58 | return Applicative.identity(Promise)(assertSame)({}) 59 | }) 60 | 61 | it('should satisfy homomorphism', () => { 62 | return Applicative.homomorphism(Promise)(assertSame)({}) 63 | }) 64 | 65 | it('should satisfy interchange', () => { 66 | return Applicative.interchange(Promise)(assertSame)({}) 67 | }) 68 | }) 69 | 70 | describe('chain', () => { 71 | it('should satisfy associativity', () => { 72 | return Chain.associativity(Promise)(assertSame)({}) 73 | }) 74 | }) 75 | 76 | describe('semigroup', () => { 77 | it('should satisfy associativity', () => { 78 | return Semigroup.associativity(fulfill)(assertSame)({}) 79 | }) 80 | }) 81 | 82 | describe('monoid', () => { 83 | it('should satisfy rightIdentity', () => { 84 | return Monoid.rightIdentity(Promise)(assertSame)({}) 85 | }) 86 | 87 | it('should satisfy leftIdentity', () => { 88 | return Monoid.leftIdentity(Promise)(assertSame)({}) 89 | }) 90 | }) 91 | 92 | describe('alt', () => { 93 | it('should satisfy associativity', () => { 94 | return Alt.associativity(assertSame)(fulfill(1))(fulfill(2))(fulfill(3)) 95 | }) 96 | 97 | it('should satisfy distributivity', () => { 98 | return Alt.distributivity(assertSame)(fulfill(1))(fulfill(-1))(x => x + 1) 99 | }) 100 | }) 101 | 102 | describe('plus', () => { 103 | it('should satisfy leftIdentity', () => { 104 | return Plus.leftIdentity(Promise)(assertSame)(fulfill({})) 105 | }) 106 | 107 | it('should satisfy rightIdentity', () => { 108 | return Plus.rightIdentity(Promise)(assertSame)(fulfill({})) 109 | }) 110 | 111 | it('should satisfy annihilation', () => { 112 | return Plus.annihilation(Promise)(assertIsNever)(x => x + 1) 113 | }) 114 | }) 115 | 116 | describe('alternative', () => { 117 | it('should satisfy distributivity', () => { 118 | return Alternative.distributivity(p => p.then(x => assert.strictEqual(1, x)))(fulfill(0))(fulfill(x => x + 1))(fulfill(x => x - 1)) 119 | }) 120 | 121 | it('should satisfy annihilation', () => { 122 | return Plus.annihilation(Promise)(assertIsNever)(fulfill({})) 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /test/inspect-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { isFulfilled, isRejected, isSettled, isPending, isHandled, isNever, silenceError, getValue, getReason } from '../src/inspect' 3 | import { resolve, reject, fulfill, never, Future } from '../src/Promise' 4 | import assert from 'assert' 5 | 6 | describe('inspect', () => { 7 | describe('isFulfilled', () => { 8 | it('should be true for fulfilled promise', () => { 9 | assert(isFulfilled(resolve())) 10 | }) 11 | 12 | it('should be false for rejected promise', () => { 13 | assert(!isFulfilled(reject())) 14 | }) 15 | 16 | it('should be false for pending promise', () => { 17 | assert(!isFulfilled(new Future())) 18 | }) 19 | 20 | it('should be false for never', () => { 21 | assert(!isFulfilled(never())) 22 | }) 23 | }) 24 | 25 | describe('isRejected', () => { 26 | it('should be true for rejected promise', () => { 27 | assert(isRejected(reject())) 28 | }) 29 | 30 | it('should be false for fulfilled promise', () => { 31 | assert(!isRejected(resolve())) 32 | }) 33 | 34 | it('should be false for pending promise', () => { 35 | assert(!isRejected(new Future())) 36 | }) 37 | 38 | it('should be false for never', () => { 39 | assert(!isRejected(never())) 40 | }) 41 | }) 42 | 43 | describe('isSettled', () => { 44 | it('should be true for fulfilled promise', () => { 45 | assert(isSettled(resolve())) 46 | }) 47 | 48 | it('should be true for rejected promise', () => { 49 | assert(isSettled(reject())) 50 | }) 51 | 52 | it('should be false for pending promise', () => { 53 | assert(!isSettled(new Future())) 54 | }) 55 | 56 | it('should be false for never', () => { 57 | assert(!isSettled(never())) 58 | }) 59 | }) 60 | 61 | describe('isPending', () => { 62 | it('should be false for fulfilled promise', () => { 63 | assert(!isPending(resolve())) 64 | }) 65 | 66 | it('should be false for rejected promise', () => { 67 | assert(!isPending(reject())) 68 | }) 69 | 70 | it('should be true for pending promise', () => { 71 | assert(isPending(new Future())) 72 | }) 73 | 74 | it('should be true for never', () => { 75 | assert(isPending(never())) 76 | }) 77 | }) 78 | 79 | describe('isNever', () => { 80 | it('should be false for fulfilled promise', () => { 81 | assert(!isNever(resolve())) 82 | }) 83 | 84 | it('should be false for rejected promise', () => { 85 | assert(!isNever(reject())) 86 | }) 87 | 88 | it('should be false for pending promise', () => { 89 | assert(!isNever(new Future())) 90 | }) 91 | 92 | it('should be true for never', () => { 93 | assert(isNever(never())) 94 | }) 95 | }) 96 | 97 | describe('isHandled', () => { 98 | it('should be false for fulfilled promise', () => { 99 | assert(!isHandled(resolve())) 100 | }) 101 | 102 | it('should be false for rejected promise', () => { 103 | assert(!isHandled(reject())) 104 | }) 105 | 106 | it('should be true for handled rejected promise', done => { 107 | const p = reject() 108 | p.catch(() => {}) 109 | .then(() => { 110 | assert(isHandled(p)) 111 | done() 112 | }) 113 | }) 114 | 115 | it('should be true for silenced rejected promise', () => { 116 | const p = reject() 117 | silenceError(p) 118 | assert(isHandled(p)) 119 | }) 120 | 121 | it('should be false for pending promise', () => { 122 | assert(!isHandled(new Future())) 123 | }) 124 | 125 | it('should be false for never', () => { 126 | assert(!isHandled(never())) 127 | }) 128 | }) 129 | 130 | describe('getValue', () => { 131 | it('should get value from fulfilled promise', () => { 132 | const x = {} 133 | assert.strictEqual(x, getValue(resolve(x))) 134 | }) 135 | 136 | it('should throw for rejected promise', () => { 137 | assert.throws(() => getValue(reject())) 138 | }) 139 | 140 | it('should throw for pending promise', () => { 141 | assert.throws(() => getValue(new Future())) 142 | }) 143 | 144 | it('should throw for never', () => { 145 | assert.throws(() => getValue(never())) 146 | }) 147 | }) 148 | 149 | describe('getReason', () => { 150 | it('should handle rejected promise', () => { 151 | const p = reject() 152 | assert(!isHandled(p)) 153 | 154 | getReason(p) 155 | assert(isHandled(p)) 156 | }) 157 | 158 | it('should get reason from rejected promise', () => { 159 | let x = {} 160 | assert.strictEqual(x, getReason(reject(x))) 161 | }) 162 | 163 | it('should throw for fulfilled promise', () => { 164 | assert.throws(() => getReason(fulfill())) 165 | }) 166 | 167 | it('should throw for pending promise', () => { 168 | assert.throws(() => getReason(new Future())) 169 | }) 170 | 171 | it('should throw for never', () => { 172 | assert.throws(() => getReason(never())) 173 | }) 174 | }) 175 | 176 | describe('silenceError', () => { 177 | it('should handle rejected promise', () => { 178 | const p = reject() 179 | assert(!isHandled(p)) 180 | 181 | silenceError(p) 182 | assert(isHandled(p)) 183 | }) 184 | 185 | it('should be a noop for fulfilled promise', () => { 186 | const p = resolve() 187 | assert(!isHandled(p)) 188 | 189 | silenceError(p) 190 | assert(!isHandled(p)) 191 | }) 192 | }) 193 | }) 194 | -------------------------------------------------------------------------------- /perf/performance.js: -------------------------------------------------------------------------------- 1 | 2 | var args = require('optimist').argv; 3 | var path = require('path'); 4 | 5 | global.LIKELIHOOD_OF_REJECTION = args.e || 0.1; 6 | global.triggerIntentionalError = function(){ 7 | if(LIKELIHOOD_OF_REJECTION && Math.random() <= LIKELIHOOD_OF_REJECTION) throw new Error("intentional failure"); 8 | } 9 | 10 | function printPlatform() { 11 | console.log("\nPlatform info:"); 12 | var os = require("os"); 13 | var v8 = process.versions.v8; 14 | var node = process.versions.node; 15 | var plat = os.type() + " " + os.release() + " " + os.arch() + "\nNode.JS " + node + "\nV8 " + v8; 16 | var cpus = os.cpus().map(function(cpu){ 17 | return cpu.model; 18 | }).reduce(function(o, model){ 19 | if( !o[model] ) o[model] = 0; 20 | o[model]++; 21 | return o; 22 | }, {}); 23 | cpus = Object.keys(cpus).map(function( key ){ 24 | return key + " \u00d7 " + cpus[key]; 25 | }).join("\n"); 26 | console.log(plat + "\n" + cpus + "\n"); 27 | } 28 | 29 | var perf = module.exports = function(args, done) { 30 | 31 | var errs = 0; 32 | var lastErr; 33 | var times = args.n; 34 | 35 | global.asyncTime = args.t; 36 | global.parallelQueries = args.p || 10; 37 | 38 | if (args.longStackSupport) { 39 | global.longStackSupport = require('q').longStackSupport 40 | = args.longStackSupport; 41 | require('bluebird').longStackTraces(); 42 | } 43 | 44 | var fn = require(args.file); 45 | 46 | var start = Date.now(); 47 | 48 | 49 | var warmedUp = 0; 50 | var tot = Math.min( 350, times ); 51 | for (var k = 0, kn = tot; k < kn; ++k) 52 | fn(k,'b','c', warmup); 53 | 54 | var memMax; var memStart; var start; 55 | function warmup() { 56 | warmedUp++ 57 | if( warmedUp === tot ) { 58 | start = Date.now(); 59 | 60 | memStart = process.memoryUsage().rss; 61 | for (var k = 0, kn = args.n; k < kn; ++k) 62 | fn(k, 'b', 'c', cb); 63 | memMax = process.memoryUsage().rss; 64 | } 65 | } 66 | 67 | function cb (err) { 68 | if (err && err.message !== "intentional failure") { 69 | ++errs; 70 | lastErr = err; 71 | } 72 | memMax = Math.max(memMax, process.memoryUsage().rss); 73 | if (!--times) { 74 | fn.end && fn.end(); 75 | done(null, { 76 | time: Date.now() - start, 77 | mem: (memMax - memStart)/1024/1024, 78 | errors: errs, 79 | lastErr: lastErr ? lastErr.stack : null 80 | }); 81 | } 82 | } 83 | } 84 | 85 | 86 | function report(err, res) { 87 | console.log(JSON.stringify(res)); 88 | } 89 | 90 | if (args.file) { 91 | perf(args, function(err, res) { 92 | report(err, res); 93 | if (res.lastErr) 94 | console.error(res.lastErr); 95 | }); 96 | } else { 97 | var cp = require('child_process') 98 | var async = require('async'); 99 | var fs = require('fs'); 100 | var dir = __dirname + '/examples'; 101 | 102 | var table = require('text-table'); 103 | 104 | 105 | var files = args._.filter(function(f) { 106 | return !/^src-/.test(path.basename(f)); 107 | }); 108 | 109 | measure(files, args.n, args.t, args.p, function(err, res) { 110 | console.log(""); 111 | console.log("results for", args.n, "parallel executions,", 112 | args.t, "ms per I/O op"); 113 | if(args.e) console.log("Likelihood of rejection:", args.e); 114 | 115 | res.sort(function(r1, r2) { 116 | return parseFloat(r1.data.time) - parseFloat(r2.data.time) 117 | }); 118 | console.log(""); 119 | res = res.map(function(r) { 120 | var failText = 'OOM'; 121 | if (r.data.timeout) failText = 'T/O'; 122 | return [path.basename(r.file), 123 | r.data.mem != null ? r.data.time: failText, 124 | r.data.mem != null ? r.data.mem.toFixed(2) : failText] 125 | }); 126 | 127 | res = [['file', 'time(ms)', 'memory(MB)']].concat(res) 128 | 129 | console.log(table(res, {align: ['l', 'r', 'r']})); 130 | printPlatform(); 131 | 132 | }); 133 | } 134 | 135 | 136 | function measure(files, requests, time, parg, callback) { 137 | async.mapSeries(files, function(f, done) { 138 | console.log("benchmarking", f); 139 | var logFile = path.basename(f) + ".log"; 140 | var profileFlags = ["--prof", "--logfile=logs/" + logFile]; 141 | 142 | var argsFork = [__filename, 143 | '--n', requests, 144 | '--t', time, 145 | '--p', parg, 146 | '--file', f]; 147 | if (args.profile) argsFork = profileFlags.concat(argsFork); 148 | if (args.harmony) argsFork.unshift('--harmony'); 149 | if (args.longStackSupport) argsFork.push('--longStackSupport'); 150 | var p = cp.spawn(process.execPath, argsFork); 151 | 152 | var complete = false, timedout = false; 153 | if (args.timeout) setTimeout(function() { 154 | if (complete) return; 155 | timedout = true; 156 | p.kill(); 157 | }, args.timeout); 158 | 159 | var r = { file: f, data: [] }; 160 | p.stdout.on('data', function(d) { r.data.push(d.toString()); }); 161 | p.stdout.pipe(process.stdout); 162 | p.stderr.pipe(process.stderr); 163 | p.stdout.on('end', function(code) { 164 | complete = true; 165 | try { 166 | r.data = JSON.parse(r.data.join('')); 167 | } catch(e) { 168 | r.data = {time: Number.POSITIVE_INFINITY, mem: null, 169 | missing: true, timeout: timedout}; 170 | } 171 | done(null, r); 172 | }); 173 | }, callback); 174 | } 175 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { isFulfilled, isRejected, isSettled, isPending, isNever, getValue, getReason } from './inspect' 2 | import { Future, resolve, reject, future, never, fulfill, all, race, iterablePromise, taskQueue } from './Promise' 3 | 4 | import _delay from './delay' 5 | import _timeout from './timeout' 6 | 7 | import Any from './Any' 8 | import Merge from './Merge' 9 | import Settle from './Settle' 10 | import { resultsArray } from './iterable' 11 | 12 | import _runPromise from './runPromise' 13 | import _runNode from './node' 14 | import _runCoroutine from './coroutine.js' 15 | 16 | // ------------------------------------------------------------- 17 | // ## Core promise methods 18 | // ------------------------------------------------------------- 19 | 20 | export { 21 | resolve, reject, future, never, fulfill, all, race, 22 | isFulfilled, isRejected, isSettled, isPending, isNever, 23 | getValue, getReason 24 | } 25 | 26 | // ------------------------------------------------------------- 27 | // ## Coroutine 28 | // ------------------------------------------------------------- 29 | 30 | // coroutine :: Generator e a -> (...* -> Promise e a) 31 | // Make a coroutine from a promise-yielding generator 32 | export function coroutine (generator) { 33 | return function coroutinified (...args) { 34 | return runGenerator(generator, this, args) 35 | } 36 | } 37 | 38 | function runGenerator (generator, thisArg, args) { 39 | const iterator = generator.apply(thisArg, args) 40 | return _runCoroutine(resolve, iterator, new Future()) 41 | } 42 | 43 | // ------------------------------------------------------------- 44 | // ## Node-style async 45 | // ------------------------------------------------------------- 46 | 47 | // type Nodeback e a = e -> a -> () 48 | // type NodeApi e a = ...* -> Nodeback e a -> () 49 | 50 | // fromNode :: NodeApi e a -> (...args -> Promise e a) 51 | // Turn a Node API into a promise API 52 | export function fromNode (f) { 53 | return function promisified (...args) { 54 | return runResolver(_runNode, f, this, args, new Future()) 55 | } 56 | } 57 | 58 | // runNode :: NodeApi e a -> ...* -> Promise e a 59 | // Run a Node API, returning a promise for the outcome 60 | export function runNode (f, ...args) { 61 | return runResolver(_runNode, f, this, args, new Future()) 62 | } 63 | 64 | // ------------------------------------------------------------- 65 | // ## Make a promise 66 | // ------------------------------------------------------------- 67 | 68 | // type Resolve e a = a|Thenable e a -> () 69 | // type Reject e = e -> () 70 | // type Producer e a = (...* -> Resolve e a -> Reject e -> ()) 71 | // runPromise :: Producer e a -> ...* -> Promise e a 72 | export function runPromise (f, ...args) { 73 | return runResolver(_runPromise, f, this, args, new Future()) 74 | } 75 | 76 | function runResolver (run, f, thisArg, args, p) { 77 | checkFunction(f) 78 | 79 | try { 80 | run(f, thisArg, args, p) 81 | } catch (e) { 82 | p._reject(e) 83 | } 84 | 85 | return p 86 | } 87 | 88 | // ------------------------------------------------------------- 89 | // ## Time 90 | // ------------------------------------------------------------- 91 | 92 | // delay :: number -> Promise e a -> Promise e a 93 | export function delay (ms, x) { 94 | /* eslint complexity:[2,4] */ 95 | const p = resolve(x) 96 | return ms <= 0 || isRejected(p) || isNever(p) ? p 97 | : _delay(ms, p, new Future()) 98 | } 99 | 100 | // timeout :: number -> Promise e a -> Promise (e|TimeoutError) a 101 | export function timeout (ms, x) { 102 | const p = resolve(x) 103 | return isSettled(p) ? p : _timeout(ms, p, new Future()) 104 | } 105 | 106 | // ------------------------------------------------------------- 107 | // ## Iterables 108 | // ------------------------------------------------------------- 109 | 110 | // any :: Iterable (Promise e a) -> Promise e a 111 | export function any (promises) { 112 | return iterablePromise(new Any(), promises) 113 | } 114 | 115 | // settle :: Iterable (Promise e a) -> Promise e [Promise e a] 116 | export function settle (promises) { 117 | const handler = new Settle(resolve, resultsArray(promises)) 118 | return iterablePromise(handler, promises) 119 | } 120 | 121 | // ------------------------------------------------------------- 122 | // ## Lifting 123 | // ------------------------------------------------------------- 124 | 125 | // merge :: (...* -> b) -> ...Promise e a -> Promise e b 126 | export function merge (f, ...args) { 127 | return runMerge(f, this, args) 128 | } 129 | 130 | function runMerge (f, thisArg, args) { 131 | const handler = new Merge(new MergeHandler(f, thisArg), resultsArray(args)) 132 | return iterablePromise(handler, args) 133 | } 134 | 135 | class MergeHandler { 136 | constructor (f, c) { 137 | this.f = f 138 | this.c = c 139 | this.promise = void 0 140 | this.args = void 0 141 | } 142 | 143 | merge (promise, args) { 144 | this.promise = promise 145 | this.args = args 146 | taskQueue.add(this) 147 | } 148 | 149 | run () { 150 | try { 151 | this.promise._resolve(this.f.apply(this.c, this.args)) 152 | } catch (e) { 153 | this.promise._reject(e) 154 | } 155 | } 156 | } 157 | 158 | function checkFunction (f) { 159 | if (typeof f !== 'function') { 160 | throw new TypeError('must provide a resolver function') 161 | } 162 | } 163 | 164 | // ------------------------------------------------------------- 165 | // ## ES6 Promise polyfill 166 | // ------------------------------------------------------------- 167 | 168 | const NOARGS = [] 169 | 170 | // type Resolve a = a -> () 171 | // type Reject e = e -> () 172 | // Promise :: (Resolve a -> Reject e) -> Promise e a 173 | class CreedPromise extends Future { 174 | constructor (f) { 175 | super() 176 | runResolver(_runPromise, f, void 0, NOARGS, this) 177 | } 178 | } 179 | 180 | CreedPromise.resolve = resolve 181 | CreedPromise.reject = reject 182 | CreedPromise.all = all 183 | CreedPromise.race = race 184 | 185 | export function shim () { 186 | /* global self */ 187 | const orig = typeof Promise === 'function' && Promise 188 | 189 | /* istanbul ignore if */ 190 | if (typeof self !== 'undefined') { 191 | self.Promise = CreedPromise 192 | /* istanbul ignore else */ 193 | } else if (typeof global !== 'undefined') { 194 | global.Promise = CreedPromise 195 | } 196 | 197 | return orig 198 | } 199 | 200 | export { CreedPromise as Promise } 201 | 202 | /* istanbul ignore if */ 203 | if (typeof Promise !== 'function') { 204 | shim() 205 | } 206 | -------------------------------------------------------------------------------- /src/Promise.js: -------------------------------------------------------------------------------- 1 | import TaskQueue from './TaskQueue' 2 | import ErrorHandler from './ErrorHandler' 3 | import makeEmitError from './emitError' 4 | import maybeThenable from './maybeThenable' 5 | import { PENDING, FULFILLED, REJECTED, NEVER } from './state' 6 | import { isNever, isSettled } from './inspect' 7 | 8 | import then from './then' 9 | import map from './map' 10 | import bimap from './bimap' 11 | import chain from './chain' 12 | 13 | import Race from './Race' 14 | import Merge from './Merge' 15 | import { resolveIterable, resultsArray } from './iterable' 16 | 17 | import fl from 'fantasy-land' 18 | 19 | const taskQueue = new TaskQueue() 20 | export { taskQueue } 21 | 22 | /* istanbul ignore next */ 23 | const errorHandler = new ErrorHandler(makeEmitError(), e => { 24 | throw e.value 25 | }) 26 | 27 | // ------------------------------------------------------------- 28 | // ## Types 29 | // ------------------------------------------------------------- 30 | 31 | // Internal base type, provides fantasy-land namespace 32 | // and type representative 33 | class Core { 34 | // empty :: Promise e a 35 | static empty () { 36 | return never() 37 | } 38 | 39 | // of :: a -> Promise e a 40 | static of (x) { 41 | return fulfill(x) 42 | } 43 | 44 | static [fl.empty] () { 45 | return never() 46 | } 47 | 48 | static [fl.of] (x) { 49 | return fulfill(x) 50 | } 51 | 52 | [fl.map] (f) { 53 | return this.map(f) 54 | } 55 | 56 | [fl.bimap] (r, f) { 57 | return this.bimap(r, f) 58 | } 59 | 60 | [fl.ap] (pf) { 61 | return pf.ap(this) 62 | } 63 | 64 | [fl.chain] (f) { 65 | return this.chain(f) 66 | } 67 | 68 | [fl.concat] (p) { 69 | return this.concat(p) 70 | } 71 | 72 | [fl.alt] (p) { 73 | return this.or(p) 74 | } 75 | 76 | static [fl.zero] () { 77 | return never() 78 | } 79 | 80 | // @deprecated The name concat is deprecated, use or() instead. 81 | concat (b) { 82 | return this.or(b) 83 | } 84 | } 85 | 86 | // data Promise e a where 87 | // Future :: Promise e a 88 | // Fulfilled :: a -> Promise e a 89 | // Rejected :: Error e => e -> Promise e a 90 | // Never :: Promise e a 91 | 92 | // Future :: Promise e a 93 | // A promise whose value cannot be known until some future time 94 | export class Future extends Core { 95 | constructor () { 96 | super() 97 | this.ref = void 0 98 | this.action = void 0 99 | this.length = 0 100 | } 101 | 102 | // then :: Promise e a -> (a -> b) -> Promise e b 103 | // then :: Promise e a -> () -> (e -> b) -> Promise e b 104 | // then :: Promise e a -> (a -> b) -> (e -> b) -> Promise e b 105 | then (f, r) { 106 | const n = this.near() 107 | return n === this ? then(f, r, this, new Future()) : n.then(f, r) 108 | } 109 | 110 | // catch :: Promise e a -> (e -> b) -> Promise e b 111 | catch (r) { 112 | const n = this.near() 113 | return n === this ? then(void 0, r, this, new Future()) : n.catch(r) 114 | } 115 | 116 | // map :: Promise e a -> (a -> b) -> Promise e b 117 | map (f) { 118 | const n = this.near() 119 | return n === this ? map(f, this, new Future()) : n.map(f) 120 | } 121 | 122 | bimap (r, f) { 123 | const n = this.near() 124 | return n === this 125 | ? bimap(r, f, this, new Future()) 126 | : n.bimap(r, f) 127 | } 128 | 129 | // ap :: Promise e (a -> b) -> Promise e a -> Promise e b 130 | ap (p) { 131 | const n = this.near() 132 | const pn = p.near() 133 | return n === this ? this.chain(f => pn.map(f)) : n.ap(pn) 134 | } 135 | 136 | // chain :: Promise e a -> (a -> Promise e b) -> Promise e b 137 | chain (f) { 138 | const n = this.near() 139 | return n === this ? chain(f, this, new Future()) : n.chain(f) 140 | } 141 | 142 | // or :: Promise e a -> Promise e a -> Promise e a 143 | or (b) { 144 | /* eslint complexity:[2,5] */ 145 | const n = this.near() 146 | const bn = b.near() 147 | 148 | return isSettled(n) || isNever(bn) ? n 149 | : isSettled(bn) || isNever(n) ? bn 150 | : race([n, bn]) 151 | } 152 | 153 | // toString :: Promise e a -> String 154 | toString () { 155 | return '[object ' + this.inspect() + ']' 156 | } 157 | 158 | // inspect :: Promise e a -> String 159 | inspect () { 160 | const n = this.near() 161 | return n === this ? 'Promise { pending }' : n.inspect() 162 | } 163 | 164 | // near :: Promise e a -> Promise e a 165 | near () { 166 | if (!this._isResolved()) { 167 | return this 168 | } 169 | 170 | this.ref = this.ref.near() 171 | return this.ref 172 | } 173 | 174 | // state :: Promise e a -> Int 175 | state () { 176 | return this._isResolved() ? this.ref.near().state() : PENDING 177 | } 178 | 179 | _isResolved () { 180 | return this.ref !== void 0 181 | } 182 | 183 | _when (action) { 184 | this._runAction(action) 185 | } 186 | 187 | _runAction (action) { 188 | if (this.action === void 0) { 189 | this.action = action 190 | } else { 191 | this[this.length++] = action 192 | } 193 | } 194 | 195 | _resolve (x) { 196 | this._become(resolve(x)) 197 | } 198 | 199 | _fulfill (x) { 200 | this._become(new Fulfilled(x)) 201 | } 202 | 203 | _reject (e) { 204 | if (this._isResolved()) { 205 | return 206 | } 207 | 208 | this.__become(new Rejected(e)) 209 | } 210 | 211 | _become (p) { 212 | if (this._isResolved()) { 213 | return 214 | } 215 | 216 | this.__become(p) 217 | } 218 | 219 | __become (p) { 220 | this.ref = p === this ? cycle() : p 221 | 222 | if (this.action === void 0) { 223 | return 224 | } 225 | 226 | taskQueue.add(this) 227 | } 228 | 229 | run () { 230 | const p = this.ref.near() 231 | p._runAction(this.action) 232 | this.action = void 0 233 | 234 | for (let i = 0; i < this.length; ++i) { 235 | p._runAction(this[i]) 236 | this[i] = void 0 237 | } 238 | } 239 | } 240 | 241 | // Fulfilled :: a -> Promise e a 242 | // A promise whose value is already known 243 | class Fulfilled extends Core { 244 | constructor (x) { 245 | super() 246 | this.value = x 247 | } 248 | 249 | then (f) { 250 | return typeof f === 'function' ? then(f, void 0, this, new Future()) : this 251 | } 252 | 253 | catch () { 254 | return this 255 | } 256 | 257 | map (f) { 258 | return map(f, this, new Future()) 259 | } 260 | 261 | bimap (_, f) { 262 | return this.map(f) 263 | } 264 | 265 | ap (p) { 266 | return p.map(this.value) 267 | } 268 | 269 | chain (f) { 270 | return chain(f, this, new Future()) 271 | } 272 | 273 | or () { 274 | return this 275 | } 276 | 277 | toString () { 278 | return '[object ' + this.inspect() + ']' 279 | } 280 | 281 | inspect () { 282 | return 'Promise { fulfilled: ' + this.value + ' }' 283 | } 284 | 285 | state () { 286 | return FULFILLED 287 | } 288 | 289 | near () { 290 | return this 291 | } 292 | 293 | _when (action) { 294 | taskQueue.add(new Continuation(action, this)) 295 | } 296 | 297 | _runAction (action) { 298 | action.fulfilled(this) 299 | } 300 | } 301 | 302 | // Rejected :: Error e => e -> Promise e a 303 | // A promise whose value cannot be known due to some reason/error 304 | class Rejected extends Core { 305 | constructor (e) { 306 | super() 307 | this.value = e 308 | this._state = REJECTED 309 | errorHandler.track(this) 310 | } 311 | 312 | then (_, r) { 313 | return typeof r === 'function' ? this.catch(r) : this 314 | } 315 | 316 | catch (r) { 317 | return then(void 0, r, this, new Future()) 318 | } 319 | 320 | map () { 321 | return this 322 | } 323 | 324 | bimap (r) { 325 | return bimap(r, void 0, this, new Future()) 326 | } 327 | 328 | ap () { 329 | return this 330 | } 331 | 332 | chain () { 333 | return this 334 | } 335 | 336 | or () { 337 | return this 338 | } 339 | 340 | toString () { 341 | return '[object ' + this.inspect() + ']' 342 | } 343 | 344 | inspect () { 345 | return 'Promise { rejected: ' + this.value + ' }' 346 | } 347 | 348 | state () { 349 | return this._state 350 | } 351 | 352 | near () { 353 | return this 354 | } 355 | 356 | _when (action) { 357 | taskQueue.add(new Continuation(action, this)) 358 | } 359 | 360 | _runAction (action) { 361 | if (action.rejected(this)) { 362 | errorHandler.untrack(this) 363 | } 364 | } 365 | } 366 | 367 | // Never :: Promise e a 368 | // A promise that waits forever for its value to be known 369 | class Never extends Core { 370 | then () { 371 | return this 372 | } 373 | 374 | catch () { 375 | return this 376 | } 377 | 378 | map () { 379 | return this 380 | } 381 | 382 | bimap () { 383 | return this 384 | } 385 | 386 | ap () { 387 | return this 388 | } 389 | 390 | chain () { 391 | return this 392 | } 393 | 394 | or (b) { 395 | return b 396 | } 397 | 398 | toString () { 399 | return '[object ' + this.inspect() + ']' 400 | } 401 | 402 | inspect () { 403 | return 'Promise { never }' 404 | } 405 | 406 | state () { 407 | return PENDING | NEVER 408 | } 409 | 410 | near () { 411 | return this 412 | } 413 | 414 | _when () { 415 | } 416 | 417 | _runAction () { 418 | } 419 | } 420 | 421 | // ------------------------------------------------------------- 422 | // ## Creating promises 423 | // ------------------------------------------------------------- 424 | 425 | // resolve :: Thenable e a -> Promise e a 426 | // resolve :: a -> Promise e a 427 | export function resolve (x) { 428 | return isPromise(x) ? x.near() 429 | : maybeThenable(x) ? refForMaybeThenable(fulfill, x) 430 | : new Fulfilled(x) 431 | } 432 | 433 | // reject :: e -> Promise e a 434 | export function reject (e) { 435 | return new Rejected(e) 436 | } 437 | 438 | // never :: Promise e a 439 | export function never () { 440 | return new Never() 441 | } 442 | 443 | // fulfill :: a -> Promise e a 444 | export function fulfill (x) { 445 | return new Fulfilled(x) 446 | } 447 | 448 | // future :: () -> { resolve: Resolve e a, promise: Promise e a } 449 | // type Resolve e a = a|Thenable e a -> () 450 | export function future () { 451 | const promise = new Future() 452 | return {resolve: x => promise._resolve(x), promise} 453 | } 454 | 455 | // ------------------------------------------------------------- 456 | // ## Iterables 457 | // ------------------------------------------------------------- 458 | 459 | // all :: Iterable (Promise e a) -> Promise e [a] 460 | export function all (promises) { 461 | const handler = new Merge(allHandler, resultsArray(promises)) 462 | return iterablePromise(handler, promises) 463 | } 464 | 465 | const allHandler = { 466 | merge (promise, args) { 467 | promise._fulfill(args) 468 | } 469 | } 470 | 471 | // race :: Iterable (Promise e a) -> Promise e a 472 | export function race (promises) { 473 | return iterablePromise(new Race(never), promises) 474 | } 475 | 476 | function isIterable (x) { 477 | return typeof x === 'object' && x !== null 478 | } 479 | 480 | export function iterablePromise (handler, iterable) { 481 | if (!isIterable(iterable)) { 482 | return reject(new TypeError('expected an iterable')) 483 | } 484 | 485 | const p = new Future() 486 | return resolveIterable(resolveMaybeThenable, handler, iterable, p) 487 | } 488 | 489 | // ------------------------------------------------------------- 490 | // # Internals 491 | // ------------------------------------------------------------- 492 | 493 | // isPromise :: * -> boolean 494 | function isPromise (x) { 495 | return x instanceof Core 496 | } 497 | 498 | function resolveMaybeThenable (x) { 499 | return isPromise(x) ? x.near() : refForMaybeThenable(fulfill, x) 500 | } 501 | 502 | function refForMaybeThenable (otherwise, x) { 503 | try { 504 | const then = x.then 505 | return typeof then === 'function' 506 | ? extractThenable(then, x) 507 | : otherwise(x) 508 | } catch (e) { 509 | return new Rejected(e) 510 | } 511 | } 512 | 513 | // WARNING: Naming the first arg "then" triggers babel compilation bug 514 | function extractThenable (thn, thenable) { 515 | const p = new Future() 516 | 517 | try { 518 | thn.call(thenable, x => p._resolve(x), e => p._reject(e)) 519 | } catch (e) { 520 | p._reject(e) 521 | } 522 | 523 | return p.near() 524 | } 525 | 526 | function cycle () { 527 | return new Rejected(new TypeError('resolution cycle')) 528 | } 529 | 530 | class Continuation { 531 | constructor (action, promise) { 532 | this.action = action 533 | this.promise = promise 534 | } 535 | 536 | run () { 537 | this.promise._runAction(this.action) 538 | } 539 | } 540 | -------------------------------------------------------------------------------- /perf/lib/timers-ctx.js: -------------------------------------------------------------------------------- 1 | // Copyright Joyent, Inc. and other Node contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | var Timer = process.binding('timer_wrap').Timer; 23 | var L = require('_linklist'); 24 | var assert = require('assert').ok; 25 | 26 | var kOnTimeout = Timer.kOnTimeout | 0; 27 | 28 | // Timeout values > TIMEOUT_MAX are set to 1. 29 | var TIMEOUT_MAX = 2147483647; // 2^31-1 30 | 31 | 32 | var debug = require('util').debuglog('timer'); 33 | 34 | 35 | // IDLE TIMEOUTS 36 | // 37 | // Because often many sockets will have the same idle timeout we will not 38 | // use one timeout watcher per item. It is too much overhead. Instead 39 | // we'll use a single watcher for all sockets with the same timeout value 40 | // and a linked list. This technique is described in the libev manual: 41 | // http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts 42 | 43 | // Object containing all lists, timers 44 | // key = time in milliseconds 45 | // value = list 46 | var lists = {}; 47 | 48 | // the main function - creates lists on demand and the watchers associated 49 | // with them. 50 | function insert(item, msecs) { 51 | item._idleStart = Timer.now(); 52 | item._idleTimeout = msecs; 53 | 54 | if (msecs < 0) return; 55 | 56 | var list; 57 | 58 | if (lists[msecs]) { 59 | list = lists[msecs]; 60 | } else { 61 | list = new Timer(); 62 | list.start(msecs, 0); 63 | 64 | L.init(list); 65 | 66 | lists[msecs] = list; 67 | list.msecs = msecs; 68 | list[kOnTimeout] = listOnTimeout; 69 | } 70 | 71 | L.append(list, item); 72 | assert(!L.isEmpty(list)); // list is not empty 73 | } 74 | 75 | function listOnTimeout() { 76 | var msecs = this.msecs; 77 | var list = this; 78 | 79 | debug('timeout callback %d', msecs); 80 | 81 | var now = Timer.now(); 82 | debug('now: %s', now); 83 | 84 | var first; 85 | while (first = L.peek(list)) { 86 | var diff = now - first._idleStart; 87 | if (diff < msecs) { 88 | list.start(msecs - diff, 0); 89 | debug('%d list wait because diff is %d', msecs, diff); 90 | return; 91 | } else { 92 | L.remove(first); 93 | assert(first !== L.peek(list)); 94 | 95 | if (!first._onTimeout) continue; 96 | 97 | // v0.4 compatibility: if the timer callback throws and the 98 | // domain or uncaughtException handler ignore the exception, 99 | // other timers that expire on this tick should still run. 100 | // 101 | // https://github.com/joyent/node/issues/2631 102 | var domain = first.domain; 103 | if (domain && domain._disposed) continue; 104 | try { 105 | if (domain) 106 | domain.enter(); 107 | var threw = true; 108 | if (!first._ctx || first._ctx === first) 109 | first._onTimeout(); 110 | else 111 | first._onTimeout.call(first._ctx); 112 | 113 | if (domain) 114 | domain.exit(); 115 | threw = false; 116 | } finally { 117 | if (threw) { 118 | process.nextTick(function() { 119 | list[kOnTimeout](); 120 | }); 121 | } 122 | } 123 | } 124 | } 125 | 126 | debug('%d list empty', msecs); 127 | assert(L.isEmpty(list)); 128 | list.close(); 129 | delete lists[msecs]; 130 | } 131 | 132 | 133 | var unenroll = exports.unenroll = function(item) { 134 | L.remove(item); 135 | 136 | var list = lists[item._idleTimeout]; 137 | // if empty then stop the watcher 138 | debug('unenroll'); 139 | if (list && L.isEmpty(list)) { 140 | debug('unenroll: list empty'); 141 | list.close(); 142 | delete lists[item._idleTimeout]; 143 | } 144 | // if active is called later, then we want to make sure not to insert again 145 | item._idleTimeout = -1; 146 | }; 147 | 148 | 149 | // Does not start the time, just sets up the members needed. 150 | exports.enroll = function(item, msecs) { 151 | // if this item was already in a list somewhere 152 | // then we should unenroll it from that 153 | if (item._idleNext) unenroll(item); 154 | 155 | // Ensure that msecs fits into signed int32 156 | if (msecs > 0x7fffffff) { 157 | msecs = 0x7fffffff; 158 | } 159 | 160 | item._idleTimeout = msecs; 161 | L.init(item); 162 | }; 163 | 164 | 165 | // call this whenever the item is active (not idle) 166 | // it will reset its timeout. 167 | exports.active = function(item) { 168 | var msecs = item._idleTimeout; 169 | if (msecs >= 0) { 170 | 171 | var list = lists[msecs]; 172 | if (!list || L.isEmpty(list)) { 173 | insert(item, msecs); 174 | } else { 175 | item._idleStart = Timer.now(); 176 | L.append(list, item); 177 | } 178 | } 179 | }; 180 | 181 | 182 | /* 183 | * DOM-style timers 184 | */ 185 | 186 | 187 | exports.setTimeout = function(callback, ctx, after) { 188 | var timer; 189 | 190 | after *= 1; // coalesce to number or NaN 191 | 192 | if (!(after >= 1 && after <= TIMEOUT_MAX)) { 193 | after = 1; // schedule on next tick, follows browser behaviour 194 | } 195 | 196 | timer = new Timeout(after); 197 | timer._ctx = ctx; 198 | timer._onTimeout = callback; 199 | 200 | if (process.domain) timer.domain = process.domain; 201 | 202 | exports.active(timer); 203 | 204 | return timer; 205 | }; 206 | 207 | 208 | exports.clearTimeout = function(timer) { 209 | if (timer && (timer[kOnTimeout] || timer._onTimeout)) { 210 | timer[kOnTimeout] = timer._onTimeout = null; 211 | if (timer instanceof Timeout) { 212 | timer.close(); // for after === 0 213 | } else { 214 | exports.unenroll(timer); 215 | } 216 | } 217 | }; 218 | 219 | 220 | exports.setInterval = function(callback, repeat) { 221 | repeat *= 1; // coalesce to number or NaN 222 | 223 | if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) { 224 | repeat = 1; // schedule on next tick, follows browser behaviour 225 | } 226 | 227 | var timer = new Timeout(repeat); 228 | var args = Array.prototype.slice.call(arguments, 2); 229 | timer._onTimeout = wrapper; 230 | timer._repeat = true; 231 | 232 | if (process.domain) timer.domain = process.domain; 233 | exports.active(timer); 234 | 235 | return timer; 236 | 237 | function wrapper() { 238 | callback.apply(this, args); 239 | // If callback called clearInterval(). 240 | if (timer._repeat === false) return; 241 | // If timer is unref'd (or was - it's permanently removed from the list.) 242 | if (this._handle) { 243 | this._handle.start(repeat, 0); 244 | } else { 245 | timer._idleTimeout = repeat; 246 | exports.active(timer); 247 | } 248 | } 249 | }; 250 | 251 | 252 | exports.clearInterval = function(timer) { 253 | if (timer && timer._repeat) { 254 | timer._repeat = false; 255 | clearTimeout(timer); 256 | } 257 | }; 258 | 259 | 260 | var Timeout = function(after) { 261 | this._idleTimeout = after; 262 | this._idlePrev = this; 263 | this._idleNext = this; 264 | this._idleStart = null; 265 | this._onTimeout = null; 266 | this._repeat = false; 267 | this._ctx = this; 268 | }; 269 | 270 | Timeout.prototype.unref = function() { 271 | if (!this._handle) { 272 | var now = Timer.now(); 273 | if (!this._idleStart) this._idleStart = now; 274 | var delay = this._idleStart + this._idleTimeout - now; 275 | if (delay < 0) delay = 0; 276 | exports.unenroll(this); 277 | this._handle = new Timer(); 278 | this._handle[kOnTimeout] = this._onTimeout; 279 | this._handle.start(delay, 0); 280 | this._handle.domain = this.domain; 281 | this._handle.unref(); 282 | } else { 283 | this._handle.unref(); 284 | } 285 | }; 286 | 287 | Timeout.prototype.ref = function() { 288 | if (this._handle) 289 | this._handle.ref(); 290 | }; 291 | 292 | Timeout.prototype.close = function() { 293 | this._onTimeout = this._ctx = null; 294 | if (this._handle) { 295 | this._handle[kOnTimeout] = null; 296 | this._handle.close(); 297 | } else { 298 | exports.unenroll(this); 299 | } 300 | }; 301 | 302 | 303 | var immediateQueue = {}; 304 | L.init(immediateQueue); 305 | 306 | 307 | function processImmediate() { 308 | var queue = immediateQueue; 309 | 310 | immediateQueue = {}; 311 | L.init(immediateQueue); 312 | 313 | while (L.isEmpty(queue) === false) { 314 | var immediate = L.shift(queue); 315 | var domain = immediate.domain; 316 | if (domain) domain.enter(); 317 | immediate._onImmediate(); 318 | if (domain) domain.exit(); 319 | } 320 | 321 | // Only round-trip to C++ land if we have to. Calling clearImmediate() on an 322 | // immediate that's in |queue| is okay. Worst case is we make a superfluous 323 | // call to NeedImmediateCallbackSetter(). 324 | if (L.isEmpty(immediateQueue)) { 325 | process._needImmediateCallback = false; 326 | } 327 | } 328 | 329 | 330 | exports.setImmediate = function(callback) { 331 | var immediate = {}, args; 332 | 333 | L.init(immediate); 334 | 335 | immediate._onImmediate = callback; 336 | 337 | if (arguments.length > 1) { 338 | args = Array.prototype.slice.call(arguments, 1); 339 | 340 | immediate._onImmediate = function() { 341 | callback.apply(immediate, args); 342 | }; 343 | } 344 | 345 | if (!process._needImmediateCallback) { 346 | process._needImmediateCallback = true; 347 | process._immediateCallback = processImmediate; 348 | } 349 | 350 | if (process.domain) immediate.domain = process.domain; 351 | 352 | L.append(immediateQueue, immediate); 353 | 354 | return immediate; 355 | }; 356 | 357 | 358 | exports.clearImmediate = function(immediate) { 359 | if (!immediate) return; 360 | 361 | immediate._onImmediate = undefined; 362 | 363 | L.remove(immediate); 364 | 365 | if (L.isEmpty(immediateQueue)) { 366 | process._needImmediateCallback = false; 367 | } 368 | }; 369 | 370 | 371 | // Internal APIs that need timeouts should use timers._unrefActive isntead of 372 | // timers.active as internal timeouts shouldn't hold the loop open 373 | 374 | var unrefList, unrefTimer; 375 | 376 | 377 | function unrefTimeout() { 378 | var now = Timer.now(); 379 | 380 | debug('unrefTimer fired'); 381 | 382 | var first; 383 | while (first = L.peek(unrefList)) { 384 | var diff = now - first._idleStart; 385 | 386 | if (diff < first._idleTimeout) { 387 | diff = first._idleTimeout - diff; 388 | unrefTimer.start(diff, 0); 389 | unrefTimer.when = now + diff; 390 | debug('unrefTimer rescheudling for later'); 391 | return; 392 | } 393 | 394 | L.remove(first); 395 | 396 | var domain = first.domain; 397 | 398 | if (!first._onTimeout) continue; 399 | if (domain && domain._disposed) continue; 400 | 401 | try { 402 | if (domain) domain.enter(); 403 | var threw = true; 404 | debug('unreftimer firing timeout'); 405 | if (!first._ctx || first._ctx === first) 406 | first._onTimeout(); 407 | else 408 | first._onTimeout.call(first._ctx); 409 | threw = false; 410 | if (domain) domain.exit(); 411 | } finally { 412 | if (threw) process.nextTick(unrefTimeout); 413 | } 414 | } 415 | 416 | debug('unrefList is empty'); 417 | unrefTimer.when = -1; 418 | } 419 | 420 | 421 | exports._unrefActive = function(item) { 422 | var msecs = item._idleTimeout; 423 | if (!msecs || msecs < 0) return; 424 | assert(msecs >= 0); 425 | 426 | L.remove(item); 427 | 428 | if (!unrefList) { 429 | debug('unrefList initialized'); 430 | unrefList = {}; 431 | L.init(unrefList); 432 | 433 | debug('unrefTimer initialized'); 434 | unrefTimer = new Timer(); 435 | unrefTimer.unref(); 436 | unrefTimer.when = -1; 437 | unrefTimer[kOnTimeout] = unrefTimeout; 438 | } 439 | 440 | var now = Timer.now(); 441 | item._idleStart = now; 442 | 443 | if (L.isEmpty(unrefList)) { 444 | debug('unrefList empty'); 445 | L.append(unrefList, item); 446 | 447 | unrefTimer.start(msecs, 0); 448 | unrefTimer.when = now + msecs; 449 | debug('unrefTimer scheduled'); 450 | return; 451 | } 452 | 453 | var when = now + msecs; 454 | 455 | debug('unrefList find where we can insert'); 456 | 457 | var cur, them; 458 | 459 | for (cur = unrefList._idlePrev; cur != unrefList; cur = cur._idlePrev) { 460 | them = cur._idleStart + cur._idleTimeout; 461 | 462 | if (when < them) { 463 | debug('unrefList inserting into middle of list'); 464 | 465 | L.append(cur, item); 466 | 467 | if (unrefTimer.when > when) { 468 | debug('unrefTimer is scheduled to fire too late, reschedule'); 469 | unrefTimer.start(msecs, 0); 470 | unrefTimer.when = when; 471 | } 472 | 473 | return; 474 | } 475 | } 476 | 477 | debug('unrefList append to end'); 478 | L.append(unrefList, item); 479 | }; 480 | -------------------------------------------------------------------------------- /dist/creed.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.creed=t.creed||{})}(this,function(t){"use strict";function e(t){return(t.state()&et)>0}function n(t){return(t.state()&nt)>0}function r(t){return(t.state()&rt)>0}function o(t){return(t.state()&ot)>0}function i(t){return(t.state()&it)>0}function c(t){return(t.state()&ct)>0}function u(t){var e=t.near();if(!n(e))throw new TypeError("getValue called on "+t);return e.value}function p(t){var e=t.near();if(!r(e))throw new TypeError("getReason called on "+t);return s(e),e.value}function s(t){t._runAction(ut)}function f(t){return function(){return setTimeout(t,0)}}function a(t){return function(){return process.nextTick(t)}}function h(t){var e=document.createTextNode("");new st(t).observe(e,{characterData:!0});var n=0;return function(){e.data=n^=1}}function l(t,e){try{y(e,t)}finally{e.length=0}}function y(t,e){for(var n=0;n0;)e[n]=arguments[n+1];return J(F,t,this,e,new Pt)}function I(t){for(var e=[],n=arguments.length-1;n-- >0;)e[n]=arguments[n+1];return J(D,t,this,e,new Pt)}function J(t,e,n,r,o){$(e);try{t(e,n,r,o)}catch(t){o._reject(t)}return o}function L(t,e){var n=A(e);return t<=0||r(n)||i(n)?n:Ht(t,n,new Pt)}function Q(t,e){var n=A(e);return o(n)?n:Wt(t,n,new Pt)}function U(t){return x(new Ft,t)}function X(t){var e=new qt(A,m(t));return x(e,t)}function Y(t){for(var e=[],n=arguments.length-1;n-- >0;)e[n]=arguments[n+1];return Z(t,this,e)}function Z(t,e,n){var r=new kt(new Gt(t,e),m(n));return x(r,n)}function $(t){if("function"!=typeof t)throw new TypeError("must provide a resolver function")}function tt(){var t="function"==typeof Promise&&Promise;return"undefined"!=typeof self?self.Promise=Jt:"undefined"!=typeof global&&(global.Promise=Jt),t}var et=1,nt=2,rt=4,ot=nt|rt,it=8,ct=16,ut={fulfilled:function(){},rejected:function(t){t._state|=ct}},pt="undefined"!=typeof process&&"[object process]"===Object.prototype.toString.call(process),st="function"==typeof MutationObserver&&MutationObserver||"function"==typeof WebKitMutationObserver&&WebKitMutationObserver,ft=function(t){return pt?a(t):st?h(t):f(t)},at=function(){var t=this;this.tasks=new Array(65536),this.length=0,this.drain=ft(function(){return t._drain()})};at.prototype.add=function(t){0===this.length&&this.drain(),this.tasks[this.length++]=t},at.prototype._drain=function(){for(var t=this.tasks,e=0;e