// Rookie mistake #2: WTF, how do I use forEach() with promises?
// I want to remove() all docs
db.allDocs({include_docs: true}).then(function (result) {
result.rows.forEach(function (row) {
db.remove(row.doc);
});
}).then(function () {
// I naively believe all docs have been removed() now!
});
// right way
db.allDocs({include_docs: true}).then(function (result) {
return Promise.all(result.rows.map(function (row) {
return db.remove(row.doc);
}));
}).then(function (arrayOfResults) {
// All docs have really been removed() now!
});
// Rookie mistake #3: forgetting to add .catch()
somePromise().then(function () {
return anotherPromise();
}).then(function () {
return yetAnotherPromise();
}).catch(console.log.bind(console));
// Rookie mistake #4: using "deferred"
$q.when(db.put(doc)).then(/* ... */); // <-- this is all the code you need
// Another strategy is to use the revealing constructor pattern,
// which is useful for wrapping non-promise APIs. For instance,
// to wrap a callback-based API like Node's fs.readFile(), you can simply do:
new Promise(function (resolve, reject) {
fs.readFile('myfile.txt', function (err, file) {
if (err) {
return reject(err);
}
resolve(file);
});
}).then(/* ... */)
// Rookie mistake #5: using side effects instead of returning
//BAD
somePromise().then(function () {
someOtherPromise();
}).then(function () {
// Gee, I hope someOtherPromise() has resolved!
// Spoiler alert: it hasn't.
});
// Okay, this is a good point to talk about everything you ever need to know about promises.
// Seriously, this is the one weird trick that, once you understand it, will prevent all of the errors I've been talking about. You ready?
// As I said before, the magic of promises is that they give us back our precious return and throw. But what does this actually look like in practice?
// Every promise gives you a then() method (or catch(), which is just sugar for then(null, ...)). Here we are inside of a then() function:
somePromise().then(function () {
// I'm inside a then() function!
});
// What can we do here? There are three things:
// 1. return another promise
// 2. return a synchronous value (or undefined)
// 3. throw a synchronous error
// That's it. Once you understand this trick, you understand promises. So let's go through each point one at a time.
// 1. Return another promise
getUserByName('nolan').then(function (user) {
return getUserAccountById(user.id);//return a promise
}).then(function (userAccount) {
// I got a user account!
});
// 2. Return a synchronous value (or undefined)
getUserByName('nolan').then(function (user) {
if (inMemoryCache[user.id]) {
return inMemoryCache[user.id]; // returning a synchronous value!
}
return getUserAccountById(user.id); // returning a promise!
}).then(function (userAccount) {
// I got a user account!
});
//3. Throw a synchronous error
getUserByName('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 getUserAccountById(user.id); // returning a promise!
}).then(function (userAccount) {
// I got a user account!
}).catch(function (err) {
// Boo, I got an error!
});
// Advanced mistake #1: not knowing about Promise.resolve()
new Promise(function (resolve, reject) {
resolve(someSynchronousValue);
}).then(/* ... */);
// You can express this more succinctly using Promise.resolve():
Promise.resolve(someSynchronousValue).then(/* ... */);
// This is also incredibly useful for catching any synchronous errors.
// It's so useful, that I've gotten in the habit of beginning nearly all of my promise-returning API methods like this:
function somePromiseAPI() {
return Promise.resolve().then(function () {
doSomethingThatMayThrow();
return 'foo';
}).then(/* ... */);
}
// Advanced mistake #2: catch() isn't exactly like then(null, ...)
// Advanced mistake #3: promises vs promise factories
function executeSequentially(promiseFactories) {
var result = Promise.resolve();
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);
});
return result;
}
function myPromiseFactory() {
return somethingThatCreatesAPromise();
}
// Advanced mistake #4: okay, what if I want the result of two promises?
// Wanting to be good JavaScript developers and avoid the pyramid of doom, we might just store the user object in a higher-scoped variable:
var user;
getUserByName('nolan').then(function (result) {
user = result;
return getUserAccountById(user.id);
}).then(function (userAccount) {
// okay, I have both the "user" and the "userAccount"
});
// This works, but I personally find it a bit kludgey. My recommended strategy: just let go of your preconceptions and embrace the pyramid:
getUserByName('nolan').then(function (user) {
return getUserAccountById(user.id).then(function (userAccount) {
// okay, I have both the "user" and the "userAccount"
});
});
// ...at least, temporarily. If the indentation ever becomes an issue,
// then you can do what JavaScript developers have been doing since time immemorial, and extract the function into a named function:
function onGetUserAndUserAccount(user, userAccount) {
return doSomething(user, userAccount);
}
function onGetUser(user) {
return getUserAccountById(user.id).then(function (userAccount) {
return onGetUserAndUserAccount(user, userAccount);
});
}
getUserByName('nolan')
.then(onGetUser)
.then(function () {
// at this point, doSomething() is done, and we are back to indentation 0
});
putYourRightFootIn()
.then(putYourRightFootOut)
.then(putYourRightFootIn)
.then(shakeItAllAbout);
// Advanced mistake #5: promises fall through
// What do you think this code prints out?
Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {
console.log(result);
});
// If you think it prints out bar, you're mistaken. It actually prints out foo!
// The reason this happens is because when you pass then() a non-function (such as a promise),
// it actually interprets it as then(null), which causes the previous promise's result to fall through.
// You can test this yourself:
Promise.resolve('foo').then(null).then(function (result) {
console.log(result);
});
// so most likely you meant to do:
Promise.resolve('foo').then(function () {
return Promise.resolve('bar');
}).then(function (result) {
console.log(result);
});
//BASICS
/*================================================================
= JavaScript Promises There and back again =
================================================================*/
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
promise.then(function(result) {
console.log(result); // "Stuff worked!"
}, function(err) {
console.log(err); // Error: "It broke"
});
//Promisifying XMLHttpRequest
function get(url) {
// Return a new promise.
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.send();
});
}
//Queuing asynchronous actions
getJSON('story.json').then(function(story) {
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
console.log("Got chapter 1!", chapter1);
});
var storyPromise;
function getChapter(i) {
storyPromise = storyPromise || getJSON('story.json');
return storyPromise.then(function(story) {
return getJSON(story.chapterUrls[i]);
})
}
// and using it is simple:
getChapter(0).then(function(chapter) {
console.log(chapter);
return getChapter(1);
}).then(function(chapter) {
console.log(chapter);
});
// Creating a sequence
// Start off with a promise that always resolves
var sequence = Promise.resolve();
// Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
// Add these actions to the end of the sequence
sequence = sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
});
// We can tidy up the above code using array.reduce:
// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
// Add these actions to the end of the sequence
return sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
//put it all together
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
return story.chapterUrls.reduce(function(sequence, chapterUrl) {
// Once the last chapter's promise is done…
return sequence.then(function() {
// …fetch the next chapter
return getJSON(chapterUrl);
}).then(function(chapter) {
// and add it to the page
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
// And we're all done!
addTextToPage("All done");
}).catch(function(err) {
// Catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
// Always hide the spinner
document.querySelector('.spinner').style.display = 'none';
});
//all
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// Take an array of promises and wait on them all
return Promise.all(
// Map our array of chapter urls to
// an array of chapter json promises
story.chapterUrls.map(getJSON)
);
}).then(function(chapters) {
// Now we have the chapters jsons in order! Loop through…
chapters.forEach(function(chapter) {
// …and add to the page
addHtmlToPage(chapter.html);
});
addTextToPage("All done");
}).catch(function(err) {
// catch any error that happened so far
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
});
// best of all.
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// Map our array of chapter urls to
// an array of chapter json promises.
// This makes sure they all download parallel.
return story.chapterUrls.map(getJSON)
.reduce(function(sequence, chapterPromise) {
// Use reduce to chain the promises together,
// adding content to the page for each chapter
return sequence.then(function() {
// Wait for everything in the sequence so far,
// then wait for this chapter to arrive.
return chapterPromise;
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
addTextToPage("All done");
}).catch(function(err) {
// catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
});
// Generators
function spawn(generatorFunc) {
function continuer(verb, arg) {
var result;
try {
result = generator[verb](arg);
} catch (err) {
return Promise.reject(err);
}
if (result.done) {
return result.value;
} else {
return Promise.resolve(result.value).then(onFulfilled, onRejected);
}
}
var generator = generatorFunc();
var onFulfilled = continuer.bind(continuer, "next");
var onRejected = continuer.bind(continuer, "throw");
return onFulfilled();
}
//
spawn(function *() {
try {
// 'yield' effectively does an async wait,
// returning the result of the promise
let story = yield getJSON('story.json');
addHtmlToPage(story.heading);
// Map our array of chapter urls to
// an array of chapter json promises.
// This makes sure they all download parallel.
let chapterPromises = story.chapterUrls.map(getJSON);
for (let chapterPromise of chapterPromises) {
// Wait for each chapter to be ready, then add it to the page
let chapter = yield chapterPromise;
addHtmlToPage(chapter.html);
}
addTextToPage("All done");
}
catch (err) {
// try/catch just works, rejected promises are thrown here
addTextToPage("Argh, broken: " + err.message);
}
document.querySelector('.spinner').style.display = 'none';
});
/*----- JavaScript Promises There and back again ------*/