Email Subscription Form

Saturday, June 6, 2020

Adventures in Node: Promises

Have you ever written an automated UI test that uses Javascript, and when you went to assert on a response, you got Promise {pending} instead of what you were expecting?  This really frustrated me when I first encountered it!  A developer I was working with explained that this is because Javascript processes commands asynchronously through the use of promises.  I sort of understood what he meant, so I tried to work with it as best I could, but I didn't really get it.

As I mentioned in this post, I've been taking a really awesome course on Node.js.  It's much more extensive than any programming language course I've ever taken, even the ones I took in college.  So I'm starting to understand Node concepts more clearly, and one of those concepts is promises!  In this post I'll explain why Javascript and Node need promises and show an example of how they work.


Javascript needs promises because it is a single-threaded language, meaning it can only do one thing at a time.  If we had a program where we needed to do three things, such as make an http request, alphabetize a list, and update a record in a database, we wouldn't want to have to wait around for each of those tasks to finish before we went on to the next one, because our program would be very slow!  So Javascript is designed to be asynchronous- it can start a task, and then while it's waiting for that task to complete, it can start the next task.

Our program with three things might actually run like this:
start the http request
start alphabetizing the list
start updating the record in the database
finish alphabetizing the list
finish updating the record in the database
finish the http request

The way that Javascript and Node manage this is through the use of promises.  Let's take a look at a promise:

const sumChecker = new Promise((resolve, reject) => {
     if (a+b==c) {
          resolve('You are correct!)
     }
     else {
          reject('Sorry, your math is wrong.')
     }
}

This function called sumChecker is a promise.  It's going to have two possible results: resolve and reject.  If the sum is correct, it resolves the promise, and if it's incorrect, it rejects it.  All promises behave in this way; there will be an option to resolve the promise and an option to reject the promise.

When the promise is called, either resolve or reject will be returned; you can't ever return both.  Let's look at an example of calling the promise:

sumChecker.then((result) => {
     console.log('Success!', result)
}).catch((error) => {
     console.log('Error!', error)
})

The result that is returned from calling the promise will either be resolve or reject.  If the result is resolve, then the program knows to continue and will return the resolve message.  If the result is reject, then the program knows to throw an error and will return the reject message.

You can try this for yourself if you have Node installed!  Simply copy the promise and the call and paste them into your favorite code editor.  Then at the beginning of the file, add these lines:

var a = 1
var b = 2
var c = 3

Save the file with the name myfile.js, navigate in the command line to the file's location, and run the file with the command node myfile.js.  You should see this response: Success! You are correct!

If you make a change to the c variable and set it to 4, save and run the command again, you'll see this response: Error! Sorry, your math is wrong.

Let's put a log statement in between the promise and the call to the promise that looks like this: console.log(sumChecker), so we can see the state of sumChecker before we've called it. If we change the value of c back to 3 so we'll get a positive result, save the file, and run the program with node myfile.js now, we'll get the result Promise { 'You are correct!'} in addition to the response we got earlier.  That seems easy!  But the reason why we can get the promise resolved so quickly is because the sumChecker promise executes really fast.  Let's see what happens if we make the sumChecker work more slowly, like a real promise would.

Update the sumChecker promise to look like this:

const sumChecker = new Promise((resolve, reject) => {
     if (a+b==c) {
          setTimeout(() => {
               resolve('You are correct!)
          }, 2000)
     }
     else {
          reject('Sorry, your math is wrong.')
     }
}

All we're doing here is adding a two-second timeout to the resolved promise.  Save the file, and run the program again with node myfile.js.  This time you'll first get the result Promise { <pending> }, and after two seconds, you'll get the result Success! You are correct!  

Now it should be clear why you get Promise { <pending> } when you are making Javascript or Node calls.  It's because the promise hasn't completed yet.  This is why we use the .then() command.  We wait for the response to the call to come back, then we do something with the response.  If we're writing a test, at that point we can assert on our result.

I hope you'll take the time to try running this file with Node, because there's nothing quite like doing hands-on work to generate understanding.  You can try changing the variables or any of the response messages to get a feel for how it's working.  Here's the final version of the file if you'd like to copy and paste it:

var a = 1
var b = 2
var c = 3

const sumChecker = new Promise((resolve, reject) => {
    if (a+b==c) {
        setTimeout(() => {
            resolve('You are correct!')
        }, 2000)
    }
    else {
        reject('Sorry, your math is wrong.')
    }
})

console.log(sumChecker)

sumChecker.then((result) => {
    console.log('Success!', result)
}).catch((error) => {
    console.log('Error!', error)
})

Enjoy your new-found understanding of promises!

No comments:

Post a Comment

New Blog Location!

I've moved!  I've really enjoyed using Blogger for my blog, but it didn't integrate with my website in the way I wanted.  So I&#...