On Github nolanlawson / nyc-camp-2015
db.get('my_doc_id').then(function(doc) {
return db.remove(doc);
}).then(function (result) {
return db.info();
}).then(function (info) {
// yay I'm done
}).catch(function (err) {
// ugh I got an error
});
I wrote it because I work on a very promise-heavy open-source library
called PouchDB, and I was tired of perusing the questions on
Stack Overflow and seeing the same errors again and again.
doSomething().then(function () {
return doSomethingElse();
});
doSomething().then(function () {
doSomethingElse();
});
doSomething().then(doSomethingElse());
doSomething().then(doSomethingElse);
doSomething().then(function () { // <- good pattern
return doSomethingElse();
});
doSomething().then(function () { // <- trap!
doSomethingElse();
});
doSomething().then(doSomethingElse()); // <- trap!
doSomething().then(doSomethingElse); // <- good pattern
public String readFile(String filename) throws IOException {
String output = "";
BufferedReader buff = new BufferedReader(
new InputStreamReader(new FileInputStream(new File(filename))));
while (buff.ready()) {
output += buff.readLine() + "\n";
}
buff.close();
return output;
}
Here's an example from Java. Now, what's
interesting here is that, from the point of view of a beginner,
there's no difference between this part of the code, "readLine()"
and the rest of tit. But if we were to promise this function
without our larger code, it'd probably be the readLine() that ends
up costing the most, especially if we're working with large files.
In short, bottlenecks tend to pile up exactly where we're doing these sorts
of InputStream operations.
You might asay that the IOException communicates that, but there are
plenty of parts of the Java core libraries that throw errors like
this, e.g. the JSON parser or SAX parser, which doesn't require any I/O.
import requests;
response = requests.get('http://oursite.com/ourapi/');
json = response.json()
print json
Here's an example from Python. Again, we really have no reason,
as a beginner, to suspect that that second line will probably
be much slower than any other line in our code. But of course an
experienced developer will know that this HTTP server could take
a very long time to respond, or not at all, so you better keep
an eye on that part of the code.
var xhr = new XMLHttpRequest();
xhr.onerror = function (err) {
console.log('whoops, got an error', err);
};
xhr.onload = function (res) {
console.log('woohoo, got a response', res);
};
xhr.open('GET', 'http://oursite.com/ourapi/');
The guy who designed JavaScript, Brendan Eich, was pretty smart.
and one of the interesting design decisions he made for
JavaScript was to make it single-threaded, with callbacks for
AJAX requests. In JavaScript, this allows the single thread to
continue working, e.g. responding to user clicks or updating
the DOM, even when an HTTP request is ongoing.
$.ajax("http://oursite.com/ourapi/", {
success: function (res) {
console.log("woohoo, got a response", res);
},
error: function (err) {
console.log("whoops, got an error", err);
}
});
console.log('firing request');
The guy who designed JavaScript, Brendan Eich, was pretty smart.
and one of the interesting design decisions he made for
JavaScript was to make it single-threaded, with callbacks for
AJAX requests. In JavaScript, this allows the single thread to
continue working, e.g. responding to user clicks or updating
the DOM, even when an HTTP request is ongoing.
var fs = require('fs');
fs.readFile('/tmp/myfile.txt', 'utf-8', function (err, text) {
if (err) {
// handle error
} else {
// handle success
}
});
This style continued with Node.js, and in fact it's the primary
reason Ryan Dahl chose JavaScript for his single-threaded server
framework. Here's a filesystem operation, which doesn't exist
in the browser, but which looks very similar to the ajax
request we saw earlier.
So, in JavaScript, it's extremely obvious when a piece of code
goes to the disk or to the network, because there's this
awkward callback style you have to deal with. And it turns out
this is kind of nice, because as this infographic shows,
the network is much slower than the disk, which is much slower
than anything done in-memory. My takeaway is: disk is about a
zillion times slower than in-memory, and the network is about
a zillion times slower than that.
So JavaScript makes it really obvious when you're going to disk
or the network, which can be nice for identifying bottlenecks
or for ensuring that your single-threaded language doesn't spend
any time waiting on I/O. And when webapps were using some light
jQuery for a few ajax requests, which were almost never chained
together, this was almost tolerable. But when Node.js came along,
it just ended up being a huge hassle to write everything in this
pyramid style when you just want to do a few I/O operations.
The Node community tried to solve this early on by standardizing
everything related to callbacks. But really this was just a convention,
and nobody was forced to adhere to it, and people frequently
messed it up. A common source of errors was accidentally calling
a callback twice, or zero times, or with both an error and
a result, etc. Lots of libraries sprang up proposing to solve
this problem, such as async and q. But in fact they had very different
and incompatible approaches, and jQuery and Angular had their own,
so out of all this mess a few very smart people managed to hammer
together a spec, and got almost everybody to agree on a small core
of best practices, which they called the Promises A+ spec. Today
this is enshrined in ES6, and it's in every major browser and soon
Node.js, so it's here for the long haul. The only major holdouts
are, sadly, jQuery and async. But you can convert their style to
the official style, or just not use them.
Okay, so everybody started using promises, and everything was great,
right? Well, not exactly. First off, there is some confusion
about which libraries and browsers actually use promises, and then
second off there is confusion about how to use promises correctly.
Let's talk about the libraries and browsers first.
doSomething(function (err) {
if (err) { return handleError(err); }
doSomethingElse(function (err) {
if (err) { return handleError(err); }
doAnotherThing(function (err) {
if (err) { return handleError(err); }
// handle success
});
});
});
function handleError(err) {
// handle error
}
doSomething(function (err) {
if (err) { return handleError(err); } // wait why is my code
doSomethingElse(function (err) {
if (err) { return handleError(err); } // on a deathmarch
doAnotherThing(function (err) {
if (err) { return handleError(err); } // to the right of the screen
// handle success
});
});
});
function handleError(err) {
// handle error
}
doSomething().then(function (result) {
// handle success
}).catch(function (err) {
// handle error
});
doSomething().then(function () {
return doSomethingElse();
}).then(function () {
return doAnotherThing();
}).then(function (result) {
// yay, I'm done
}).catch(function (err) {
// boo, I got an error
});
doSomething().then(function () {
return doSomethingElse(); // ________
}).then(function () { // |__MEOW__|
return doAnotherThing(); // \_/
}).then(function (result) { // /\_/\
// yay, I'm done // ____/ o o \
}).catch(function (err) { // /~____ =ø= /
// boo, I got an error // (______)__m_m)
});
doSomething().then(function () {
doSomethingElse().then(function () {
doAnotherThing().then(function () {
// handle success
}).catch(function (err) {
// handle error
});
}).catch(function (err) {
// handle error
});
}).catch(function (err) {
// handle error
});
doSomething().then(function () {
return doSomethingElse();
}).then(function () {
return doAnotherThing();
}).then(function (result) {
// handle success
}).catch(function (err) {
// handle error
});
playWithFire().then(function () {
return liveDangerously();
}).then(function () {
return betTheFarm();
}).then(function (result) {
// handle success
}); // ← forgot to catch. any errors will be swallowed
playWithFire().then(function () {
return liveDangerously();
}).then(function () {
return betTheFarm();
}).then(function (result) {
// handle success
}).catch(console.log.bind(console)); // ← this is badass
goToTheATM().then(function () {
grabMyCash();
}).then(function () {
grabMyCard();
}).then(function (result) {
// grabMyCash() and grabMyCard()
// are not done yet!
}).catch(console.log.bind(console));
goToTheATM().then(function () {
return grabMyCash();
}).then(function () {
return grabMyCard();
}).then(function (result) {
// yay, everything is done
}).catch(console.log.bind(console));
someLibrary.doSomething().then(function () {
// I'm inside a then() function!
});
someLibrary.doSomething().then(function () {
// 1) return another promise
return someLibrary.doSomethingElse();
// 2) return a synchronous value
return {hooray: true};
// 3) throw a synchronous error
throw new Error('oh noes');
});
db.get('user:nolan').then(function (user) {
if (user.isLoggedOut()) {
throw new Error('user logged out!'); // throwing a synchronous error!
}
if (inMemoryCache[user.id]) {
return inMemoryCache[user.id]; // returning a synchronous value!
}
return db.get('account:' + user.id); // returning a promise!
}).then(function (userAccount) {
// I got a user account!
}).catch(function (err) {
// Boo, I got an error!
});
var bluebird = require('bluebird');
var fs = bluebird.promisifyAll(require('fs'));
fs.readFileAsync('/tmp/myfile.txt', 'utf-8').then(function (text) {
// handle success
}).catch(function (err) {
// handle error
});
var Promise = require('some-valid-library');
var promise = new Promise(function (resolve, reject) {
// roll your own promise inside here
});
promise.then();
promise.catch();
Promise.resolve();
Promise.reject();
Promise.all();
Promise.race();
Promise.resolve().then(function () {
throw new Error("aw shucks");
});
var db = new PouchDB('mydb');
db.post({}).then(function (result) { // post a new doc
return db.get(result.id); // fetch the doc
}).then(function (doc) {
console.log(doc); // log the doc
}).catch(function (err) {
console.log(err); // log any errors
});
let db = new PouchDB('mydb');
try {
let result = await db.post({});
let doc = await db.get(result.id);
console.log(doc);
} catch (err) {
console.log(err);
}