New way to work with promises (async-await)

In the last lesson, we saw that with just a little knowledge about HTTP methods and REST APIs, we could use what we already know about promises to get and change external data with the fetch API.

So let's take another look at how we used fetch to get a single post from the json placeholder api:

  fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => res.json()).then(data => console.log(data));

To quickly run through that process once again, of executing this promise:

First, we use the fetch function, to make a request for a single post's data from this /posts endpoint. Second, we get back our response, and use the json method, to convert the response body to json data And finally, we get our actual object data, which we can log in the console.

But what if there was a different way of writing this promise that didn't force us to write multiple callback functions everytime? What if we could make it look like synchronous code, where we can just resolve our promise and immediately put it in a variable, just like we would for any other value in JavaScript, like this:

  const res = fetch('https://jsonplaceholder.typicode.com/posts/1')
//  ​ .then(res => res.json())
//  ​ .then(data => console.log(data));
}

Let's introduce a different syntax for working with promises. First let's create a function that declares what we want the promise to do. In this case, for our current promise that gets a blog post's data, a good name would be getBlogPost:

function getBlogPost() {}

And since we want this function to resolve our promise, what if there was a way to tell the function that's what we wanted. We can do just that by adding the keyword async in front of the function keyword:

async function getBlogPost() {}

Note that if we were using an arrow function--let me just rewrite this as an arrow function--the async would be before the function parameters, since that is the beginning of the arrow function itself:

// async function getBlogPost() {}
const getBlogPost = async () => {}; // remove this later

When we use the async keyword before any function, it always returns a promise. To prove that it does, let's try calling getBlogPost and chain on .then() at the end. And maybe we'll add a console.log in body, saying 'works as a promise'

async function getBlogPost() {}

getBlogPost().then(() => console.log("works as a promise")); // 'works as a promise'

And if we run this, we get our log. So any function prepended with the async keyword automatically returns a promise.

What about resolving or rejecting the promise though? In this case, using the async keyword, if we don't get an error, the promise will be fulfilled, or resolved successfully. So for example, if we return a value from this function, unless it's an error, it will be passed to the then callback.

So let's return the text: 'works here too!', and then in our then callback, console.log it as 'value'

async function getBlogPost() {
  return "works here too!";
}

getBlogPost().then((value) => console.log(value)); // 'works here too!'

await keyword

So how does this async keyword help us with promises? You might be saying--I don't see the benefit at this point.

Let's take a look at what it can do combination with another special keyword.

But first let's switch back to the old way of writing a promise for a minute. So let's remove async keyword for now. And you remember how to create traditional promises using the Promise constructor. So if you would, take some time, remember how to write a Promise from scratch and write a new promise instance within the getBlogPost function...

So to write our promise, we'll say new Promise. It'll get a callback function, with the parameters, resolve and reject.

function getBlogPost() {
  new Promise((resolve, reject) => {});
}

And within this promise, let's not make a network request yet, but instead mimic our an api call with a setTimeout. For now, instead of actual blog post data, we'll return the text 'blog post' after a second and at the end of the promise, once it's resolved, console.log 'done' to see a visual indication of that. Remember that with a normal promise, we need to create a reference to the promise, in a variable, and use .then() on it to resolve it. And then we'll use .finally() to tell log 'done' and tell us when everything finishes.

So not counting the callback passed to setTimeout, there are three functions here created in an attempt to resolve this very simple "request" for a blog post. That's quite a lot of code just to resolve one value.

function getBlogPost() {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("blog post"), 1000);
  });

  promise
    .then((value) => console.log(value))
    .finally(() => console.log("done"));
}

What if, instead of using then and finally, we could just tell this function to pause until the promise is resolved, put the resolved value in a variable, and only then continue the function to run our 'done' console.log?

function getBlogPost() {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("blog post"), 1000);
  });

  //  promise.then(value => console.log(value)).finally(() => console.log("done"));
  const result = promise;
  console.log("done");
}

We can do just this if along with declaring our function as async with the async keyword, we use the await keyword. await does all of the things we mentioned--it pauses our code on the line its used, resolves our promise, allows us to immediately use the resolved value as we like, and then resumes the function. To use it we just put await before any promise that we want to resolve. We'll put the resolved value in a variable, result, which we'll console.log to see. And now, believe it or not, we can call getBlogPost just like we would any synchronous function without any then or catch functions:

async function getBlogPost() {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("blog post"), 1000);
  });

  const result = await promise;
  console.log(result);
  console.log("done");
}

getBlogPost();

// blog post
// done

And amazingly the code runs each line in the proper order, just like it was synchronous code. This is the power of async await. No need for callback functions whatsoever and totally readable.

Caveats

Let's be clear about a couple of things first when it comes to async await.

First, this doesn’t replace promises. You’re merely wrapping promises in a better syntax.

And second, you cannot resolve promises with await unless it is within a function that is prepending with the async keyword. await can't work without async, otherwise you'll get an error, however, as you saw earlier, you can create async function that don't use await if you resolve them like normal promises with then

From promises to async-await

Finally, let take our original fetch example from this lesson where the promise is resolved with then and use the async await syntax instead:

  fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => res.json()).then(data => console.log(data));
}

Pause the video, take a minute and try to do this on your own if you can based off of what we've covered.

First, we need to create a function. We have to have a function to use await in. It may be possible in the future to use await without functions, called top-level await, but for now async and await are used together in functions:

So we create our function and add the async keyword to the front of it:

async function getPost() {}

Now the function returns a promise. We can take our fetch request and since calling it gives us a promise, we resolve promises with the await keyword in front of it:

async function getPost() {
  await fetch('https://jsonplaceholder.typicode.com/posts/1')
}

We can immediately do what we like with the result, but since we called the resolved object that came back the response, we'll create a response variable. Named either response or res:

async function getPost() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts/1')
}

To get json data, we use the json method off of it, which returns a promise too, so that will be awaited and put in a data variable :

async function getPost() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
  const data = await res.json()
}

And finally, we can with data what we like, so let's log it. All that's left to be done is call getPost like a normal function:

async function getPost() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
  const data = await res.json()
  console.log(data)
}

getPost() // {userId: 1, id: 1, title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body: "quia et suscipit↵suscipit recusandae consequuntur …strum rerum est autem sunt rem eveniet architecto"}

So there we go, how to make async network requests look like synchronous code. Not a callback function in sight. async await has really transformed our code. It's as clean as it gets and as we saw plays very nice with normal, synchronous JavaScript code due to the its ability to pause functions to wait for an awaited promise to resolve, so code runs in the exact order we wrote.

We're going to dive deeper into async await, next covering something we missed in this video, which is how to catch errors without a catch statement