laissez-faire
Advanced tools
Comparing version 0.0.2 to 0.1.0
17
glue.js
@@ -5,3 +5,2 @@ var fs = require('fs'), | ||
new Glue() | ||
// Declare dependencies | ||
.include('src/') | ||
@@ -15,2 +14,16 @@ .set('debug', true) | ||
}) | ||
}); | ||
}); | ||
new Glue() | ||
.include('tests/') | ||
.exclude(/browser-built\.js$/) | ||
.replace({ | ||
'sinon': 'window.sinon', | ||
'assert': 'window.assert' | ||
}) | ||
.set('debug', true) | ||
.main('tests/browser') | ||
.export('tests') | ||
.watch(function (err, txt) { | ||
fs.writeFile('tests/browser-built.js', txt) | ||
}) |
{ | ||
"name": "laissez-faire", | ||
"version": "0.0.2", | ||
"description": "A lightweight library that provides tools for abstracting async processes", | ||
"version": "0.1.0", | ||
"description": "A promise implementation not compliant with the commonjs Promises/A spec. Helps to abstract the when from computation. Simple and fast", | ||
"main": "src/index.js", | ||
@@ -10,4 +10,10 @@ "directories": { | ||
"devDependencies": { | ||
"gluejs": "*" | ||
"gluejs": "*", | ||
"mocha": "*", | ||
"sinon": "*" | ||
}, | ||
"scripts" : { | ||
"build": "node ./glue.js", | ||
"test": "node ./tests/" | ||
}, | ||
"repository": { | ||
@@ -14,0 +20,0 @@ "type": "git", |
232
README.md
@@ -1,231 +0,1 @@ | ||
# RSVP.js | ||
RSVP.js provides simple tools for organizing asynchronous code. | ||
Specifically, it is a tiny implementation of Promises/A and a | ||
mixin for turning objects into event targets. | ||
It works in node and the browser. You can get the browser build in | ||
`browser/rsvp.js` and `browser/rsvp.min.js`. | ||
## Promises | ||
`RSVP.Promise` is an implementation of | ||
[Promises/A](http://wiki.commonjs.org/wiki/Promises/A) that passes the | ||
[promises test suite](https://github.com/domenic/promise-tests) written | ||
by Domenic Denicola. | ||
It passes both the primary suite, which tests explicit compliance with | ||
the Promises/A spec, and the extension tests, which test compliance with | ||
commonly accepted practices around promises in JavaScript. | ||
It delivers all promises asynchronously, even if the value is already | ||
available, to help you write consistent code that doesn't change if the | ||
underlying data provider changes from synchronous to asynchronous. | ||
It is compatible with [TaskJS](http://taskjs.org/), a library by Dave | ||
Herman of Mozilla that uses ES6 generators to allow you to write | ||
synchronous code with promises. It currently works in Firefox, and will | ||
work in any browser that adds support for ES6 generators. See the | ||
section below on TaskJS for more information. | ||
### Basic Usage | ||
```javascript | ||
var promise = new Promise(); | ||
promise.then(function(value) { | ||
// success | ||
}, function(value) { | ||
// failure | ||
}); | ||
// later... | ||
promise.resolve(value) // triggers first callback | ||
promise.reject(error) // triggers second callback | ||
``` | ||
Once a promise has been resolved or rejected, it cannot be resolved or | ||
rejected again. | ||
Here is an example of a simple XHR2 wrapper written using RSVP.js: | ||
```javascript | ||
var getJSON = function(url) { | ||
var promise = new RSVP.Promise(); | ||
var client = new XMLHttpRequest(); | ||
client.open("GET", url); | ||
client.onreadystatechange = handler; | ||
client.responseType = "json"; | ||
client.setRequestHeader("Accept", "application/json"); | ||
client.send(); | ||
function handler() { | ||
if (this.readyState === this.DONE) { | ||
if (this.status === 200) { promise.resolve(this.response); } | ||
else { promise.reject(this); } | ||
} | ||
}; | ||
return promise; | ||
}; | ||
getJSON("/posts.json").then(function(json) { | ||
// continue | ||
}, function(error) { | ||
// handle errors | ||
}); | ||
``` | ||
### Chaining | ||
One of the really awesome features of Promises/A promises are that they | ||
can be chained together. In other words, the return value of the first | ||
resolve handler will be passed to the second resolve handler. | ||
If you return a regular value, it will be passed, as is, to the next | ||
handler. | ||
```javascript | ||
getJSON("/posts.json").then(function(json) { | ||
return json.post; | ||
}).then(function(post) { | ||
// proceed | ||
});; | ||
``` | ||
The really awesome part comes when you return a promise from the first | ||
handler: | ||
```javascript | ||
getJSON("/post/1.json").then(function(post) { | ||
// save off post | ||
return getJSON(post.commentURL); | ||
}).then(function(comments) { | ||
// proceed with access to posts and comments | ||
});; | ||
``` | ||
This allows you to flatten out nested callbacks, and is the main feature | ||
of promises that prevents "rightward drift" in programs with a lot of | ||
asynchronous code. | ||
Errors also propagate: | ||
```javascript | ||
getJSON("/posts.json").then(function(posts) { | ||
}).then(null, function(error) { | ||
// even though no error callback was passed to the | ||
// first `.then`, the error propagates | ||
}); | ||
``` | ||
You can use this to emulate `try/catch` logic in synchronous code. | ||
Simply chain as many resolve callbacks as a you want, and add a failure | ||
handler at the end to catch errors. | ||
```javascript | ||
getJSON("/post/1.json").then(function(post) { | ||
return getJSON(post.commentURL); | ||
}).then(function(comments) { | ||
// proceed with access to posts and comments | ||
}).then(null, function(error) { | ||
// handle errors in either of the two requests | ||
}); | ||
``` | ||
## TaskJS | ||
The [TaskJS](http://taskjs.org/) library makes it possible to take | ||
promises-oriented code and make it synchronous using ES6 generators. | ||
Let's review an earlier example: | ||
```javascript | ||
getJSON("/post/1.json").then(function(post) { | ||
return getJSON(post.commentURL); | ||
}).then(function(comments) { | ||
// proceed with access to posts and comments | ||
}).then(null, function(error) { | ||
// handle errors in either of the two requests | ||
}); | ||
``` | ||
Without any changes to the implementation of `getJSON`, you could write | ||
the following code with TaskJS: | ||
```javascript | ||
spawn(function *() { | ||
try { | ||
var post = yield getJSON("/post/1.json"); | ||
var comments = yield getJSON(post.commentURL); | ||
} catch(error) { | ||
// handle errors | ||
} | ||
}); | ||
``` | ||
In the above example, `function *` is new syntax in ES6 for | ||
[generators](http://wiki.ecmascript.org/doku.php?id=harmony:generators). | ||
Inside a generator, `yield` pauses the generator, returning control to | ||
the function that invoked the generator. In this case, the invoker is a | ||
special function that understands the semantics of Promises/A, and will | ||
automatically resume the generator as soon as the promise is resolved. | ||
The cool thing here is the same promises that work with current | ||
JavaScript using `.then` will work seamlessly with TaskJS once a browser | ||
has implemented it! | ||
## Event Target | ||
RSVP also provides a mixin that you can use to convert any object into | ||
an event target. The promises implementation uses `RSVP.EventTarget`, so | ||
`RSVP` exposes it for your own use. | ||
### Basic Usage | ||
The basic usage of `RSVP.EventTarget` is to mix it into an object, then | ||
use `on` and `trigger` to register listeners and trigger them. | ||
```javascript | ||
var object = {}; | ||
RSVP.EventTarget.mixin(object); | ||
object.on("finished", function(event) { | ||
// handle event | ||
}); | ||
object.trigger("finished", { detail: value }); | ||
``` | ||
### Prototypes | ||
You can mix `RSVP.EventTarget` into a prototype and it will work as | ||
expected. | ||
```javascript | ||
var Person = function() {}; | ||
RSVP.EventTarget.mixin(Person.prototype); | ||
var yehuda = new Person(); | ||
var tom = new Person(); | ||
yehuda.on("poke", function(event) { | ||
console.log("Yehuda says OW"); | ||
}); | ||
tom.on("poke", function(event) { | ||
console.log("Tom says OW"); | ||
}); | ||
yehuda.trigger("poke"); | ||
tom.trigger("poke"); | ||
``` | ||
The example will work as expected. If you mix `RSVP.EventTarget` into a | ||
constructor's prototype, each instance of that constructor will get its | ||
own callbacks. | ||
# Laissez-faire It works in [node](./src/index.js) and the [browser](./browser/Promise.js). Anything in the name? Yea its a French word meaning "let it be". Seemed apt for a library designed to eliminate the need to worry about when computation occurs. ## Differences from other libraries `Laissez-faire` is compliant with the [Promises/A](http://wiki.commonjs.org/wiki/Promises/A) spec as far as I understand it. However, it does break from some patterns which are common in other implementations. Most implementations allow you to throw any value and they will treat it as an error. I think this is a pointless feature so have built the assumption that all Errors will result in rejections and anything else results in the fulfillment. This means you don't need to throw errors, you can return them with the exact same effect. Also all popular implementations guarantee async calls to `then`. Again I believe this is a stupid feature so have not included it. Also most implementations provide a separate resolver objects from the promise itself. This is intended to protect again assholes resolving promises they didn't make. Other libraries will often also freeze promises after they are fulfilled/rejected to again prevent assholes from causing trouble. Why is everyone so paranoid around promises? I left these security features out of my implementation since the cost benefit seemed way out of balance. Learn what promises are for and use them for that purpose and everyone can sleep fine at night. As a result of the different error handling semantics Laissez-faire fails many of the common tests found in the [promises test suite](https://github.com/domenic/promise-tests) written by Domenic Denicola. Though Since most people only use real Errors anyway though you should be able to replace your existing promise implementation with `Laissez-faire` fairly easily. It comes with a lightly modified version of the promises test suite as well as a few extra suites for `Laissez-faire` specific features. Its also the smallest implementation I have seen and probably the fastest too. I haven't done any benchmarks but I would expect performance to be similar to that of an EventEmitter. ### What makes it better 1. Smaller 2. Faster 3. Simpler error semantics 4. Easier to debug (uncaught errors are logged) 5. Flexible API ### What makes it worse 1. Not asshole proof ## API ```javascript var Promise = require('laissez-faire') var promise = new Promise ``` * __promise.then(done, success)__ * __promise.end(done, success)__ * __promise.later(done, success)__ * __promise.resolve(value)__ * __promise.reject(error)__ * __promise.assign(value|error)__ Handle uncaught errors just set `Promise.prepareException = function (failingPromise, error) {}` And to cancel the logic you set in motion set `Promise.cancelException = function (noLongerfailingPromise) {}` ## Basic Usage ```javascript var promise = new Promise(); promise.then(function(value) { // success }, function(value) { // failure }); // later... promise.resolve(value) // triggers first callback // or... promise.reject(error) // triggers second callback ``` Once a promise has been resolved or rejected, it cannot be resolved or rejected again. Here is an example of a simple XHR2 wrapper written using `Laissez-faire`: ```javascript var getJSON = function(url) { var promise = new Promise() var client = new XMLHttpRequest() client.open("GET", url) client.onreadystatechange = handler client.responseType = "json" client.setRequestHeader("Accept", "application/json") client.send() function handler() { if (this.readyState === this.DONE) { if (this.status === 200) promise.resolve(this.response) else promise.reject(this) } }; return promise; }; getJSON("/posts.json").then(function(json) { // continue }, function(error) { // handle errors }); ``` ## Understanding promises I struggled to understand promises and that is why I started playing with this project. It would be rude of me not to share my learnings. I hope my explanation prevents you from needing to take the same route in order to understand them. Every explanation I have read begins like this: "A promise represents the eventual value returned from the single completion of an operation". I have read a few and they all sounded suspiciously wrote. That one was from the Promises/A spec. Its a shame they all choose to use the word "eventual" since it is actually slightly misleading. Promises may be resolved now. Therefore, a simpler and more accurate way of introducing promises would be to say "A promise represents the value of a computation". This of course would raise the question: isn't that was variables are for? Yes that is what variables are for. Except variables break if the computation occurs at any time other than right now. And now you see the problem they are designed to solve. Promises allow you to not care when the computation occurs just that it does. In order to do this they need to do two things. 1. Store the result of the computation as soon as it happens 2. Provide a mechanism to connect further computations on this value By providing these capabilities they allow you to ignore the timing of computation completely. They could easily become a language feature complimenting the less powerful variable. You saw promises in use in the previous section but lets walk through how they are used since they look nothing like variables syntactically. First we create the promise: `var promise = new Promise`. So we need to use a variable in order to use a promise. You could say then that we are creating an enhanced variable. Then we do some computation and assign our value to the promise `return promise.assign('some value')`. Note you must always `return` the promise now though you don't have to assign to it until you are good and ready. So your computations must know how to manage a promise, as shame since semantically they aren't much different from variables, though blame that one on the language designers. Maybe one day JavaScript will be inspired and adopt message passing like Io and Smalltalk, maybe. Lets see if we can visualize a program written synchronously and contrast it with one using promises in place of variables. Then lets look at one using callbacks. Lets perform a sequence of additions on unknown values. In summary promises are a way of storing and accessing values devoid of when. That makes them very useful for situations where you can't be certain something is going to be sitting in memory ready to go but would like to take advantage of the speed if it is. Callbacks enable this and will always be faster, however promises provide the value and error propagation infrastructure to make these programs easier to reason about. Plus implementations like `Laissez-faire` are actually fairly light weight so offer good bang for your buck. The time they save you can coding be spent optimizing algorithms. Sync only: ```javascript var a = {}, b = 2 try { // var c = a + b throw new Error } catch (e) { c = 0 } c += 1 console.log(c) ``` Note: JavaScript won't throw an error when adding an object and a number like I though when I first wrote the example so we are throwing one manually. Timing irrelevant (with promises): ```javascript var a = new Promise().assign({}), b = new Promise().assign(2), c = a .then(function(a){ return b.then(function(b) { // return a + b return new Error }) }) .then(null, function(e) { return 0 }) .then(function (c) { return c + 1 }) .then(console.log.bind(console)) ``` Here we assigned values to the promises immediately, and therefore, the subsequent computations were run immediately. However, if it took all year for their values to be computed that would be fine. The first operation actually returns a promise. This is fine since when `Laissez-faire` see a promise being resolved with a promise it is smart enough to know not to take this literally and will instead fetch the value from the returned promise before resolving for real. In JavaScript the + operator returns a value instantly though the above code would work fine if it was to return a promise and take its sweet time doing the actual operation. Timing irrelevant (with callbacks): ```javascript var c getA(function(a){ getB(function(b){ try { // c = a + b throw new Error } catch (e) { c = 0 } c.plus(1, console.log.bind(console)) })) }) ``` Notice: that we used a plus method with a callback. Since that for this entire operation to be free from the need for instant operation every operation must be async/sync safe. It still depends on the assignment operation being done instantly however. ## Chaining If you return a regular value, it will be passed, as is, to the next handler. ```javascript getJSON("/posts.json") .then(function(json) { return json.post }) .then(function(post) { // proceed }); ``` Returned promises are recognized as promises and have their resolved value is used to resolve the promise they were returned to ```javascript getJSON("/post/1.json") .then(function(post) { // save off post return getJSON(post.commentURL) }) .then(function(comments) { // proceed with access to posts and comments }) ``` ## Error Handling Errors also propagate: ```javascript getJSON("/badurl.json") .then(function(posts) { }) .then(null, function(error) { // even though no error callback was passed to the // first `.then`, the error propagates }) ``` Technically a promise can never throw an error since at any time in the future a user could add a child promise and handle the exception. However in practice the promise user will bind their error handlers immediately, so in most cases unhandled rejections which sit around for more than one tick of the event loop are genuine errors. `Laissez-faire` provides a mechanism for you to hook into these errors. See the source for details. It actually uses this mechanism itself to provide a nice default behavior which is to log the errors to the console after a 1 second delay. If this doesn't suit you then it is easy to override the behavior. ## Finishing with a promise Promises represent values. Like any other value you can never be sure it will never be used again until nobody has access to it. If however you have a promise and you know the next operation you do one it will be your last there is no need to create a chainable promise from it. To allow you to reduce the cost this operation (by a little) `Laissez-faire` provides an `end` method. It will provide the promises value to your callbacks as usual but will not return a new promise. Thereby, saving some computation. We also take advantage of the situation and allow any uncaught errors to blow up since we know you have no plans of catching them. Furthermore, `end` will return `this` thereby allowing a jquery style use of promises which is nice where appropriate. ```javascript var p = new Promise().assign(1) .end(function(val){ // val === 1 return val + 1 }) .end(function(val){ // val === 1 return val + 1 }) .end(function(val){ // this error will not be caught throw new Error }) .then(function(val){ // val === 1 return val + 1 }, function(){ // will not handle exception since it is on another branch }) .then(function(val){ // finally val === 2 console.log(val) }) ``` ## Generators Promises are often compared to the upcoming JavaScript feature "Generators". It is true that they are the solution to many of the same problems; however, they have very different semantics. Don't get confused by comparisons between the two like I did. Generators are probably closer to event emitters. ## Mutability Its widely considered that promises should be immutable and I agree. I though it was interesting to note however though what happens to promises when they are implemented in a mutable way. In such an implementation I would expect that when a promise has its value changed it would propagate this value to any child promises. At which point you would of switched to the [Reactive programming](http://en.wikipedia.org/wiki/Reactive_programming) paradigm. The change in implementation to create this effect is trivial. I guess we can conclude then that promises borderline bring a new paradigm to JavaScript. Its amazing how flexible this language is. |
128
src/index.js
@@ -15,2 +15,27 @@ 'use strict'; | ||
/** | ||
* Dafualt uncought error handler. It will wait 1 second for you to handle the error before logging it to the console. Feel free to replace this function with one you find more useful. Its purely a debugging feature. Technically promises throw unhandled exceptions. They just sit around waiting for you to handle them | ||
* | ||
* @param {Promise} promise The promise that was rejected without an error handler or child promise | ||
* @param {Error} e The Error instance emitted by the promise | ||
*/ | ||
Promise.prepareException = function (promise, e) { | ||
// If we were to log the error immeadiatly a synchronously rejected promise might incorrectly have its error logged | ||
promise._throw = setTimeout(function () { | ||
console.warn(promise, | ||
'\nPromise error ('+(e.message.replace(/^Error:\s+/, ''))+') not handled within 1 second' | ||
+ e.stack.toString().replace(/^.*/, '')) | ||
}, 1000) | ||
} | ||
/** | ||
* Sometimes error handlers may be added after a promise has been rejected. If you would like to cancel any undhandled exception handling logging logic you can do so by replaceing this function | ||
* | ||
* @param {Promise} promise The promise which previously failed without error handlers | ||
*/ | ||
Promise.cancelException = function (promise) { | ||
clearTimeout(promise._throw); | ||
delete promise._throw | ||
} | ||
/** | ||
* Process the current promise and propagate the resulting value/error to the next promise in sequence | ||
@@ -65,4 +90,3 @@ * | ||
else if (this.isRejected) { | ||
// clearTimeout(this._throw); | ||
// delete this._throw | ||
Promise.cancelException(this) | ||
run(thenPromise, fail, this.reason) | ||
@@ -78,10 +102,27 @@ } | ||
/** | ||
* Create a shallow branch. Allows jquery style use of promises | ||
* Like then but is designed for terminal operations. So any values generated will not be available for follow up and uncought errors will be thrown. Use this method in place of then when you don't plan to do anything with the stuff generated by your callbacks | ||
* | ||
* @param {Function} done | ||
* @param {Function} fail | ||
* @return {Promise} p.and() === p | ||
* @param {Function} done | ||
* @param {Function} fail | ||
* @return {Self} p.end() === p; Allowing jquery style use of promises | ||
*/ | ||
proto.and = function (done, fail) { | ||
this.then(done, fail) | ||
proto.end = function (done, fail) { | ||
if (this.isResolved) { | ||
typeof done === 'function' && done(this.value) | ||
} | ||
else if (this.isRejected) { | ||
if (typeof fail === 'function') { | ||
Promise.cancelException(this) | ||
fail(this.reason) | ||
} else { | ||
throw this.reason | ||
} | ||
} | ||
else { | ||
// Queue an unsafe sudo-promise | ||
this.children.push({ | ||
resolve: done, | ||
reject: fail | ||
}) | ||
} | ||
return this | ||
@@ -91,21 +132,26 @@ } | ||
/** | ||
* Like then but throws errors instead of propagating them | ||
* Works like then except it gaurentees your callbacks won't be called now. It does not however gaurontee asynchronosity, just that you have some time to manipulate the promise before your callbacks are called. | ||
* | ||
* @param {Function} done | ||
* @param {Function} fail | ||
* @return {undefined} Since this should be the last call in any chain you don't need anything back | ||
* @example | ||
* var p = new Promise() | ||
* var b = p.later().success(function(val){return val + 1}) | ||
* p.assign(1) | ||
* | ||
* The above is still synchronous since p was not resolved at the time later was called | ||
*/ | ||
proto.end = function (done, fail) { | ||
this.then(done, function (e) { | ||
if (typeof fail === 'function') { | ||
try { | ||
e = fail(e) | ||
} catch (err) { | ||
e = err | ||
} | ||
} | ||
if (e instanceof Error) setTimeout(function () { | ||
throw e | ||
}, 0) | ||
}) | ||
proto.later = function (done, fail) { | ||
var thenPromise = new Promise(done, fail) | ||
if (this.isResolved) { | ||
setTimeout(run.bind(this, thenPromise, done, this.value), 0) | ||
} | ||
else if (this.isRejected) { | ||
Promise.cancelException(this) | ||
setTimeout(run.bind(this, thenPromise, fail, this.reason), 0) | ||
} | ||
else { | ||
this.children.push(thenPromise) | ||
} | ||
return thenPromise | ||
} | ||
@@ -139,2 +185,3 @@ | ||
* @param {!Error} value Can be anything other than an Error | ||
* @return {Self} | ||
*/ | ||
@@ -146,5 +193,6 @@ proto.resolve = function (value) { | ||
this.resolve = this.reject = noop | ||
this.children.forEach(function (promise) { | ||
run(promise, promise._success, this.value) | ||
}, this) | ||
var children = this.children, | ||
len = children.length, | ||
i = -1 | ||
while (++i < len) run(children[i], children[i]._success, value) | ||
return this | ||
@@ -157,2 +205,3 @@ } | ||
* @param {Error} e The reason for rejection | ||
* @return {Self} | ||
*/ | ||
@@ -164,14 +213,21 @@ proto.reject = function (e) { | ||
this.resolve = this.reject = noop | ||
if (this.children.length) { | ||
this.children.forEach(function (promise) { | ||
run(promise, promise._fail, this.reason) | ||
}, this) | ||
var children = this.children, | ||
len = children.length | ||
if (len) { | ||
var i = 0 | ||
do run(children[i], children[i]._fail, e); while (++i < len) | ||
} | ||
else { | ||
// Throwing the error is done async to allow subsequent calls to `then` to add error handling | ||
// this._throw = setTimeout(function () { | ||
// throw e | ||
// }, 0) | ||
// Technically a promise can never throw an error since at any time in the future a user could add a child promise and handle the exception. However in practice the promise user probably has no plans of handling rejected promise later, so in most cases unhandled rejections are genuine errors. The following method provides a place for users to access these errors. | ||
Promise.prepareException(this, e) | ||
} | ||
return this | ||
} | ||
/** | ||
* Alias resolve for people like myself who prefer to allways use the same method when completing a promise | ||
* | ||
* @param {Any} value If you pass an Error the promise will be rejected. Any other value, including null/undefined will result in the promise being fulfiled with that value | ||
* @return {Self} | ||
*/ | ||
proto.assign = proto.resolve |
20981
231
3
1