Comparing version 0.0.4 to 0.1.0
236
kew.js
@@ -0,1 +1,10 @@ | ||
/** | ||
* An object representing a "promise" for a future value | ||
* | ||
* @param {function(Object)} onSuccess a function to handle successful | ||
* resolution of this promise | ||
* @param {function(Error)} onFail a function to handle failed | ||
* resolution of this promise | ||
* @constructor | ||
*/ | ||
function Promise(onSuccess, onFail) { | ||
@@ -8,23 +17,11 @@ this.promise = this | ||
Promise.prototype.withInput = function (data) { | ||
if (this._successFn) { | ||
try { | ||
this.resolve(this._successFn(data)) | ||
} catch (e) { | ||
this.reject(e) | ||
} | ||
} else this.resolve(data) | ||
} | ||
/** | ||
* Resolve this promise with a specified value | ||
* | ||
* @param {Object} data | ||
*/ | ||
Promise.prototype.resolve = function (data) { | ||
if (this._error || this._hasData) throw new Error("Unable to resolve or reject the same promise twice") | ||
Promise.prototype.withError = function (e) { | ||
if (this._failFn) { | ||
try { | ||
this.resolve(this._failFn(e)) | ||
} catch (e) { | ||
this.reject(e) | ||
} | ||
} else this.reject(e) | ||
} | ||
Promise.prototype.resolve = function (data) { | ||
var i | ||
if (data && data._isPromise) { | ||
@@ -38,2 +35,9 @@ this._child = data | ||
} | ||
if (this._onComplete) { | ||
for (var i = 0; i < this._onComplete.length; i+= 1) { | ||
data.fin(this._onComplete[i]) | ||
} | ||
delete this._onComplete | ||
} | ||
return | ||
@@ -45,5 +49,11 @@ } | ||
if (this._onComplete) { | ||
for (i = 0; i < this._onComplete.length; i++) { | ||
this._onComplete[i]() | ||
} | ||
} | ||
if (this._promises) { | ||
for (var i = 0; i < this._promises.length; i += 1) { | ||
this._promises[i].withInput(data) | ||
for (i = 0; i < this._promises.length; i += 1) { | ||
this._promises[i]._withInput(data) | ||
} | ||
@@ -54,23 +64,43 @@ delete this._promises | ||
/** | ||
* Reject this promise with an error | ||
* | ||
* @param {Error} e | ||
*/ | ||
Promise.prototype.reject = function (e) { | ||
if (this._error || this._hasData) throw new Error("Unable to resolve or reject the same promise twice") | ||
var i | ||
this._error = e | ||
if (this._ended) { | ||
process.nextTick(function () { | ||
throw e | ||
}) | ||
} | ||
if (this._onComplete) { | ||
for (i = 0; i < this._onComplete.length; i++) { | ||
this._onComplete[i]() | ||
} | ||
} | ||
if (this._promises) { | ||
for (var i = 0; i < this._promises.length; i += 1) { | ||
this._promises[i].withError(e) | ||
for (i = 0; i < this._promises.length; i += 1) { | ||
this._promises[i]._withError(e) | ||
} | ||
delete this._promises | ||
} | ||
if (this._ended) throw e | ||
} | ||
Promise.prototype._chainPromise = function (promise) { | ||
if (this._child) this._child._chainPromise(promise) | ||
else if (this._hasData) promise.withInput(this._data) | ||
else if (this._error) promise.withError(this._error) | ||
else if (!this._promises) this._promises = [promise] | ||
else this._promises.push(promise) | ||
} | ||
/** | ||
* Provide a callback to be called whenever this promise successfully | ||
* resolves. Allows for an optional second callback to handle the failure | ||
* case. | ||
* | ||
* @param {function(Object)} onSuccess | ||
* @param {?function(Error)} onFail | ||
* @return {Promise} returns a new promise with the output of the onSuccess or | ||
* onFail handler | ||
*/ | ||
Promise.prototype.then = function (onSuccess, onFail) { | ||
@@ -85,2 +115,8 @@ var promise = new Promise(onSuccess, onFail) | ||
/** | ||
* Provide a callback to be called whenever this promise is rejected | ||
* | ||
* @param {function(Error)} onFail | ||
* @return {Promise} returns a new promise with the output of the onFail handler | ||
*/ | ||
Promise.prototype.fail = function (onFail) { | ||
@@ -90,8 +126,31 @@ return this.then(null, onFail) | ||
/** | ||
* Provide a callback to be called whenever this promise is either resolved | ||
* or rejected. | ||
* | ||
* @param {function()} onComplete | ||
* @return {Promise} returns the current promise | ||
*/ | ||
Promise.prototype.fin = function (onComplete) { | ||
if (!this._onComplete) this._onComplete = [onComplete] | ||
else this._onComplete.push(onComplete) | ||
if (this._hasData || this._error) { | ||
onComplete() | ||
return this | ||
} | ||
if (this._child) { | ||
this._child.fin(onComplete) | ||
} else { | ||
if (!this._onComplete) this._onComplete = [onComplete] | ||
else this._onComplete.push(onComplete) | ||
} | ||
return this | ||
} | ||
/** | ||
* Mark this promise as "ended". If the promise is rejected, this will throw an | ||
* error in whatever scope it happens to be in | ||
* | ||
* @return {Promise} returns the current promise | ||
*/ | ||
Promise.prototype.end = function () { | ||
@@ -102,4 +161,64 @@ if (this._error) { | ||
this._ended = true | ||
return this | ||
} | ||
/** | ||
* Attempt to resolve this promise with the specified input | ||
* | ||
* @param {Object} data the input | ||
*/ | ||
Promise.prototype._withInput = function (data) { | ||
if (this._successFn) { | ||
try { | ||
this.resolve(this._successFn(data)) | ||
} catch (e) { | ||
this.reject(e) | ||
} | ||
} else this.resolve(data) | ||
} | ||
/** | ||
* Attempt to reject this promise with the specified error | ||
* | ||
* @param {Error} e | ||
*/ | ||
Promise.prototype._withError = function (e) { | ||
if (this._failFn) { | ||
try { | ||
this.resolve(this._failFn(e)) | ||
} catch (e) { | ||
this.reject(e) | ||
} | ||
} else this.reject(e) | ||
} | ||
/** | ||
* Chain a promise to the current promise | ||
* | ||
* @param {Promise} the promise to chain | ||
*/ | ||
Promise.prototype._chainPromise = function (promise) { | ||
var i | ||
if (this._child) { | ||
this._child._chainPromise(promise) | ||
} else if (this._hasData) { | ||
promise._withInput(this._data) | ||
} else if (this._error) { | ||
promise._withError(this._error) | ||
} else if (!this._promises) { | ||
this._promises = [promise] | ||
} else { | ||
this._promises.push(promise) | ||
} | ||
} | ||
/** | ||
* Utility function used for creating a node-style resolver | ||
* for deferreds | ||
* | ||
* @param {Promise} deferred a promise that looks like a deferred | ||
* @param {Error} err an optional error | ||
* @param {Object} data optional data | ||
*/ | ||
function resolver(deferred, err, data) { | ||
@@ -110,2 +229,8 @@ if (err) deferred.reject(err) | ||
/** | ||
* Creates a node-style resolver for a deferred by wrapping | ||
* resolver() | ||
* | ||
* @return {function(Error, Object)} node-style callback | ||
*/ | ||
Promise.prototype.makeNodeResolver = function () { | ||
@@ -115,2 +240,8 @@ return resolver.bind(null, this) | ||
/** | ||
* Static function which creates and resolves a promise immediately | ||
* | ||
* @param {Object} data data to resolve the promise with | ||
* @return {Promise} | ||
*/ | ||
function resolve(data) { | ||
@@ -122,2 +253,8 @@ var promise = new Promise() | ||
/** | ||
* Static function which creates and rejects a promise immediately | ||
* | ||
* @param {Error} e error to reject the promise with | ||
* @return {Promise} | ||
*/ | ||
function reject(e) { | ||
@@ -129,6 +266,11 @@ var promise = new Promise() | ||
function setOutput(arr, idx, val) { | ||
arr[idx] = val | ||
} | ||
/** | ||
* Replace an element in an array with a new value. Used by .all() to | ||
* call from .then() | ||
* | ||
* @param {Array.<Object>} arr | ||
* @param {number} idx | ||
* @param {Object} val | ||
* @return {Object} the val that's being injected into the array | ||
*/ | ||
function replaceEl(arr, idx, val) { | ||
@@ -139,2 +281,9 @@ arr[idx] = val | ||
/** | ||
* Takes in an array of promises or literals and returns a promise which returns | ||
* an array of values when all have resolved. If any fail, the promise fails. | ||
* | ||
* @param {Array.<Promise|Object>} promises | ||
* @return {Promise.<Array.<Object>>} | ||
*/ | ||
function all(promises) { | ||
@@ -170,3 +319,3 @@ if (!promises.length) return resolve([]) | ||
if (counter === 0) { | ||
if (counter === 0 && !finished) { | ||
finished = true | ||
@@ -179,2 +328,7 @@ promise.resolve(outputs) | ||
/** | ||
* Create a new Promise which looks like a deferred | ||
* | ||
* @return {Promise} | ||
*/ | ||
function defer() { | ||
@@ -181,0 +335,0 @@ return new Promise() |
{ | ||
"name": "kew" | ||
, "description": "kew?" | ||
, "version": "0.0.4" | ||
, "description": "a lightweight promise library for node" | ||
, "version": "0.1.0" | ||
, "homepage": "https://github.com/Obvious/kew" | ||
@@ -21,7 +21,7 @@ , "authors": [ | ||
, "devDependencies": { | ||
"nodeunit": "*" | ||
, "jasmine-node": "*" | ||
"nodeunit": "0.7.4" | ||
} | ||
, "scripts": { | ||
} | ||
"test": "./node_modules/nodeunit/bin/nodeunit test" | ||
} | ||
} |
250
README.md
@@ -1,1 +0,249 @@ | ||
kew. | ||
kew: a lightweight (and super fast) promise/deferred framework for node.js | ||
================================== | ||
**kew** is a lightweight promise framework with an aim of providing a base set of functionality similar to that provided by the [Q library](https://github.com/kriskowal/q "Q"). | ||
A few answers (for a few questions) | ||
------- | ||
*Why'd we write it?* | ||
During our initial usage of **Q** we found that it was consuming 80% of the cpu under load (primarily in chained database callbacks). We spent some time looking at patching **Q** and ultimately found that creating our own lightweight library for server-usage would suit our needs better than figuring out how to make a large cross-platform library more performant on one very specific platform. | ||
*So this does everything Q does?* | ||
Nope! **Q** is still an awesome library and does *way* more than **kew**. We support a tiny subset of the **Q** functionality (the subset that we happen to use in our actual use cases). | ||
What are Promises? | ||
------- | ||
At its core, a *Promise* is a promise to return a value at some point in the future. A *Promise* represents a value that will be (or may return an error if something goes wrong). *Promises* heavily reduce the complexity of asynchronous coding in node.js-like environments. Example: | ||
```javascript | ||
// assuming the getUrlContent() function exists and retrieves the content of a url | ||
var htmlPromise = getUrlContent(myUrl) | ||
// we can then filter that through an http parser (our imaginary parseHtml() function) asynchronously (or maybe synchronously, who knows) | ||
var tagsPromise = htmlPromise.then(parseHtml) | ||
// and then filter it through another function (getLinks()) which retrieves only the link tags | ||
var linksPromise = tagsPromise.then(getLinks) | ||
// and then parses the actual urls from the links (using parseUrlsFromLinks()) | ||
var urlsPromise = linksPromise.then(linksPromise) | ||
// finally, we have a promise that should only provide us with the urls and will run once all the previous steps have ran | ||
urlsPromise.then(function (urls) { | ||
// do something with the urls | ||
}) | ||
``` | ||
How do I use **kew**? | ||
------- | ||
As a precursor to all the examples, the following code must be at the top of your page: | ||
```javascript | ||
var Q = require('kew') | ||
``` | ||
### Convert a literal into a promise | ||
The easiest way to start a promise chain is by creating a new promise with a specified literal using Q.resolve() or Q.reject() | ||
```javascript | ||
// create a promise which passes a value to the next then() call | ||
var successPromise = Q.resolve(val) | ||
// create a promise which throws an error to be caught by the next fail() call | ||
var failPromise = Q.reject(err) | ||
``` | ||
In addition, you can create deferreds which can be used if you need to create a promise but resolve it later: | ||
```javascript | ||
// create the deferreds | ||
var successDefer = Q.defer() | ||
var failDefer = Q.defer() | ||
// resolve or reject the defers in 1 second | ||
setTimeout(function () { | ||
successDefer.resolve("ok") | ||
failDefer.reject(new Error("this failed")) | ||
}, 1000) | ||
// extract promises from the deferreds | ||
var successPromise = successDefer.promise | ||
var failPromise = failDefer.promise | ||
``` | ||
If you have a node-style callback (taking an **Error** as the first parameter and a response as the second), you can call the magic `makeNodeResolver()` function on a defer to allow the defer to handle the callbacks: | ||
```javascript | ||
// create the deferred | ||
var defer = Q.defer() | ||
// some node-style function | ||
getObjectFromDatabase(myObjectId, defer.makeNodeResolver()) | ||
// grab the output | ||
defer.promise | ||
.then(function (obj) { | ||
// successfully retrieved the object | ||
}) | ||
.fail(function (e) { | ||
// failed retrieving the object | ||
}) | ||
``` | ||
### Handling successful results with `.then()` | ||
When a promise is resolved, you may call the `.then()` method to retrieve the value of the promise: | ||
```javascript | ||
promise.then(function (result) { | ||
// do something with the result here | ||
}) | ||
``` | ||
`.then()` will in turn return a promise which will return the results of whatever it returns (asynchronously or not), allowing it to be chained indefinitely: | ||
```javascript | ||
Q.resolve('a') | ||
.then(function (result) { | ||
return result + 'b' | ||
}) | ||
.then(function (result) { | ||
return result + 'c' | ||
}) | ||
.then(function (result) { | ||
// result should be 'abc' | ||
}) | ||
``` | ||
In addition, `.then()` calls may return promises themselves, allowing for complex nesting of asynchronous calls in a flat manner: | ||
```javascript | ||
var htmlPromise = getUrlContent(myUrl) | ||
var tagsPromise = htmlPromise.then(function (html) { | ||
if (!validHtml(html)) throw new Error("Invalid HTML") | ||
// pretend that parseHtml() returns a promise and is asynchronous | ||
return parseHtml(html) | ||
}) | ||
``` | ||
### Handling errors with `.fail()` | ||
If a promise is rejected for some reason, you may handle the failure case with the `.fail()` function: | ||
```javascript | ||
getObjectPromise | ||
.fail(function (e) { | ||
console.error("Failed to retrieve object", e) | ||
}) | ||
``` | ||
Like `.then()`, `.fail()` also returns a promise. If the `.fail()` call does not throw an error, it will pass the return value of the `.fail()` handler to any `.then()` calls chained to it: | ||
```javascript | ||
getObjectPromise | ||
.fail(function (e) { | ||
return retryGetObject(objId) | ||
}) | ||
.then(function (obj) { | ||
// yay, we received an object | ||
}) | ||
.fail(function (e) { | ||
// the retry failed :( | ||
console.error("Retrieving the object '" + objId + "' failed") | ||
}) | ||
}) | ||
``` | ||
If you've reached the end of your promise chain, you may call `.end()` which signifies that the promise chain is ended and any errors should be thrown in whatever scope the code is currently in: | ||
```javascript | ||
getObjectPromise | ||
// this will throw an error to the uncaught exception handler if the getObjectPromise call is asynchronous | ||
.end() | ||
``` | ||
### `.fin()` when things are finished | ||
You may attach a handler to a promise which will be ran regardless of whether the promise was resolved or rejected (but will only run upon completion). This is useful in the cases where you may have set up resources to run a request and wish to tear them down afterwards. `.fin()` will return the promise it is called upon: | ||
```javascript | ||
var connection = db.connect() | ||
var itemPromise = db.getItem(itemId) | ||
.fin(function () { | ||
db.close() | ||
}) | ||
``` | ||
Other utility methods | ||
------- | ||
There's only one utility method as of now: | ||
### `.all()` for many things | ||
If you're waiting for multiple promises to return, you may pass them (mixed in with literals if you desire) into `.all()` which will create a promise that resolves successfully with an array of the results of the promises: | ||
```javascript | ||
var promises = [] | ||
promises.push(getUrlContent(url1)) | ||
promises.push(getUrlContent(url2)) | ||
promises.push(getUrlContent(url3)) | ||
Q.all(promises) | ||
.then(function (content) { | ||
// content[0] === content for url 1 | ||
// content[1] === content for url 2 | ||
// content[2] === content for url 3 | ||
}) | ||
``` | ||
If any of the promises fail, Q.all will fail as well (so make sure to guard your promises with a `.fail()` call beforehand if you don't care whether they succeed or not): | ||
```javascript | ||
var promises = [] | ||
promises.push(getUrlContent(url1)) | ||
promises.push(getUrlContent(url2)) | ||
promises.push(getUrlContent(url3)) | ||
Q.all(promises) | ||
.fail(function (e) { | ||
console.log("Failed retrieving a url", e) | ||
}) | ||
``` | ||
Contributing | ||
------------ | ||
Questions, comments, bug reports, and pull requests are all welcome. | ||
Submit them at [the project on GitHub](https://github.com/Obvious/kew/). | ||
Bug reports that include steps-to-reproduce (including code) are the | ||
best. Even better, make them in the form of pull requests that update | ||
the test suite. Thanks! | ||
Author | ||
------ | ||
[Jeremy Stanley](https://github.com/azulus) | ||
supported by | ||
[The Obvious Corporation](http://obvious.com/). | ||
License | ||
------- | ||
Copyright 2013 [The Obvious Corporation](http://obvious.com/). | ||
Licensed under the Apache License, Version 2.0. | ||
See the top-level file `LICENSE.TXT` and | ||
(http://www.apache.org/licenses/LICENSE-2.0). |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
39928
1
8
772
250
1