📘
NavDoc by Bash School
GithubContact
📘
NavDoc by Bash School
  • 🎓Introduction
  • 🐢Getting Started
  • ⚡Changelog
  • 👨‍🚀Maintainers
  • 🛣️Roadmap
  • Fundamentals
    • The Internet
      • Introduction
      • What is a URL
      • What is a port
      • The DNS protocol
      • The TCP protocol
      • The UDP protocol
      • The Web
      • The HTTP protocol
      • Hyperlinks
      • What is a Web browser
      • What is a Web server
    • HTML
      • Your first HTML page
      • Text tags
      • Attributes
      • Links
      • Images
      • Lists
      • Head Tags
      • Container tags
    • CSS
      • Introduction
      • Colors
      • selectors
      • Cascade
      • Specificity
      • Units
      • Advanced selectors
      • Typography
      • The box model
      • The display property
      • Responsive design
  • JavaScript
    • Basics
      • Introduction
      • Literals , Identifiers, Variables
      • Comments
      • The difference between let, const and var
      • Types
      • Operators and expressions
      • Arithmetic operators
      • The assignment operator
      • Operators precedence
      • Strings
      • Numbers
      • Semicolons, white space and sensitivity
      • Arrays
      • Conditionals
      • Loops
      • Functions
      • Objects
      • Arrays + functions
      • OOPS
      • Asynchronous
      • Scope, hoisting, event loop
      • ES Modules
      • Errors and exceptions
      • Built-in objects
        • The global object
        • Object properties
        • Number
        • String
        • Math
        • JSON
        • Date
        • Intl
        • Set and Map
      • More operators
    • Nodejs
      • Getting Started
      • Installation
      • Hello World in Node
      • Modules
      • Packages
      • File Handling
      • HTTP Request
      • Processing Files
      • HTTP
    • Express.js
      • Getting Started
      • Middleware
      • Serve Static Assets
      • How to Send Files to the Client
      • Sessions
      • Validate Input
      • Sanitizing Data
      • Forms
      • File Uploads
    • React
      • Setting up a React project with Vite
      • React Components
      • Introduction to JSX
      • Using JSX to compose UI
      • The difference between JSX and HTML
      • Embedding JavaScript in JSX
      • Handling user events
      • Managing state
      • Component props
      • Data flow
      • Lifecycle events
      • Managing forms in React
      • Install the React Developer Tools
      • Installing Tailwind CSS in a React app
      • Build a counter in React
    • TypeScript
      • Key Benefits
      • Types of Languages
      • The Need for TypeScript
      • What is TypeScript?
      • The tsc Compiler
      • Basic Types in TypeScript
      • tsconfig
      • Interfaces
      • Types
      • Arrays in TypeScript
      • Enums
      • Exporting and importing
    • MongoDB
      • SQL vs. NoSQL Databases
      • Installing MongoDB
      • MongoDB Databases and Collections
      • Working with Documents
      • MongoDB Operators
      • Sorting, Indexing & Searching
      • Built-in Methods
Powered by GitBook
On this page
  • Callbacks
  • Timers
  • Promises
  • Async functions
  • Chaining promises
  • Orchestrating promises
  • Promise.all()
  • Promise.race()
  • Promise.allSettled()

Was this helpful?

Edit on GitHub
  1. JavaScript
  2. Basics

Asynchronous

These days you can rarely write a JavaScript program without thinking about asynchronous code. You must understand this deeply.

In this section we’ll talk about Callbacks, Promises, Async/Await.

What is asynchronous code?

Suppose your program needs to perform some kind of action.

This action could take multiple seconds to perform. You don’t want to halt your entire program to wait for its completion, right?

That’s why we need a way to tell JavaScript “do this, and get back to me when you’re done, but in the meantime continue doing some other work”.

We’ll introduce this topic in the same historical order of appearance of the ways we can do it.

JavaScript, since its early days, had callbacks, paired with timers and other browser-provided APIs like event handlers.

Then we got promises and async functions, two relatively recent addition to the language, which completely transformed the way we now write JavaScript.

We’ll first learn callbacks, then we’ll dive into promises and async functions.

Callbacks

JavaScript is synchronous by default and is single threaded. This means that code cannot create new threads and run in parallel.

