Sync vs Async in JS


API

Updated Sep 7th, 2022

Article here and codeevolution summary video here.

Promises were meant to replace callbacks.

Really three ways to do asynchronous code in JavaScript. 1.) Callbacks 2.) Promises using .then().catch() and 3.) Promises using async/await

Consuming promises via API (example fetch, mongodb). Great that they can be chained together. Better than callbacks for error handle because you can have one catch block for all instead of one for each. Same for a catch block if using async/await instead of .then().catch()

Important to also note that in creating your own promises you can use the “new Promise” syntax and call “resolve” and “reject” or alternatively just use the “async” keyword (which will automatically return a promise) and use the “return” and “throw” keywords.

Understand Event Loop Order

Call Stack, browser APIs, callback queue (task queue), and the Job Queue (micro task queue)

Fireship Explanation of Promises

Video is here

See the Fireship video that talks about a loop freezing your code until complete so wrapping the loop in a promise to get the loop off the main thread and execute as a micro task.

Explain that “return new promise” may not get it off the main thread but you need to return Promise.resolve().then(() =>{//put loop here}). catch()

Using async keyword automatically returns the value as Promise.resolve behind the scenes. Whatever gets returned inside of this function gets returned as a promise of that value. It also creates a context for us to use the “await” keyword.

A few pro tips:

A few different ways to handle an error in the catch block, (catch the error obviously OR catch and throw another error OR catch the error and return a value). If you return a value it’s basically like ignoring the error and then providing some replacement value. With this approach the consumer of the promise won’t get an error but we’ll get the resolved value inside of the then callback.

You need to be careful when using async await in a .map or a .forEach function. It won’t actually pause the function in this context. Instead it will run all these promises concurrently, And this might not be the behavior your looking for. If you want to run a loop and have every iteration in the loop await a promise, you need to use a traditional for loop. So you can write anything functions and then write a for loop inside that function and then use the weight keyword inside the loop. This will pause each step of the loop until the promises resolved. Though more often than not, you will want to run things concurrently.

A cool thing you can do is use the awake keyword directly in a for loop. If you have a promise that You know we’ll resolve to an array You can actually just use the await keyword directly in your loop.

const fruitLoop = async () => {
  for await (const emoji of smoothie) {
    // do something
  }
}

You can also use The await keyword directly in your conditionals. So you can await the resolved value of the promise to see if it’s equal to some other value. Super concise way to write conditional expressions when working with promises.

I Didn’t Understand At First

If code is synchronous by default, and you make an API call or a DB operation or something that takes time don’t we want the other code to wait? And so we don’t we just leave it the way it is?

The idea is these may take a long time so they are set up to return promises and not wait by default. So it’s up to you to decide which code should wait and which code doesn’t wait.

If fetch returns a promise and MongoDB methods return promises do I even need to make my own promises? No probably not. 9 times out of ten you don’t need to create your own promises, you just need to know how to leverage a promise already set up. But you can if you are returning a value to use in another function.

It’s About Specifying Which Code Should Wait

JS waits by default but when an “async” action like a “setTimeout” is introduced the subsequent code will not wait so “async/await” is about moving specific code to the right place so it does wait again!

Fetch and “mongoDB methods” are already set up to return a promise. Any code below these lines will not wait unless you make some changes.

// old
fetch().then(res => return res.json()).then(data => console.log(data))

// new
async function start() {
  const response = await fetch()
  const data = await response.json()
  console.log(data)
}
start()

The Promise Object

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

If I take a look at the API created in the “React for the Rest of Us” course, he has wrapped the database operations in the model files in a promise and so he’s able to “resolve” with specific information. Mine currently are “async” functions that also have error handling with “try/catch.”

I am guessing that by creating a “new promise” he’s able to “resolve” or “reject” with specific messages/values. This is true but you can accomplish the same thing with “async/await” via the “return ” and “throw” keywords as described in the “refactor” section below.

In the controller file he then uses that “resolved” value, even if empty, block and sends a “res.json” success message. I’m the case above, an empty “resolve” in a model file still triggers a “.then()” in a controller file.

Refactor in FSJS Course

Converts “.then/catch” to “async/await” with “try/catch.” Also ditches the Promise object. Code before refactor:

Post.prototype.create = function() {
  return new Promise((resolve, reject) => {
    this.cleanup()
    this.validate()
    if (!this.errors.length) {
      postsCollection.insertOne(this.data).then((info)=>{
      resolve(info.insertedId)
      }).catch(()=> {
        this.errors.push("Please try again later.")
        reject(this.errors)
      })
    } else {
        reject(this.errors)
      }
  })
}

Want to wait for something can’t just say wait, wait for what? Wait for a promise to resolve. We work with promises more than needing to create our own. fetch resolves automatically, mongoDB methods resolve automatically. Your own validate function needs to be defined to resolve.

Some functions were also refactored later to not use the “return new Promise” syntax and just “returned” in place of the “resolve” keyword and “throw error” in place of “reject” keyword.

Note: An async function automatically returns a promise so you don’t ned to explicitly say “return new Promise((resolve, reject) => {})”

Also refactored to drop wrapper function that’s sole purpose was to prevent the immediate execution of a “new Promise.” Originally needed because as soon as JavaScript sees a promise using the “new Promise” syntax, it will immediately try and execute the promise. So the wrapper function was to used to only run the promise when that function was called. With the “async” keyword approach, this isn’t the case, the wrapper function isn’t needed, as you need to call the async function for the promise to run.

Using Async function without new Promise Syntax

Source: the end of learn web code video

Behind the scenes the browser will create a promise and resolve with whatever is returned.