├── .gitignore ├── package.json ├── benchmark.js ├── README.md ├── index.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-value-promise", 3 | "version": "1.1.1", 4 | "description": "Creates a value/error pair to mimic promise behavior", 5 | "author": "Stephen Belanger (https://github.com/qard)", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "bench": "node benchmark.js", 10 | "test": "tap test.js" 11 | }, 12 | "keywords": [ 13 | "async", 14 | "value", 15 | "container", 16 | "promise" 17 | ], 18 | "homepage": "https://github.com/Qard/async-value-promise#readme", 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/Qard/async-value-promise.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/Qard/async-value-promise/issues" 25 | }, 26 | "dependencies": { 27 | "async-value": "^1.2.2" 28 | }, 29 | "devDependencies": { 30 | "benchmark-fn-list": "^1.0.0", 31 | "benchmark-fn-pretty-print": "^1.0.0", 32 | "tap": "^11.1.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const benchList = require('benchmark-fn-list') 4 | const benchPrettyPrint = require('benchmark-fn-pretty-print') 5 | const AsyncValuePromise = require('./') 6 | 7 | benchList([ 8 | { 9 | name: 'then before resolve', 10 | iterations: 100000, 11 | task(cb) { 12 | var result = new AsyncValuePromise() 13 | result.then(cb, cb) 14 | result.resolve('hello') 15 | } 16 | }, 17 | { 18 | name: 'resolve before then', 19 | iterations: 100000, 20 | task(cb) { 21 | var result = new AsyncValuePromise() 22 | result.resolve('hello') 23 | result.then(cb, cb) 24 | } 25 | }, 26 | { 27 | name: 'then before reject', 28 | iterations: 100000, 29 | task(cb) { 30 | var result = new AsyncValuePromise() 31 | result.then(cb, cb) 32 | result.reject('hello') 33 | } 34 | }, 35 | { 36 | name: 'reject before then', 37 | iterations: 100000, 38 | task(cb) { 39 | var result = new AsyncValuePromise() 40 | result.reject('hello') 41 | result.then(cb, cb) 42 | } 43 | }, 44 | { 45 | name: 'resolve in constructor', 46 | iterations: 100000, 47 | task(cb) { 48 | var result = new AsyncValuePromise('hello') 49 | result.then(cb, cb) 50 | } 51 | }, 52 | { 53 | name: 'reject in constructor', 54 | iterations: 100000, 55 | task(cb) { 56 | var result = new AsyncValuePromise(null, 'hello') 57 | result.then(cb, cb) 58 | } 59 | }, 60 | { 61 | name: 'promise', 62 | iterations: 100000, 63 | task(cb) { 64 | var promise = Promise.resolve('hello') 65 | promise.then(cb) 66 | } 67 | } 68 | ], results => { 69 | console.log(benchPrettyPrint(results)) 70 | }) 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-value-promise 2 | 3 | An `AsyncValuePromise` represents a value that will exist in the future. It is 4 | similar to a `Promise` but does not include error boundary zoning or forced 5 | asynchrony, for performance reasons. 6 | 7 | This module is internally synchronous. What this means is that if a value 8 | promise has already had `resolve` or `reject` called, any calls to `then` will 9 | trigger the corresponding callback immediately. Conversely, if any callbacks 10 | have already been attached with `then` before a `resolve` or `reject` occurs 11 | they too will run immediately. This is intentional for performance reasons. 12 | 13 | ## Install 14 | 15 | ```sh 16 | npm install async-value-promise 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | var pass = new AsyncValuePromise() 23 | 24 | pass.then(name => { 25 | console.log(`hello, ${name}!`) 26 | }) 27 | 28 | pass.resolve('world') 29 | 30 | var fail = new AsyncValuePromise() 31 | 32 | fail.then(null, error => { 33 | console.error(err.stack) 34 | }) 35 | 36 | fail.reject(new Error('oops')) 37 | ``` 38 | 39 | --- 40 | 41 | ### Copyright (c) 2018 Stephen Belanger 42 | #### Licensed under MIT License 43 | 44 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncValue = require('async-value') 4 | const assert = require('assert') 5 | 6 | function isDefined(thing) { 7 | return thing !== null & thing !== undefined 8 | } 9 | 10 | module.exports = class AsyncValuePromise { 11 | constructor(value, error) { 12 | this.value = new AsyncValue() 13 | this.error = new AsyncValue() 14 | 15 | if (isDefined(error)) { 16 | this.reject(error) 17 | } else if (isDefined(value)) { 18 | this.resolve(value) 19 | } 20 | } 21 | 22 | then(pass, fail) { 23 | const promise = new AsyncValuePromise() 24 | 25 | if (this.value) { 26 | if (!pass) { 27 | this.value.send(promise.value) 28 | } else { 29 | this.value.get(value => { 30 | promise.resolve(pass(value)) 31 | }) 32 | } 33 | } 34 | 35 | if (this.error) { 36 | if (!fail) { 37 | this.error.send(promise.error) 38 | } else { 39 | this.error.get(error => { 40 | try { 41 | promise.resolve(fail(error)) 42 | } catch (err) { 43 | promise.reject(err) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | return promise 50 | } 51 | 52 | resolve(value) { 53 | assert(this.value, 'already rejected') 54 | if (value instanceof AsyncValuePromise) { 55 | value.value.send(this.value) 56 | if (this.error) { 57 | value.error.send(this.error) 58 | } 59 | } else { 60 | this.value.set(value) 61 | this.error = null 62 | } 63 | } 64 | 65 | reject(error) { 66 | assert(this.error, 'already resolved') 67 | if (error instanceof AsyncValuePromise) { 68 | error.error.send(this.error) 69 | if (this.value) { 70 | error.value.send(this.value) 71 | } 72 | } else { 73 | this.error.set(error) 74 | this.value = null 75 | } 76 | } 77 | 78 | catch(fail) { 79 | return this.then(null, fail) 80 | } 81 | 82 | static resolve(value) { 83 | return new AsyncValuePromise(value) 84 | } 85 | 86 | static reject(error) { 87 | return new AsyncValuePromise(null, error) 88 | } 89 | 90 | static all(promises) { 91 | const promise = new AsyncValuePromise() 92 | const count = promises.length 93 | const results = new Array(count) 94 | let remaining = count 95 | let failed = false 96 | 97 | function fail(error) { 98 | if (!failed) { 99 | promise.reject(error) 100 | failed = true 101 | } 102 | } 103 | 104 | if (count === 0) { 105 | promise.resolve([]) 106 | } else { 107 | for (let i = 0; i < count; i++) { 108 | promises[i].then(value => { 109 | if (failed) return 110 | results[i] = value 111 | if (--remaining === 0) { 112 | promise.resolve(results) 113 | } 114 | }).catch(fail) 115 | } 116 | } 117 | 118 | return promise 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var tap = require('tap') 4 | var AsyncValuePromise = require('./') 5 | 6 | tap.test('resolve', t => { 7 | var promise = new AsyncValuePromise() 8 | 9 | promise.then(value => { 10 | t.equal(value, 'hello') 11 | t.end() 12 | }) 13 | 14 | setImmediate(() => { 15 | promise.resolve('hello') 16 | }) 17 | }) 18 | 19 | tap.test('reject', t => { 20 | var promise = new AsyncValuePromise() 21 | 22 | promise.then(null, error => { 23 | t.equal(error, 'hello') 24 | t.end() 25 | }) 26 | 27 | setImmediate(() => { 28 | promise.reject('hello') 29 | }) 30 | }) 31 | 32 | tap.test('throw on reject after resolve', t => { 33 | var promise = new AsyncValuePromise() 34 | promise.resolve('pass') 35 | t.throws(() => promise.reject('fail')) 36 | t.end() 37 | }) 38 | 39 | tap.test('throw on resolve after reject', t => { 40 | var promise = new AsyncValuePromise() 41 | promise.reject('fail') 42 | t.throws(() => promise.resolve('pass')) 43 | t.end() 44 | }) 45 | 46 | tap.test('catch', t => { 47 | var promise = new AsyncValuePromise() 48 | 49 | promise.catch(error => { 50 | t.equal(error, 'hello') 51 | t.end() 52 | }) 53 | 54 | setImmediate(() => { 55 | promise.reject('hello') 56 | }) 57 | }) 58 | 59 | tap.test('all success', t => { 60 | var promiseA = new AsyncValuePromise() 61 | var promiseB = new AsyncValuePromise() 62 | var promise = AsyncValuePromise.all([ 63 | promiseA, 64 | promiseB 65 | ]) 66 | 67 | promise 68 | .then((values) => { 69 | t.equal(values[0], 'first') 70 | t.equal(values[1], 'second') 71 | t.end() 72 | }) 73 | .catch(() => { 74 | t.fail('should not reject') 75 | }) 76 | 77 | setImmediate(() => { 78 | promiseB.resolve('second') 79 | setImmediate(() => { 80 | promiseA.resolve('first') 81 | }) 82 | }) 83 | }) 84 | 85 | tap.test('all failure', t => { 86 | var promiseA = new AsyncValuePromise() 87 | var promiseB = new AsyncValuePromise() 88 | var promise = AsyncValuePromise.all([ promiseA, promiseB ]) 89 | 90 | promise 91 | .then(() => t.fail('should not resolve')) 92 | .catch(error => { 93 | t.equal(error, 'error') 94 | t.end() 95 | }) 96 | 97 | setImmediate(() => { 98 | promiseA.resolve('success') 99 | promiseB.reject('error') 100 | }) 101 | }) 102 | 103 | tap.test('all empty', t => { 104 | var promise = AsyncValuePromise.all([]) 105 | 106 | promise 107 | .then((values) => { 108 | t.equal(values.length, 0) 109 | t.end() 110 | }) 111 | .catch(() => { 112 | t.fail('should not reject') 113 | }) 114 | }) 115 | 116 | tap.test('static resolve', t => { 117 | var promise = AsyncValuePromise.resolve('hello') 118 | 119 | promise 120 | .then(value => { 121 | t.equal(value, 'hello') 122 | t.end() 123 | }) 124 | .catch(() => t.fail('should not reject')) 125 | }) 126 | 127 | tap.test('static reject', t => { 128 | var promise = AsyncValuePromise.reject('hello') 129 | 130 | promise 131 | .then(() => t.fail('should not resolve')) 132 | .catch(error => { 133 | t.equal(error, 'hello') 134 | t.end() 135 | }) 136 | }) 137 | 138 | tap.test('catch', t => { 139 | var promise = AsyncValuePromise.reject('world') 140 | 141 | promise 142 | .catch(name => 'hello ' + name) 143 | .then(value => { 144 | t.equal(value, 'hello world') 145 | t.end() 146 | }) 147 | .catch(() => t.fail('should not reject')) 148 | }) 149 | --------------------------------------------------------------------------------