Lines of code are executed in series, one after another, for example:

const a = 1
const b = 2
const c = a * b
console.log(c)
doSomething()

JavaScript was born inside the browser, its main job, in the beginning, was to respond to user actions, like mouse clicks.

We needed a way to say “do this when the user clicks that button”.

The browser implemented a way to do it by providing a set of APIs.

We’re not going into the DOM and event handlers in this masterclass, because that’s a big topic on its own, that’s not pure “JavaScript”. But I need to introduce them briefly to explain asynchronous coding.

In this example we have document.querySelector() which lets us select the item with the id attribute set to button. Then we call addEventListener() to attach an event handler to the click event:

document.querySelector('#button').addEventListener('click', () => {
  //this function is executed when the mouse is clicked
})

The second argument to addEventListener is this arrow function:

() => {
  //this function is executed when the mouse is clicked
}

We call this a callback function.

It is only executed when the browser decides it should run, which in this case is when the user clicks the button.

So the browser provides us a way to defer the execution of the function in the future.

And in the meantime it will keep executing other lines of JavaScript in our program, without stopping to wait when the user will click the button.

Timers

Event handlers are just one possible use case for callbacks.

Another quite useful one are timers.

One thing to note is that just like event handlers, timers are not part of the JavaScript language. They are add-ons, provided by the browser or Node.js depending if you write frontend or backend code.

But I mention them because they are perfect to explain callbacks and asynchronous code.

We have 2 kinds of timers:

  • setTimeout()

  • setInterval()

With setTimeout you specify a callback function to execute later, and a value expressing how later you want it to run, in milliseconds:

setTimeout(() => {
  // runs after 2 seconds
}, 2000)

setTimeout(() => {
  // runs after 50 milliseconds
}, 50)

setTimeout returns the timer id. This is generally not used, but you can store this id, and clear it if you want to delete this scheduled function execution:

const id = setTimeout(() => {
  // should run after 2 seconds
}, 2000)

// I changed my mind
clearTimeout(id)

setInterval() is a function similar to setTimeout, with a difference: instead of running the callback function once, it will run it forever, at the specific time interval you specify (in milliseconds):

setInterval(() => {
  // runs every 2 seconds
}, 2000)

The function above runs every 2 seconds unless you tell it to stop, using clearInterval, passing it the interval id that setInterval returned:

const id = setInterval(() => {
  // runs every 2 seconds
}, 2000)

clearInterval(id)

It’s useful to call clearInterval inside the setInterval callback function, to let it auto-determine if it should run again or stop. For example, this code runs something unless status has the value done:

const interval = setInterval(() => {
  if (status === 'done') {
    clearInterval(interval)
    return
  }
  // otherwise do things
}, 100)

Promises

Promises are another way to handle asynchronous code.

Here’s a simple example of a promise:

let done = true

const isFinished = new Promise((resolve, reject) => {
  if (done) {
    const workDone = 'Here is the thing I built'
    resolve(workDone)
  } else {
    const why = 'Still working on something else'
    reject(why)
  }
})

You create a promise using the new Promise() syntax, which accepts a function.

That function gets two parameters, resolve and reject:

new Promise((resolve, reject) => {

})

Inside the function you do some work, and then you either call resolve() or reject(), because those 2 parameters are functions.

By the name of those 2 functions, you can imagine the consequences.

The promise is either fulfilled, or rejected.

In the above example, we call resolve() when done is true. You can change done to false, and the promise will fail.

What does it mean? It means that when we’ll use the promise, we’ll be able to handle a different scenario.

Here’s how to use a promise:

isFinished.then(ok => {
  console.log(ok)
}).catch(err => {
  console.error(err)
})

This code will execute the isFinished() promise and will wait for it to resolve.

When this happens, the code inside the then() function callback will be executed.

If there is an error and the promise is rejected, we will handle it in the catch callback.

Async functions

Async functions are a higher level abstraction over promises.

They are built on promises, so they don’t replace. them, but they expand what promises can do in a really powerful way.

Async functions reduce the boilerplate around promises. They make it so much easier to use them in a more straightforward way.

Suppose you want to use the isFinished promise we defined in the previous lesson.

To use this promise you know you’d have to do something like this:

