The Web Developer Bootcamp 2024

Colt Steele

Back to JavaScript Index Page

JavaScript Promises

JavaScript Promises are a means of sending and receiving data to an "external" source, such as a web API. The "promise" is that Js will send a request for the data, and that a response for either a successful retrieval of the data, or a failure in the retrieval, for what ever reason, is received. For example. Say you have a web app that has the current weather displayed on the page. You would send a request to the weather source API to retrieve that data. The coding in the "Promise" would ensure that you would get a response, whether you received the data or not.

Here is a technical definition:

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.

A Promise is in one of these states:

  • pending: initial state, neither fulfilled nor rejected.
  • fulfilled: meaning that the operation was completed successfully.
  • rejected: meaning that the operation failed.

The eventual state of a pending promise can either be fulfilled with a value or rejected with a reason (error). When either of these options occur, the associated handlers queued up by a promise's then method are called. If the promise has already been fulfilled or rejected when a corresponding handler is attached, the handler will be called, so there is no race condition between an asynchronous operation completing and its handlers being attached.

*A promise is said to be settled if it is either fulfilled or rejected, but not pending. More info can be found here:


Writing Promises

Normally when using Ajax, or other similar technologies, we don't write too many Promises ourselves. We just normally write the code that will interact within the promise. But if we did write a Promise, this is commonly how it would be written:

  • const fakeRequest = (url) => {
    • return new Promise((resolve, reject) => {
      • const rand = Math.random();
      • setTimeout(() => {
        • if (rand < 0.7) {
          • resolve('Fake Data was returned.');
        • }
        • reject('Error Retrieving the Fake Data!!');
      • }, 1000)
    • })
  • }

So we've created a function with a Fake Request, which will send a request to a dummy link (url). Within this function, we have created a new Promise, which will return the results of this Promise.

Within the Promise itself, we have set up a Random Number Generator, which will return a random number between 0 and 0.9. We then set up an if loop. if the random number is less than 0.7, the Promise will return a resolve(), otherwise it will return a reject().

Now within the Promise itself:

  • - notice that the "P" in "Promise" is UpperCase
  • - the Promise expects us to pass in a function
  • - the function always expects two parameters
    • - we named ours resolve, and reject but you can name the parameters anything you want
  • - the two parameters represent the resolution or the rejection of the promise
  • - they can be executed anywhere within the Promise, so you would write functions that would deal with the resolution or rejection of the Promise

And now to call the fakeRequest function:

  • fakeRequest('dogs/1');   // pointing to a fake url : dogs, pg.1
    • .then((data) => {
      •    // if the Promise returned resolve()   
      • console.log('Request ');
      • console.log('Complete : ', data);
    • })
    • .catch((err) => {
      •    // if the Promise returned reject()   
      • console.log('Oh No!!', err);
    • })

So if the fakeRequest function returns a resolve(), then the console will log "Request Complete : Fake Data was returned." But if the fakeRequest function returns a reject(), the console then logs "Oh No!! Error Retrieving the Fake Data!!".

In Conclusion

We need to understand that this is just a very basic example that is simply showing the "concept" of Promises. The results of a resolve(), or a reject() are dependant on the random number generated.

But in reality, these results would be determined by the outcome of the data request, and that outcome would be set by the resolution, or rejection of the Promise. You would then write the appropriate code to deal with both scenarios, the resolution, and the rejection, so that your program could deal with both outcomes.

Async & Await

Async & Await are a "newer" means of working with Promises.

Async & Await in JavaScript are used to simplify handling asynchronous operations using promises. By enabling asynchronous code to appear synchronous, they enhance code readability and make it easier to manage complex asynchronous flows.

  • a). The keyword async before a function makes the function return a Promise:
    • - async functions always return a Promise.
    • - if the function returns a value, the Promise will be resolved with that value
    • - if the function throws an exception, the Promise will be rejected
  • b). The await keyword can only be used inside an async function. It makes the function pause the execution, and wait for a resolved promise before it continues.

More info can be found here:


Async Example

Here is a simple example of using async:

  • const login = async (username, password) => {
    • if (!username || !password) throw 'Credentials Incomplete!'
    • if (password === 'pswd') return 'Welcome'
    • throw 'Invalid Password!'
  • }
  • *the throw statement allows you to create a custom error. The technical term for this is: The throw statement throws an exception

So we've created a async function to parse a login attempt for valid information.

  • - if the username or password is blank ('empty string'), then it throws an error with the message "Credentials Incomplete!"
  • - if the password is "invalid" then it throws an error with the message "Invalid Password!"
  • - if the login data is correct, there are no exceptions and it returns the message "Welcome, you have successfully logged in"

