Promise and async await
Tags: javascript, tutorial, codenewbie, webdev
Published: 2022-07-31 18:22:00

Probably most of us learn `Promise` for the first time is when we start learning how to call an API with `fetch` to get recourses on the web. They are two complex topics. It is difficult to learn them at once. In this article, we will learn how to use `Promise` and its syntax sugar `async await`. So later when you learn `fetch`, you don't need to worry about `Promise`.

By the way, if you don't know what API means or you have been confused by the term, I like to think it as anything that people make it easy for us to use. In web, people usually refers API to an URL that you can simply call it, and it will give you data or do some stuff on the server side. API can mean different things in different context. `Promise` is a browser API, which means that it is not a built in feature in JavaScript. If we run JavaScript in Browsers, we can easily use those browser API, because people have made them easy for us. 

If you'd like to follow along, you can open the console of devtools and code right there. It would be better to open the console on a new browser tab, because sometimes there are errors that you need to refresh to get rid of, but you don't want to refresh the page you currently reading.

---

## Understand how to use promise
I think there are two keys to understand how to use `Promise`,  callback and the order of code execution. 

### Callback
Callback is essentially a [function](https://dev.to/gohomewho/function-5fgn), it is passed to another function as an argument. How callback is called and used is  decided inside the function that we pass the callback to. Let's look at an example.

```js
// make a function that accept a callback
function myFunc(callback) {
  const state = {
    status: 'pending',
    value: undefined
  } 

  // callback is just a variable name that you can name it whatever you like
  // but since we call it like a function, it has to be a function
  // here, we just simply call it
  callback()
  
  return state 
}
```


Calling `myFunc` without passing a function as an argument. That `callback` parameter inside `myFunc` will be undefined, so this will error. 
```js
myFunc() 
// Uncaught TypeError: callback is not a function
```

Passing a function as a callback to `myFunc`. We can define inline function as an argument directly when we are calling a function. 
```js
myFunc(() => {
  console.log('hello')
})
// hello
// {status: 'pending', value: undefined}
```
'hello' is logged by the callback and the object is returned by `myFunc`.


Generally when you see a function that accept a callback, that callback will be called with some [variables](https://dev.to/gohomewho/variable-2n4g) that lives inside the function. 
```js
function myFunc(callback) {
  const state = {
    status: 'pending',
    value: undefined
  } 

  // add a function
  function resolve() {
    state.status = 'fulfilled'
  }

  // add another function
  function reject() {
    state.status = 'rejected'
  }
  
  // pass new added function as arguments
  callback(resolve, reject)
  
  return state
}
```

Now our callback is called with two arguments, so we can define the callback with two parameters.
```js
const result = myFunc((res, rej) => {})
result // {status: 'pending', value: undefined}
```
I name the parameters of the callback `res` and `rej` on purpose. We can name parameters whatever we want. But if we don't call the callback in `myFunc` with those two arguments `resolve` and `reject`, then `res` and `rej` will be undefined. Just like what we saw earlier why we call `myFunc()` without passing a callback will show error. `res` and `rej` do not magically appear. It can be confused because normally we would define a function before we use it. On the other hand, callback is like it is used before we define it. Because it is already used somehow and somewhere inside a function, we'll need to follow the rules.


We can call the `resolve` function passed from `myFunc` in our callback.
```js
const result = myFunc((res, rej) => {
  res() // res is the resolve function passed from myFunc
})
// status becomes 'fulfilled'
result // {status: 'fulfilled', value: undefined}
```

Modify `resolve` and `reject` function so they can take value and change the state.
```js
function myFunc(callback) {
  const state = {
    status: 'pending',
    value: undefined
  } 

  // accept a parameter
  function resolve(v) {
    state.status = 'fulfilled'
    // modify state.value with that parameter
    state.value = v
  }

  // add a parameter
  function reject(v) {
    state.status = 'rejected'
    // modify state.value with that parameter
    state.value = v
  }
  
  callback(resolve, reject)
  
  return state
}
```

Calling `resolve` with a value in the callback.
```js
// we only want to call resolve
// so we can omit defining a second parameter on the callback
const result = myFunc((res) => {
  res('nice')
})
result // {status: 'fulfilled', value: 'nice'}
```

Calling `reject` with a value in the callback.
```js
const result = myFunc((res, rej) => {
  rej('bad')
})
result // {status: 'rejected', value: 'bad'}
```

We can use a condition to determine whether we call `resolve` or `reject`.
```js
const result = myFunc((res, rej) => {
  // a lots of logic
  // ...
  const someCondition = true // imagine it could be true or false

  if(someCondition === true) {
    res('nice')
  } else {
    rej('bad')
  }
})
```

Generally, a function that accepts callbacks is more generic, and the callbacks are for us to control the state that created from the function. In our example, `myFunc` accept a callback and call that callback with two functions `resolve` and `reject`. In that callback, we can do some computation and use `resolve` and `reject` as we want to modify the state created from `myFunc`.

We could also call `res` and `rej` "callbacks". Personally, I often get confused by the word, so I just call them "functions" and focus on the code rather than a term.

Now we know how things get passed around with callback, we can move on to the next part.


### The order of code execution
JavaScript runs synchronous code immediately. Synchronous code can schedule something to run asynchronously. When an asynchronous task is done and ready to do something else, it will be run after the current synchronous code is finished. 


#### What is synchronous code
Synchronous code are run one by one. `console.log()` is synchronous code, we run it and it finishes instantly.

```js
console.log(1) // run after previous code
console.log(2) // run after console.log(1)
console.log(3) // run after console.log(2)
// 1
// 2
// 3
```

#### What is asynchronous code

`setTimeout()` is a browser API that can schedule something to run asychronously. In this example, `setTimeout()` is called immediately after `console.log(1)` and right before the `console.log(3)`. The callback we pass to `setTimeout()`, however, is run asynchronously. That's why we'll see `3` being logged before `2`.
```js
console.log(1) // run after previous code
// setTimeout is run immediately after console.log(1)
setTimeout(() => {
  console.log(2) // the callback is scheduled to run asynchronously after 1000ms   
}, 1000)
console.log(3) // run after setTimeout
// 1
// 3 
// 2
```
The second argument of `setTimout()` schedules how much time to run the callback later. 1000ms doesn't guarantee to be exactly 1000ms. It has to wait current synchronous code to be finished. For example, if the timer is count to 999ms, and there is an user interaction that triggers a for loop that takes 500ms to run, the callback of the `setTimout()` will need to wait that for loop, asynchronous code won't interrupt the order of current synchronous code. 


Asynchronous code are always run after current synchronous code. Although we set the second argument of `setTimeout()` to `0ms`, the callback of `setTimeout()` is still scheduled as an asynchronous code, it has to wait current synchronous code to be finished.   
```js
console.log(1)
setTimeout(() => {
  console.log(2) 
}, 0) // set delay to 0ms
console.log(3) 
// 1
// 3 
// 2
```

We can imagine that JavaScript has a list of current synchronous code waiting to be executed. When asynchronous code is ready, it will be added at the end of that list and becomes part of the synchronous code. 

If you are interested in learning more about how JavaScript run our code, I would recommend watching [this series](https://www.youtube.com/watch?v=pN6jk0uUrD8&list=PLlasXeu85E9cQ32gLCvAvr9vNaUccPVNP&index=2&ab_channel=AkshaySaini) on YouTube created by Akshay Saini

---

## Promise
Promise is a way to schedule asynchronous code. Unlike `setTimeout` that only schedules asynchronous code to run later. We can run synchronous code when we create a `Promise` and schedule other things to run asynchronously. Let's first learn how promise work on its own, and then we'll learn how it works with other code.

### How promise works on its own
This is how we create a `Promise`. `new Promise()` with a callback `() => {}`. [new](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) is a JavaScript keyword used to create a new object. We can create an array like this `new Array()`, but usually we simply write the literal form `[]`. `Promise` doesn't have a literal form, so we need to create one like `new Promise(callback)`.
```js
new Promise(() => {})
// Promise {<pending>}
```
Notice that there is an callback that does nothing. If we don't provide a callback to `new Promise()`, it will error. If you don't know why, you probably should go back to previous section where we break down callback.


Inside `Promise`, it calls our callback with two arguments, `resolve` and `reject`. They are two functions provided from the promise to our callback. They are used to control the state of `Promise`.
```js
new Promise((resolve, reject) => {})
// Promise {<pending>}
```

When we call `resolve()`, the state of the promise becomes "fulfilled". 
```js
new Promise((resolve, reject) => {
  resolve()
})
// Promise {<fulfilled>: undefined}
```
We didn't pass argument to resolve(), so the result of this promise is `undefined`. it's like when we call a function that doesn't not explicitly return anything, the function will always return undefined.

This time we `resolve()` the promise with a value 'nice'. The state of the promise becomes "fulfilled" and the result is "nice".
```js
new Promise((resolve, reject) => {
  resolve('nice')
})
// Promise {<fulfilled>: 'nice'}
```

A promise could be resolved or rejected. `reject` works similar to `resolve`, it changes the state of the promise and provides a result.
```js
new Promise((resolve, reject) => {
  reject('bad')
})
// Promise {<rejected>: 'bad'}
```
If you run this code, you will see `Uncaught (in promise) bad`. We'll deal with it later, so let's ignore it for now.

At this point, we only make synchronous code. 
```js
console.log(1)
new Promise((resolve, reject) => {
  console.log(2)
  resolve('nice')
  console.log(3)
})
console.log(4)

// this result shows that everything runs in order, synchronously.
// 1
// 2
// 3
// 4
``` 
Keep one thing in mind, when we construct a promise, it schedules something to run asynchronously immediately. The action of starting a task is synchronous. It is the action of handling the result of `resolve` or `reject` being asynchronous. 

So, how do we handle the result? 
`Promise` object has a `.then` method that we can use to handle the result of a "fulfilled" state promise which has been resolved with `resolve()`.

`.then()` also accept a callback, it will pass the result of the previous `Promise` to the callback.
```js
new Promise((resolve, reject) => {
  resolve('nice')
}).then((result) => {
  console.log(result)
})

// nice
```

If you run this code in the console, you probably have noticed one thing. After "nice" is logged, there is a promise logged.
```js
// Promise {<fulfilled>: undefined}
```
Before we learn where this promise comes from, we need to know why it is logged.

When we call a function in the console, it will automatically log the result of the function. If a function doesn't explicitly return anything, it returns undefined.
```js
console.log(1)
// 1 
// console.log() doesn't explicitly return anything, so it returns undefined.
// undefined 
```
I am using chrome and I am not sure if this happens in other browsers.

Alright, so where does that promise come from? In fact, what we return from `.then()` will be a new "fulfilled" state promise with the result of the returned value. We only `console.log(result)` in `.then()` and didn't return anything explicitly, so the result is undefined.
```js
new Promise((resolve, reject) => {
  resolve('nice')
}).then((result) => {
  console.log(result)
})

// nice
// Promise {<fulfilled>: undefined}
```

This means that we can chain `.then` on and on.
```js
new Promise((resolve, reject) => {
  resolve('nice')
}).then((result) => {
  console.log(result) // 'nice'
}).then((result) => {
  console.log(result) // undefined
})
```

We can use `.catch()` to handle the result of a "rejected" state promise which has been rejected with `reject()`. We can schedule something to run like `promise1.then().then().catch()`. if one of the promises in the chain is rejected, `.then()` will be skipped and `.catch()` will take the rejected result.

```js
new Promise((resolve, reject) => {
  reject('bad')
}).then((result) => {
  // previous promise is rejected, so the code inside .then() is skipped
  console.log(result)
}).catch((error) => {
  console.log(error) // bad is logged from here
})

// bad 
```
We handle the rejected promise, so we won't see `Uncaught (in promise) bad` error this time. You probably have noticed ` Promise {<fulfilled>: undefined}` appear in the console again. This is something you'd like to keep in mind. What we return from `.catch()` is also a "fulfilled" state promise. 


We can return a rejected promise in `.then()` or `.catch()` with `throw new Error('some message')`. 
```js
new Promise((resolve, reject) => {
  resolve('nice')
}).then((result) => {
  if(result !== 'great') {
    // throw error inside .then will return a rejected promise
    throw new Error('not good enough') 
  }
}).catch((error) => {
  console.log(error) 
})

// Error: not good enough
```

Throwing an error inside `.catch()`.
```js
new Promise((resolve, reject) => {
  reject('bad')
}).then((result) => {
  console.log(result) 
}).catch((error) => {
  if(error === 'bad') {
    throw new Error('this is really bad')
  }
})

// Uncaught (in promise) Error: this is really bad
```
The error we see in the console proves that we returns a rejected promise. 

`Promise.reject()` is an another way to return a rejected promise.  This is useful when we want to return a more complex data like an object. 
```js
// return a rejected promise in .then
new Promise((resolve, reject) => {
  resolve('nice')
}).then((result) => {
  return Promise.reject({reason: 'something went wrong'})
})
// Promise {<rejected>: {…}}

// return a rejected promise in .catch
new Promise((resolve, reject) => {
  reject('bad')
}).catch((error) => {
  return Promise.reject({reason: 'something went wrong'})
})
// Promise {<rejected>: {…}}
```
Notice that we didn't add `new` keyword to `Promise.reject()`. It is a method available on the `Promise` object. It's similar to how we call `Array.isArray()`, using the `Array` object.


Returning another rejected promise from `.catch()` seems to be redundant. The idea is that sometimes we would handle in different ways. Returning a rejected promise means that we can `.catch()` it in other places.


### How promise work with other code
So far we only make `Promise` and resolve or reject it immediately. In real world, we usually don't know how much time doing something can take, but we want to react to it as soon as something is done. That's the value of scheduling asynchronous code with `Promise`. Let's see an example to help understand how asynchronous code run.
```js
// define a variable 
let promiseResolver = null

new Promise((resolve, reject) => {
  // store the resolve of this promise to the variable
  promiseResolver = resolve
}).then((result) => {
  console.log(1, result)
}).then((result) => {
  console.log(2, result)
})

// This is the first promise we created with new Promise(callback)
// Promise {<pending>}
```
We don't see `1` and `2` being logged from those two `.then()` because `.then()` is only called when the previous promise is resolved.


Now we can call the `resolve` function stored in `promiseResolver` to resolve the first promise.
```js
promiseResolver('hello')
// 1 'hello'
// 2 undefined
```
As soon as we call `promiseResolver('hello')`, first `.then()` can run and `1` is logged as expect. second `.then()` also runs because the first `.then()` implicitly return a "fulfilled" promise with a value `undefined`. That's why the second log is `2 undefined`. Although we see the results from `.then()` right after we resolve the first promise, asynchronous code has to wait the current synchronous code to be finished, which means that if a promise is resolved or rejected during some synchronous code running, `.then()` or `.catch()` will be queued and run after those synchronous code already in the list are finished.

A promise can only be "fulfilled" or "rejected" once. Let's modify the example and explicitly return a new promise in the first `.then()`.
```js
let promiseResolver = null

new Promise((resolve, reject) => {
  promiseResolver = resolve
}).then((result) => {
  console.log(1, result)
  // explicitly return a new promise
  return new Promise(() => {})
}).then((result) => {
  console.log(2, result)
})
```

promiseResolver only stores the resolve function of the first promise, so calling it multiple times will only work for the first time.
```js
// resolve first promise
promiseResolver('hello')
// 1 'hello'

// no effect
promiseResolver('hello')
promiseResolver('hello')
promiseResolver('hello')
```
The second `.then()` will never be triggered because we didn't store the resolve function of the promise returned from the first promise, which means that the state of the promise will always stay "pending".

This time we also store the resolve function of the promise returned from first `.then()`.
```js
let promiseResolver = null

new Promise((resolve, reject) => {
  // store resolve function of this promise
  promiseResolver = resolve
})
.then((result) => {
  console.log(1, result)
  // explicitly return a new promise 
  return new Promise((resolve, reject) => {
    // store resolve function of this promise
    promiseResolver = resolve
  })
})
.then((result) => {
  console.log(2, result)
})
```

Resolving the first and the second promise with the resolve functions that were assigned to `promiseResolver`.
```js
// resolve first promise created by new Promise()
promiseResolver('hello')
// run first .then
// 1 'hello' 

// resolve second promise returned by first .then
promiseResolver('hi')
// run second .then
// 2 'hi'
```
Again, `.then` only runs after the previous promise is resolved. 

Let's see how promise work with more synchronous code. 
```js
console.log(1)
new Promise((resolve, reject) => {
  console.log(2)
  resolve()
  console.log(3)
}).then(() => {
  console.log(4) // 4 is here
})
console.log(5)

// 1
// 2
// 3
// 5
// 4 is here
```
This is a promise that resolves immediately. The code inside `new Promise()`'s callback is synchronous. Promise can schedule asynchronous code with `.then()`. We want to `console.log(4)` after the promise is resolved as soon as possible, but asynchronous code has to wait until all synchronous code are finished. When JavaScript see asynchronous code, it will put asynchronous code aside and read next statement. if next statement is synchronous, JavaScript runs that code immediately. if next statement is asynchronous, JavaScript puts that code aside again and read the next statement. When asynchronous code is ready to be run, it will be queued to the end of the synchronous code. That's why `4` is logged after `5`. 

#### Challenge: 
Can you reason about the result of this snippet?
```js
console.log(1)

new Promise((resolve, reject) => {
  console.log(2)
  resolve()  
}).then(() => {
  console.log(3) 
})

new Promise((resolve, reject) => {
  resolve()
  console.log(4)
}).then(() => {
  console.log(5) 
}).then(() => {
  console.log(6) 
})

new Promise((resolve, reject) => {
  console.log(7)
  resolve()  
}).then(() => {
  console.log(8) 
})

console.log(9)
```

Result:
```js
1
2
4
7
9
3
5
8
6
```
It's totally fine if you don't get the correct result by reading the snippet, but I think you are able to tell why after you see the result. We won't use promises like this snippet in real world. This is only for testing if you understand the order of synchronous and asynchronous code.

---

## async await
`async` function and `await` is the _syntax sugar_ to work with `Promise`. 

Creating an async function is easy. Define a function and put the `async` keyword before the `function` keyword. What we return from a async function will implicitly be a "fulfilled" promise. 
```js
async function myFunc() {

}
myFunc()
// Promise {<fulfilled>: undefined}
```

We can explicitly `throw new Error` or `return Promise.reject()` to return a rejected promise.
```js
async function myFunc() {
  return Promise.reject({reason: 'something went wrong'})
}
myFunc()
// Promise {<rejected>: {…}}
```

The benefit of using async function is that we can use `await` keyword to make asynchronous code look like synchronous code. 
```js
async function myFunc() {
  console.log(1)
  // await will wait this promise to be done before moving on to next statement.
  await new Promise((resolve, reject) => {
    console.log(2)
    resolve()
  })
  // below code runs after the promise being await is done
  console.log(3)
}

myFunc()
// 1
// 2
// 3
```

This is the same code writing without `async` and `await`. We need to chain a `.then()` to get the correct order of running `console.log(3)`. 
```js
function myFunc() {
  console.log(1)
  new Promise((resolve, reject) => {
    console.log(2)
    resolve()
  }).then(() => {
    console.log(3)  
  })
}

myFunc()
```

We can `await` a promise to store the result of a "fulfilled" promise with a variable
```js
async function myFunc() {
  const result = await new Promise((resolve, reject) => {
    resolve('hello')
  })
  console.log(result)
}

myFunc()
// hello
```

async function returns a promise, so it can also be `await`. Unless we explicitly return something or the code reach the end of a async function, the promise state would be "pending", which means that calling an async function with `await`, the code of that async function has to be done before moving on. 
```js
// async function returns a promise
async function myFunc() {
  console.log(1)
  await new Promise((resolve, reject) => {
    console.log(2)
    // delay the resolve
    setTimeout(() => {
      resolve()
    }, 1000)   
  })
  console.log(3)
}

// calling another async function inside this async function
async function anotherFunc() {
  console.log(4)
  // await an async function
  await myFunc()
  console.log(5)
}

anotherFunc()
// 4
/*
  await myFunc()
  1
  2 
  wait 1000ms
  3           
*/
// 5
```

We can use `try catch` to handle rejected promise inside async function. 
```js
async function myFunc() {
  try {
    await new Promise((resolve, reject) => {
      reject('something went wrong')
    })
  } catch(error) {
    console.log(error)
  }
}

myFunc()
// something went wrong
```
[try catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) is not a special thing for async function. It is meant to be used to catch errors.

We can handle error in the child scope and return a "fulfilled" promise, and decide what to do with the result inside the parent scope.
```js
async function myFunc() {
  try {
    await new Promise((resolve, reject) => {
      // change the rejected value here to see different result from calling anotherFunc()
      reject('not a big deal')
    })
  } catch(error) {
    if (error === 'not a big deal') {
      return 'ok'
    } else {
      return 'bad'
    }
  }
}

async function anotherFunc() {
  try {
    // the function being called is the child scope
    const result = await myFunc()
    if (result === 'ok') {
      console.log('keep running code below')
    } else {
      throw new Error('throw error in try will jump to catch')
    }
    // other code
    // ...
  } catch(error) {
    console.log(error)
  }
}

anotherFunc()
// keep running code below
```

We can also return a rejected promise from the child scope, so we don't need to examine the result again in `try` block of the parent scope and can handle the error directly in `catch` block.
```js
async function myFunc() {
  try {
    await new Promise((resolve, reject) => {
      reject('is a big deal')
    })
  } catch(error) {
    if (error === 'not a big deal') {
      return 'ok'
    } else {
      // explicitly return a rejected promise
      return Promise.reject('bad')
    }
  }
}

async function anotherFunc() {
  try {
    // await a promise that is rejected will jump to catch
    const result = await myFunc()
    
    // there is an error above 
    // so rest of the code in try block won't run
    // ...
  } catch(error) {
    console.log(error)
  }
}

anotherFunc()
```
Which one should be used is based on your needs.

---

## Wrap up
In this article, we learn the necessary fundamentals to understand `Promise`. We learn how to use `Promise` and also learn how to use `async await` to work with `Promise`. I've tried to make this article as simple as possible, but learning promise can be frustrated. I hope this article can help make your journeys a little bit easier.