No description or website provided.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
final-code/mongoose-movies
starter-code/mongoose-movies
README.md

README.md

click to view as a presentation


JavaScript Promises


Learning Objectives


Students Will Be Able To:

  • Describe the Use Case for Promises
  • Create a Promise
  • Run code when a Promise resolves
  • Run code when a Promise is rejected
  • Chain Promises
  • Seed a Database

Roadmap


  1. Setup
  2. The Use Case of Promises
  3. What's a Promise?
  4. Making Promises
  5. Resolving Promises
  6. Rejecting Promises
  7. Chaining Promises
  8. Example - Seeding a Database

Setup

  1. In Terminal, cd to the class repo.

  2. Get the latest updates from the remote class repo:

    $ git pull upstream master
  3. cd into the
    .../work/w05/d2/01-js-promises/starter-code/mongoose-movies folder.

  4. Open the mongoose-movies folder in vscode.

  5. Install the Node modules:

    $ npm install

The Use Case of Promises


  • Promises provide another way to deal with asynchronous code execution.

  • Pair up and take two minutes to answer the following:

    • What functions/methods have we used that execute asynchronously?
    • What "mechanism" have we used that enables code to be run after an asynchronous operation is complete?

The Use Case of Promises


  • Promises provide an alternative to callbacks as a way to work with asynchronous code execution.

  • A functions/method that wants to implement an asynchronous operation must be written to either:

    • Accept a callback
    • Return a promise
    • Or do both (Mongoose queries are an example of this)

What's a Promise?


  • A promise is a special JavaScript object.

  • A promise represents the eventual completion, or failure, of an asynchronous operation.

  • Although we usually consume promises returned by functions, we'll start by creating one so that we can see how they work...


Making Promises


  • In the seeds.js file, let's make a promise using the Promise class/constructor:

     var p = new Promise();
  • Saving and running in terminal:

     $ node seeds

    Will generate an error because a function argument must be passed in...


Making Promises


  • Let's give new Promise() an executor function as an argument that has two parameters:

     var p = new Promise(function(resolve, reject) {
     	console.log(resolve, reject);
     });
     console.log(p);
  • Observations:

    • The executor is immediately called by the Promise constructor passing functions as args for the resolve and reject parameters.
    • The promise created is an object with a <pending> notation.

Making Promises


  • A promise is always in one of three states:

    • pending: Initial state, neither fulfilled nor rejected.
    • fulfilled: The async operation completed successfully.
    • rejected: The async operation failed.
  • Once a promise has been settled, i.e., it's no longer pending, its state will not change again.


Resolving Promises


  • So, how does a promise become fulfilled?
    By calling the resolve function:

     var p = new Promise(function(resolve, reject) {
       var value = 42;
       resolve(value);
     });
  • The promise, p, has been resolved with the value 42.

  • Note that promises can only be resolved with a single value, however that value can be anything such as an object, etc.


Resolving Promises


  • How do we get the value of a resolved promise?
    By calling the promise's then method:

     var p = new Promise(function(resolve, reject) {
       var value = 42;
     	resolve(value);
     });
     
     p.then(function(result) {
       console.log(result);
     });
  • The then method will execute the callback as soon as the promise is resolved. BTW, you can call then multiple times to access the value of a promise.


Resolving Promises


  • So far our code is synchronous, let's make it asynchronous:

     var p = new Promise(function(resolve, reject) {
       setTimeout(function() {
         resolve('Timed out!');
       }, 2000);
     });
     
     p.then(function(result) {
       console.log(result);
     });

    We're using setTimeout to create an asynchronous operation

  • So, we've seen how the resolve function fulfills a promise,
    I bet you know what the reject function does...


Rejecting Promises


  • Now let's call the reject function instead of resolve:

     var p = new Promise(function(resolve, reject) {
       setTimeout(function() {
         reject('Something went wrong!');
       }, 2000);
     });
  • After 2 seconds, we'll see a UnhandledPromiseRejectionWarning: ... error.

  • Reading the error more closely reveals that we need a .catch() to handle the promise rejection...


Rejecting Promises


  • Let's chain a catch method call:

     p.then(function(result) {
       console.log(result);
     }).catch(function(err) {
       console.log(err);
     });
  • That's better!

  • The next slide shows a graphic summarizing what we've learned so far about promises...


Promises - Review

  • We've covered the fundamentals of promises. Next we'll see how we can chain multiple promises. But first...

Promises - Review Questions


  • As a way of working with asynchronous operations, promises provide an alternative to _________ functions.

  • What three states can a promise be in?

  • What method do we call on a promise to obtain its resolved value?


Chaining Promises


  • Do you remember having to nest callback functions?

  • It can get ugly:

  • The advantage of promises is that they "flatten" the async flow and thus avoid the so-called pyramid of doom.


Chaining Promises


  • We can chain as many .then methods we want:

     p
     .then(function(result) {
       console.log(result);
       return 42;
     })
     .then(function(result) {
       console.log(result);
       return 'Done!'
     })
     .then(function(result) {
       console.log(result);
     });
  • Let's see what happens if we return promises instead of primitives...


