Promises Flashcards
What are the states of a promise?
A promise can be:
fulfilled - The action relating to the promise succeeded
rejected - The action relating to the promise failed
pending - Hasn’t fulfilled or rejected yet
settled - Has fulfilled or rejected
How would you build a promise-like structure without promises?
With event listeners…
var img1 = document.querySelector(‘.img-1’);
function loaded() { // woo yey image loaded }
if (img1.complete) { loaded(); } else { img1.addEventListener('load', loaded); }
img1.addEventListener('error', function() { // argh everything's broken });
How does a then work?
Thens can work in one of two ways.
If they’re working with a promise, you can pass two functions, the first for resolve() and the second for reject().
Alternatively, if the thenable object returns a value, then they can accept that as an input.
Either way, you can chain them asynchronously and return promises from them.
var promise = new Promise(function(resolve, reject) { resolve(1); });
promise.then(function(val) {
console.log(val); // 1
return val + 2;
}).then(function(val) {
console.log(val); // 3
})
How do you make a get request promise?
Put the XMLHttpRequest in a promise, then use the resolve() and reject() functions to act on the outcome.
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(); }); }
Explain how to use a catch statement with promises.
Instead of providing the second argument to your then statement, chain a catch statement into your chain to handle the errors.
All thens will be skipped over until they hit the catch, and then pick up again from there if you want to chain more on.
get('story.json').then(function(response) { console.log("Success!", response); }).catch(function(error) { console.log("Failed!", error); })
How does providing a reject() function to a then differ from a catch statement?
With a resolve() and a reject() you get one or the other, a catch() allows you to use the same error handling for the original promise or your resolve().
asyncThing1().then(function() { return asyncThing2(); }).then(function() { return asyncThing3(); }).catch(function(err) { return asyncRecovery1_2_3(); }).then(function() { return asyncThing4(); }, function(err) { return asyncRecovery1_2_3_recovery(); }).catch(function(err) { console.log("Don't worry about it"); // for 4 or 1_2_3_recovery }).then(function() { console.log("All done!"); })
What happens if an error is thrown in the promise constructor callback?
Rejections happen when a promise is explicitly rejected, but also implicitly if an error is thrown in the constructor callback:
var jsonPromise = new Promise(function(resolve, reject) { // JSON.parse throws an error if you feed it some // invalid JSON, so this implicitly rejects: resolve(JSON.parse("This ain't JSON")); });
jsonPromise.then(function(data) { // This never happens: console.log("It worked!", data); }).catch(function(err) { // Instead, this happens: console.log("It failed!", err); })
This means it’s useful to do all your promise-related work inside the promise constructor callback, so errors are automatically caught and become rejections.
The same goes for errors thrown in then() callbacks.
get('/').then(JSON.parse).then(function() { // This never happens, '/' is an HTML page, not JSON // so JSON.parse throws console.log("It worked!", data); }).catch(function(err) { // Instead, this happens: console.log("It failed!", err); })
How could this synchronous block be rewritten with promises?
try { var story = getJSONSync('story.json'); var chapter1 = getJSONSync(story.chapterUrls[0]); addHtmlToPage(chapter1.html); } catch (e) { addTextToPage("Failed to show chapter"); } document.querySelector('.spinner').style.display = 'none'
Assuming we had a getJSON function that returned a promise…
getJSON('story.json').then(function(story) { return getJSON(story.chapterUrls[0]); }).then(function(chapter1) { addHtmlToPage(chapter1.html); }).catch(function() { addTextToPage("Failed to show chapter"); }).then(function() { document.querySelector('.spinner').style.display = 'none'; })
What if you don’t want to recover from an error, but still want to use the catch statement for logging?
You may want to catch() simply for logging purposes, without recovering from the error. To do this, just rethrow the error. We could do this in our getJSON() method:
function getJSON(url) {
return get(url).then(JSON.parse).catch(function(err) {
console.log(“getJSON failed for”, url, err);
throw err;
});
}
How could we rewrite this asynchronously?
try { var story = getJSONSync('story.json'); addHtmlToPage(story.heading);
story.chapterUrls.forEach(function(chapterUrl) { var chapter = getJSONSync(chapterUrl); addHtmlToPage(chapter.html); });
addTextToPage("All done"); } catch (err) { addTextToPage("Argh, broken: " + err.message); }
document.querySelector(‘.spinner’).style.display = ‘none’
We could chain off of resolved promise and get all our requests going asynchronously…
getJSON('story.json').then(function(story) { // 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); }); }); });
but we could also tidy up our then using 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())
Putting 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'; })
This is asynchronous, but still calls every load sequentially. How could we rewrite it to get them to load synchronously?
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'; })
Promise.all takes an array of promises and creates a promise that fulfills when all of them successfully complete. You get an array of results (whatever the promises fulfilled to) in the same order as the promises you passed in.
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'; });
How can we improve perceived performance on this code? When chapter one arrives we should add it to the page. This lets the user start reading before the rest of the chapters have arrived. When chapter three arrives, we wouldn’t add it to the page because the user may not realize chapter two is missing. When chapter two arrives, we can add chapters two and three, etc etc.
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'; });
To do this, we fetch JSON for all our chapters at the same time, then create a sequence to add them to the document:
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 in 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'; })