diff --git a/patterns/conversion.md b/patterns/conversion.md new file mode 100644 index 0000000..d063c79 --- /dev/null +++ b/patterns/conversion.md @@ -0,0 +1,119 @@ +# Working with callbacks + +This page explains how promises work with APIs that return callbacks. + +When users have a callback API in a format like: + +###1. One time event: + +```js +process.on("rejectionHandled", function(e) { // I only ever want to deal with this once + +}); +``` + +###2. Plain non-standard callback: + +```js +function request(onChangeHandler){ +... +request(function(){ + // change happened +}); +``` + +###3. Standard style callback ("nodeback"): + +```js +function getStuff(dat,callback){ +... +getStuff("dataParam",function(err,data){ + +} +``` +###4. A whole library with node style callbacks: + +```js +API; +API.one(function(err,data){ + API.two(function(err,data2){ + API.three(function(err,data3){ + + }) + }); +}); +``` + +###How do I work with the API in promises, how do I "promisify" it? + +First of all, there are plans to add support to promises in core. You can track +that here: https://github.com/nodejs/node/pull/5020 + +APIs should only be converted to return promises if they do not already return promises. + +Promises have state, they start as pending and can settle to: + + - __fulfilled__ meaning that the computation completed successfully. + - __rejected__ meaning that the computation failed. + +Promise returning functions _should never throw_, they should return rejections instead. +Throwing from a promise returning function will force you to use both a `} catch { ` _and_ a `.catch`. +People using promisified APIs do not expect promises to throw. If you're not sure how async +APIs work in JS - please [see this answer](http://stackoverflow.com/questions/14220321) first. + +###1. DOM load or other one time event: + +So, creating promises generally means specifying when they settle - that means when they move +to the fulfilled or rejected phase to indicate the data is available (and can be accessed with `.then`). + +```js +function load() { // our event + return new Promise(function(resolve,reject) { // create a new promise + process.on("rejectionHandled", resolve); // resolve it when the event happens + }); +} +``` + +###2. Non-Standard callback: + +These APIs are rather common since well... callbacks are common in JS. In Node it is strictly prefered +that callback code you write uses the standard `(err, data)` convention. + +Let's look at the common case of having `onSuccess` and `onFail`: + +function getUserData(userId, onLoad, onFail){ ... + +This would look like the following + +```js +function getUserDataAsync(userId) { + return new Promise(function(resolve,reject) { + getUserData(userId, resolve, reject); // pass reject/resolve handlers explicitly + }); +} +``` + +###3. Standard style callback ("nodeback"): + +Node style callbacks (nodebacks) have a particular format where the callbacks is always the last +argument and its first parameter is an error. Let's first promisify one manually: + +getStuff("dataParam",function(err,data){ + +To: + +```js +function getStuffAsync(param) { // a function that takes the parameter without the callback + return new Promise(function(resolve,reject) { // we invoke the promise constructor + getStuff(param,function(err,data) { // call the function internally + if(err) return reject(err); // if it errord we reject + resolve(data); // otherwise re resolve + }); + }); +} +``` +Notes: + + - Of course, when you are in a `.then` handler you do not need to promisify things. + Returning a promise from a `.then` handler will resolve or reject with that promise's value. + Throwing from a `.then` handler is also good practice and will reject the promise. diff --git a/patterns/disposer.md b/patterns/disposer.md new file mode 100644 index 0000000..43d1b20 --- /dev/null +++ b/patterns/disposer.md @@ -0,0 +1,84 @@ +# The Promise Disposer Pattern + +Often we have to perform resource management in code. For example - release a DB connection: + + +```js +var users = getDb().then(conn => + conn.query("SELECT name FROM users").then(users => { + conn.release(); + return users; + }, e => { + conn.release(); + throw e; + }) +}); +``` + +The problem with the above approach is that if you forget releasing the connection after every single +time you perform `getDb` you have a resource leak that might freeze your app eventually +when it runs out of the resource you're leaking. + +The above approach means that the user has to manually manage resources. + +The user might in one place do: + +```js +var users = getDb().then(function(conn) { + return conn.query("SELECT name FROM users"); +}); +``` + +Which will leak a database connection that was never closed. + +The disposer pattern is a way to couple a scope of code with owning the resource. By binding the resource +to a scope we make sure it is always released when we're done with it and we can't easily forget +to release it. It is similar to `using` in C#, `with` in Python and try-with-resource in Java +as well as RAII in C++. + + +It looks like: + +```js + withResource(function(resource) { + return fnThatDoesWorkWithResource(resource); // returns a promise + }).then(function(result) { + // resource disposed here + }); +``` + +If we wrote our code as: + +```JS +function withDb(work) { + var _db; + return myDbDriver.getConnection().then(function(db) { + _db = db; // keep reference + return work(db); // perform work on db + }).then(v => { + if (_db) { + _db.release(); + } + return v; + }, e => { + if(_db) { + _db.release(); + } + throw e; + }); +} +``` + +We could write our above code as: + +```js +withDb(conn => + conn.query("SELECT name FROM users"); +).then(users => { + // connection released here +}); +``` + +Userland examples of users of the disposer pattern are [sequelize](https://github.com/sequelize/sequelize/issues/1939) +and [knex](https://github.com/tgriesser/knex/issues/265) (bookshelf's query builder). It's also possible to use it +for simpler things like hiding a loader when all AJAX requests completed for instance. diff --git a/patterns/explicit-construction.md b/patterns/explicit-construction.md new file mode 100644 index 0000000..b3aefc8 --- /dev/null +++ b/patterns/explicit-construction.md @@ -0,0 +1,46 @@ +##The Explicit Construction Anti-Pattern + +This is the most common anti-pattern. It is easy to fall into this when you haven't really grasped +promises. It's also sometimes called the promise constructor anti-pattern. Let's recap: promises +are about making asynchronous code retain most of the lost properties of synchronous code such +as flat indentation and one exception channel. + +This pattern is also called the deferred anti-pattern. + +In the explicit construction anti-pattern, promise objects are created for no reason, complicating code. + +First example is creating a new promise object when you already have a promise or thenable: + +```js +function getFullName(userId) { + return new Promise(function(resolve, reject) { + db.getUsers(userId).then(data => { + resolve(data.fullName); + }); + }); +} +``` + +This superfluous wrapping is also dangerous, any kind of errors and rejections are swallowed and +not propagated to the caller of this function. + +Instead of using the above the code should simply return the promise it already has and propagate +values using `return`: + +```js +function getFullName(userId) { + return db.getUsers(userId).then(data => + data.fullName) + ); + }); +} +``` + +Not only is the code shorter but more importantly, if there is any error it will propagate properly to +the final consumer. + +**So when should the promise constructor be used?** + +Well simply, when you have to. + +You might have to use explicit wrapping object when wrapping a callback API.