isFinished.then(ok => {
  console.log(ok)
})

With async functions, you’d write it in this way:

const result = await isFinished
console.log(result)

When the JavaScript engine sees the await keyword, the execution of the current function will halt until the isFinished promise is resolved or rejected.

There’s just one caveat: the enclosing function must be declared as async:

const doSomething = async () => {
  const result = await isFinished
  console.log(result)
}

doSomething()

We can’t call

await isFinished

outside of a function not defined as async.

And remember IIFE, immediately invoked functions? They turn out to be very useful when dealing with async/await, because we can create an IIFE to wrap an async function, and have a more clear syntax compared to defining doSomething and then calling it, like we did above:

(async () => {
  const result = await isFinished
  console.log(result)
})()

Instead of using a .catch() block like in promises, we’d use a try/catch block, for which we’ll learn more about when we’ll talk about errors and exceptions, but here’s a sneak peek:

isFinished.then(ok => {
  console.log(ok)
}).catch(err => {
  console.error(err)
})
try {
  const result = await isFinished
  console.log(result)
} catch(err) {
  console.error(err)
}

Chaining promises

A promise can be returned from a then() method of a promise.

You can then call its then() method, creating a chain of promises.

I think the best way to explain this concept is to use fetch(), a JavaScript feature we’ll introduce later on in details during this masterclass.

In short, we use fetch() to get a resource from the network.

In this example, we call fetch() to get JSON from GitHub with my user details (try with yours) using this URL: https://api.github.com/users/flaviocopes

fetch('https://api.github.com/users/flaviocopes')

fetch() returns a promise which resolves to a Response object. When we got that, we need to call its json() method to get the body of the response:

fetch('https://api.github.com/users/flaviocopes')
  .then(response => response.json())

This in turn returns a promise, so we can add another .then() with a callback function that is called when the JSON processing has finished, with the job of writing the data we got to the console:

fetch('https://api.github.com/users/flaviocopes')
  .then(response => response.json())
  .then(data => {
    console.log('User data', data)
  })

For comparison, with async functions we’d write the above code in this way:

const getData = async () => {
  const response = await fetch('https://api.github.com/users/flaviocopes')
  const data = await response.json()
  console.log('User data', data)
}

getData()

Orchestrating promises

Promise.all()

If you need to synchronize different promises, Promise.all() helps you define a list of promises, and execute something when they are all resolved.

Promise.all() executes the callback function passed to its then() method when all of the promises you pass to it successfully resolve.

Example:

const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')

Promise.all([f1, f2]).then(([res1, res2]) => {
  console.log('Results', res1, res2)
})

res1 and res2 are objects, each containing the data of the network request response.

Promise.race()

Promise.race() executes the callback function passed to its then() method as soon as one of the promises you pass to it resolves.

It runs the callback function just once, with the result of the first promise resolved.

Example:

const promiseOne = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one')
})
const promiseTwo = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two')
})

Promise.race([promiseOne, promiseTwo]).then(result => {
  console.log(result) // 'two'
})

Promise.allSettled()

The Promise.allSettled() method is similar to Promise.all() but with one key difference.

Promise.all() executes the callback function passed to its then() method when all of the promises you pass to it resolve.

Promise.allSettled() executes the callback function passed to its then() method when all of the promises you pass to it either resolve OR are rejected.

In the callback function you will have an array of results, each with a set of status and value properties.

Example:

const f1 = fetch('https://api.github.com/users/flaviocopes')
const f2 = fetch('https://api.github.com/users/dhh')

Promise.allSettled([f1, f2]).then(([res1, res2]) => {
  console.log(res1.status, res1.value) // status = 'fulfilled', value = Response object
  console.log(res2.status, res2.value)  // status = 'fulfilled', value = Response object
})

With an invalid URL, like this:

const f1 = fetch('https://api.github.com/users/flaviocopes')
const f2 = fetch('test')

Promise.allSettled([f1, f2]).then(([res1, res2]) => {
  console.log(res1.status, res1.value)
  console.log(res2.status, res2.value)
})

status in the second request will be 'rejected' and the value of the response will be undefined.

PreviousOOPSNextScope, hoisting, event loop

Last updated 1 year ago

Was this helpful?