And next we would need to call this function:

  • login('MyName', 'pswd')
    • .then(msg => {
      • console.log(`${msg}, you have successfully Logged In`);
    • })
    • .catch(err => {
      • console.log(`ERROR!, ${err}`);
    • })
  •  
  • - the .then() method provides two callbacks, one function to run when a promise is fulfilled, and one function to run when a promise is rejected.
  • - the .catch() method provides a callback to run when a promise is rejected.

So if we leave either the username or password fields blank, then the console logs the message "ERROR!, Credentials Incomplete!"

If we enter an invalid password, the console logs "ERROR!, Invalid Password"

And finally, if we enter a correct username and password, then the console logs "Welcome, you have successfully Logged In."


The Await Expression

We now know that placing “async” before a function, means one simple thing. This is an async function, and an async function always returns a promise. In other words, the keyword async before a function, always makes the function return a promise

The await expression pauses the execution of its surrounding async functions, until that promise is settled (that is, fulfilled or rejected). When execution resumes, the value of the await expression becomes that of the fulfilled promise.

An await is only valid inside async functions and modules. It wil pause the execution of the async function, and waits for a promise to resolve, before it continues.


Let's start with this example. We're going to add a <div> with a width of 400px and a height of 35px (as defined in a css stylesheet). We're going to give the <div> and id="colorChange", so we can reference it within our JavaScript:

  • HTML
  • <div id=colorChange"></div>
  • JavaScript
  • const colorChange = document.GetElementById('colorChange');

Next, we're going to write a function that will create a new Promise that will change the background color of our div, every 1.5 seconds (1500ms):

  • JavaScript
  • const delayedColorChange = (color) => {
    • return new Promise((resolve, reject) => {
      • setTimeout(() => {
        • colorChange.style.backgroundColor = color;
        • resolve();
      • }, 1500)
    • })
  • }
So this function creates a new Promise that will change the background color of our div, to the color passed in to the function. It will then return a resolution of the Promise, in this case, a resolve().

Next we need to create our async function which will pass in all of the colors for the background color changes. And inside this function will be several calls of the function delayedColorChange(), which if we look at the function, we see that after 1.5 seconds, it changes our <div>'s background-color. But wait Houston... we have a problem.

If we call our async function, and pass in our first two delayedColorChange() functions, like so:

  • JavaScript
  • async function rainbow() {
    • delayedColorChange('red')
    • delayedColorChange('orange')
  • }

What do you think happens? Do we see a Red background, and then 1.5 seconds later a orange background?

Well, actually we don't.

Because we've told our colorChange function to wait 1500ms (1.5 seconds), but when we call this function a second time for the second color, it actually changes the color before the 1.5 seconds the timeout calls for. So it has changed the background color twice, before the timeout has finished running. So instead of getting Red to Orange, we just see the Orange.

And this is where we find the use for our await expression.

Remember that we can only call await inside of an async function. And an async function always returns a promise. So when we preface our colorChange function with await, it forces the Promise it is working on, to wait until it has finished, before moving on to the next Promise.

So our async function would be:

  • JavaScript
  • async function rainbow() {
    • await delayedColorChange('red')
    • await delayedColorChange('orange')
    • await delayedColorChange('yellow')
    • await delayedColorChange('green')
    • await delayedColorChange('blue')
    • await delayedColorChange('indigo')
    • await delayedColorChange('violet')
    • return "ALL DONE!"
  • }

And finally, all that's left to do is to actually call the rainbow() async function:

  • rainbow();

And here is our final result. Our <div> with changing background color.

So as we can see, the await expression has paused the execution of its surrounding async functions, until that promise is settled (that is, fulfilled or rejected). And in our example, the end result is that every color change requires a 1.5 second delay before moving on to the next one.


Final Code
  • HTML
    • <div id=colorChange"></div>
  •  
  • CSS
    • #colorChange {
      • width: 400px;
      • height: 35px;
      • border: 2px solid black;
      • border-radius: 8px;
      • transition: 1.25s background-color;
    • }
  •  
  • JavaScript
    • // create a variable for a div with id="colorChange"
    • const colorChange = document.getElementById('colorChange');
    •  
    • // random background color change with a time delay
    • const delayedColorChange = (color) => {
      • return new Promise((resolve, reject) => {
        • setTimeout(() => {
          • colorChange.style.backgroundColor = color;
          • resolve();
        • }, 1500)
      • })
    • }
    •  
    • // process the async with awaits
    • async function rainbow() {
      • await delayedColorChange('red')
      • await delayedColorChange('orange')
      • await delayedColorChange('yellow')
      • await delayedColorChange('green')
      • await delayedColorChange('blue')
      • await delayedColorChange('indigo')
      • await delayedColorChange('violet')
      • return "ALL DONE!"
    • }
    •  
    • // run the rainbow async function
    • rainbow();
  •  

Back to Top