Chaining Promises


  • First we need a cool function with an asynchronous operation:

     function asyncAdd(a, b, delay) {
       return new Promise(function(resolve) {
         setTimeout(function() {
           resolve(a + b);
         }, delay);
       });
     }
  • The function returns a promise that resolves to the result of adding two numbers after a delay (ms).


Chaining Promises


  • This code demonstrates promise chaining in action:

     asyncAdd(5, 10, 2000)
     .then(function(sum) {
       console.log(sum);
       return asyncAdd(sum, 100, 1000);
     })
     .then(function(sum) {
       console.log(sum);
       return asyncAdd(sum, 1000, 2000);
     })
     .then(function(sum) {
       console.log(sum);
     });
  • Note how when the then callback returns a promise, the next then is called when that promise resolves.


Chaining Promises


  • Nice!

  • We've made our own promises, resolved them, and chained them!

  • More commonly though, we'll be consuming promises returned by libraries such as Mongoose...


Example - Seeding a Database


  • Seeding a database is the process populating a database with some initial data.

  • Use cases for seeding a database include, for example:

    • Creating an initial admin user
    • To provide data for lookup tables/collections. For example, in a inventory app for a grocery store, you might seed a departments table/collection with values like Deli, Dairy, Bakery, Meat & Seafood, etc.
  • The code to seed a database is external to the applications that use the database and is executed separately.


Example - Seeding a Database


  • At the top of seeds.js, let's connect to the database, require the Models and load the data module:

     // utility to initialize database
     require('./config/database');
     const Movie = require('./models/movie');
     const Performer = require('./models/performer');
     const data = require('./data');
  • To avoid duplicates when seeding a database, we first need to delete all data from the collections we'll be inserting data into...


Example - Seeding a Database


  • The following code deletes all movie documents and correctly ends the program:

     // clear out all movies and performers to prevent dups
     Movie.deleteMany({})
     .then(function(results) {
       console.log(results);
       process.exit();
     });

    No callback provided to the deleteMany method!

  • Run $ node seeds and you'll see the result object logged out.


Example - Seeding a Database


  • Most Mongoose Model methods return a "thenable" that works like a promise. That means we can chain the code to delete performers:

     Movie.deleteMany({})
     .then(function(results) {
       console.log('Deleted movies: ', results);
       return Performer.deleteMany({});
     })
     .then(function(results) {
       console.log('Deleted performers:', results);
     })
     .then(function() {
       process.exit();
     });
  • The above works, but there's a better way...


Example - Seeding a Database


  • As written, the code first deletes movies, then afterwards, deletes the performers in series.

  • Because they are not dependent upon each other, it would be more efficient to perform both operations simultaneously - the Promise.all method is our ticket...


Example - Seeding a Database


  • Promise.all accepts an array of promises and returns a single promise in their place:

     // clear out all movies and performers to prevent dups
     const p1 = Movie.deleteMany({});
     const p2 = Performer.deleteMany({});
     Promise.all([p1, p2])
     .then(function(results) {
       console.log(results);
     })
     .then(function() {
       process.exit();
     });
  • The above code now removes documents from the movies & performers collections in parallel!


Example - Seeding a Database


  • Finally, let's create some data, beginning with performers:

     ...
     Promise.all([p1, p2])
     .then(function(results) {
       console.log(results);
       return Performer.create(data.performers);
     })
     .then(function(performers) {
       console.log(performers);
     })
     .then(function() {
       process.exit();
     });
  • Try it out. Now it's your turn...


Example - Seeding a Database


  • data.movies contains an array of movie data.

  • YOU DO: Add the code that will create the movie documents.

  • Click the chili pepper when you've finished.


Example - Seeding a Database


  • Spinning up the server and browsing to localhost:3000 verifies the data is looking sweet.

  • Although using the app to assign performers to a movie's cast property is fun, let's take a look at how you might do it in seeds.js.


Example - Seeding a Database


  • Important: You can never refer to an actual _id within the code in a seeds file.
    For example, you can't write code like:

     Movie.findById('5c609ac7641fdd63f6b8b71d')
     .then(...)
  • Why?


Example - Seeding a Database


  • Instead, we have to query for a document based on its other properties.

  • For example:

     // find all PG-13 movies
     Movie.find({mpaaRating: 'PG-13'})
     .then(function(movies) {
       console.log(movies);
     });

Example - Seeding a Database


  • Let's say we want to assign the performer, Mark Hamill, to the movie, Star Wars - A New Hope.

  • The code on the following slide uses another Promise.all because we can't resolve more than one value...


Example - Seeding a Database

  • We'll review as we type this:

     .then(function(movies) {
       return Promise.all([
         Performer.findOne({name: 'Mark Hamill'}),
         Movie.findOne({title: 'Star Wars - A New Hope'})
       ]);
     })
     .then(function(results) {  // one day we'll destructure this!
       const mark = results[0];
       const starWars = results[1];
       starWars.cast.push(mark);
       return starWars.save();
     })
     .then(function() {
       process.exit();
     });
  • Check it out in the app - congrats! On to the lab...


References