Promises Flashcards

1
Q

What are the states of a promise?

A

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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

How would you build a promise-like structure without promises?

A

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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

How does a then work?

A

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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

How do you make a get request promise?

A

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();
  });
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Explain how to use a catch statement with promises.

A

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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

How does providing a reject() function to a then differ from a catch statement?

A

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!");
})
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What happens if an error is thrown in the promise constructor callback?

A

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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

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'
A

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';
})
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

What if you don’t want to recover from an error, but still want to use the catch statement for logging?

A

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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

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’

A

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';
})
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

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';
})
A

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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

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';
});
A

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';
})
How well did you know this?
1
Not at all
2
3
4
5
Perfectly