JS Intermediate – What is Asynchronous Programming in JavaScript

Understanding Asynchronous Programming

Asynchronous programming is a programming technique that allows multiple tasks to be executed concurrently, without blocking the execution of other tasks. In traditional synchronous programming, tasks are executed one after another, and the program waits for each task to complete before moving on to the next one. This can lead to slow and unresponsive applications, especially when working with long-running tasks such as network requests or file I/O.

Asynchronous programming in JavaScript is typically implemented using callbacks, promises, or async/await functions. Callbacks are functions that are passed as arguments to other functions and are executed when the other function completes its task. Promises are objects that represent the eventual completion or failure of an asynchronous operation and allow for the chaining of multiple operations. Async/await functions are a newer syntax that allows for more readable and concise asynchronous code, by using the async keyword to mark a function as asynchronous and the await keyword to pause execution until a promise is resolved.

Asynchronous programming is particularly important in modern web development, where applications need to be able to respond quickly to user input and make efficient use of network resources. By allowing multiple tasks to be executed concurrently, asynchronous programming can significantly improve the performance and responsiveness of web applications, leading to a better user experience and increased productivity. However, asynchronous programming can also be more complex and error-prone than synchronous programming and requires careful management of resources and error handling to ensure that tasks are executed correctly and in the right order.

Handle Asynchronous

The JavaScript engine uses an event loop and is single-threaded. Simply, this indicates that all statements you execute will be processed sequentially. JavaScript has a number of strategies to prevent blocking calls in order to prevent waiting while calculations are made. These operations are asynchronous.

JavaScript has three ways to deal with asynchronous code: promises, which let you chain together methods, callbacks, which let you specify functions to be called after the asynchronous method has finished processing, and async/await keywords, which are really syntactic sugar on promises.

Callbacks

The initial approach to asynchronicity management. With callbacks, you may specify a function that will run once the asynchronous code has been completed. The function cb() in the example below accepts a function as an argument and calls that function once it is complete.

Example:

console.log('I\'m Line number 1');
function cb() {
   console.log('I\'m Done.');
   setTimeout(() => {
       console.log('I\'m Line number 2');
   }, 2000);
}
cb()
console.log('I\'m Line number 3');

Output:

I'm Line number 1
I'm Done.
I'm Line number 3
I'm Line number 2

Promises

Promises are proxy objects for a value that might not yet exist and are an abstraction for asynchronous control flow. Internally, a Promise is always in 1 of 3 states:

  • Pending
  • Resolved
  • Rejected

Optionally, a resolved or rejected promise contains a value.
Every promise initially exists in the state of pending and can only change once (i.e. a resolved promise never becomes rejected later.

Creating promises

The static methods Promise.resolved() and Promise.rejected() allow us to immediately encapsulate any value in a promise.

We use the constructor more frequently, which we can resolve or reject at any time in the future.

Example:

const resPromise = Promise.resolve(363)
 
resPromise.then(value => {
   console.log(value)
})
 
const rejPromise = Promise.reject('It is rejected')
 
rejPromise.catch(value => {
   console.log(value)
})
 
const funcPromise = new Promise((resolve, reject) => {
   resolve('Enablegeek')
})
 
funcPromise
   .then(value => {
       console.log('Resolved', value)
   })
   .catch(value => {
       console.log('Rejected', value)
   })

Output:

363
It is rejected
Resolved Enablegeek

Chaining promises

We can chain processes together using .then and .catch. Both of these techniques produce fresh promises. All .then calls will be skipped until a.catch is found if we throw an error or return a rejected promise.

A rejected promise should, by convention, provide an error object rather than a random value. A rejected promise is implicitly created when an error is thrown.

Example:

Promise.resolve(112)
   .then(value => value * 2)
   .then(value => Promise.resolve(value + 1300))
   .then(value => {
       console.log(value)
   })
 
Promise.resolve(42)
   .then(value => value * 2)
   .then(value => Promise.reject(value))
   .then(value => Promise.resolve(value + 1000))
   .catch(value => {
       console.log('Caught!', value)
       return value
   })
   .then(value => value * 2)
   .then(value => {
       console.log('OK', value)
   })

Output:

1524
Caught! 84
OK 168

Handling events

We’ve been utilizing promises synchronously up to this point, but when working with the event loop, we usually utilize them asynchronously. As an illustration, we could include a setTimeout call in a promise.

Consider utilizing a Promise in place of a callback whenever the callback will only be made once.

Example:

function sleep(delay, value) {
   return new Promise(resolve => {
       setTimeout(() => resolve(value), delay)
   })
}
 
sleep(3000, 'Guss').then(value => {
   console.log(`Hey! ${value}`)
})

Output:

Hey! Guss

Multiple promises

Promises allow us to do several asynchronous activities concurrently. Promise.race() resolves if only one promise in the array resolves, whereas Promise.all() returns a promise that resolves if all promises in an array do.

Example:

function sleep(delay, value) {
   return new Promise(resolve => {
       setTimeout(() => resolve(value), delay)
   })
}
 
const promiseA = sleep(3000, 'A')
const promiseB = sleep(1000, 'B')
const promiseC = sleep(2000, 'C')
 
Promise.all([promiseA, promiseB, promiseC]).then(values => {
   console.log(values)
})
 
Promise.race([promiseA, promiseB, promiseC]).then(value => {
   console.log(value)
})

Output:

B
[ 'A', 'B', 'C' ]

Async/Await

For working with promises, a particular syntax is used with the words async and await. If we return a non-promise value from a function that is designated as async, it is automatically wrapped in a Promise.resolve().

We may use await inside the function to watch for a promise to be accepted or refused and then access its value. The key benefit of this syntax is that it avoids the need for callback chains that are deeply nested. Even though the syntax is cleaner, all the complexity of asynchronous programming is still present.

Example:

function sleep(delay, value) {
   return new Promise(resolve => {
       setTimeout(() => resolve(value), delay)
   })
}
 
async function run() {
   const value = await sleep(2000, 'Hey! Geeks.')
 
   console.log(value)
 
   return value
}
 
run()
   .then(value => {
       console.log(value)
   })
   .catch(error => {
       console.log(error)
   })

Output:

Hey! Geeks.
Hey! Geeks.

Ajax

Ajax, which stands for “Asynchronous JavaScript and XML,” is a group of online development methods that uses a variety of client-side web technologies to construct asynchronous web applications. Ajax enables web applications to communicate with servers asynchronously (in the background) and to transmit and receive data without changing how a page looks or behaves. Ajax enables dynamic content changes on web pages and, by extension, web applications without requiring a page reload by separating the data exchange layer from the presentation layer. Modern implementations typically use JSON rather than XML in practice.

Ajax is a programming idea rather than a technology. Information can be marked up and styled using a combination of HTML and CSS. JavaScript can alter the webpage such that the user can interact with the new content while it is dynamically displayed. Ajax allows websites to load content into the screen without refreshing the page by using the built-in XMLHttpRequest object. Ajax is not a novel technology nor a novel language. It is rather the utilization of already existing technologies in novel ways.

Fetch API

The Fetch API offers a JavaScript interface for navigating and interacting with requests and answers, as well as other components of the HTTP pipeline. Additionally, it offers the global fetch() method, which offers a simple, comprehensible way to asynchronously fetch network resources.

XMLHttpRequest was formerly used to achieve this kind of functionality. Better alternatives, such as Fetch, are available and are simple for other technologies, like Service Workers. Additionally, Fetch offers a solitary, sensible location to declare other HTTP-related terms like CORS and extensions to HTTP.

Example:

fetch('https://jsonplaceholder.typicode.com/users/1')
   .then(response => response.json())
   .then(json => console.log(json))

Output:

{
  "id": 1,
  "name": "Leanne Graham",
  "username": "Bret",
  "email": "[email protected]",
  "address": {
    "street": "Kulas Light",
    "suite": "Apt. 556",
    "city": "Gwenborough",
    "zipcode": "92998-3874",
    "geo": {
      "lat": "-37.3159",
      "lng": "81.1496"
    }
  },
  "phone": "1-770-736-8031 x56442",
  "website": "hildegard.org",
  "company": {
    "name": "Romaguera-Crona",
    "catchPhrase": "Multi-layered client-server neural-net",
    "bs": "harness real-time e-markets"
  }
}

Async Generators

The idea of an async generator function was introduced with the TC39 async iterators proposal, which also brought for/await/of to JavaScript. JavaScript now offers six different categories of functions:

  • Normal functions function() {}
  • Arrow functions () => {}
  • Async functions async function() {}
  • Async arrow functions async () => {}
  • Generator functions function*() {}
  • Async generator functions async function*() {}

Because you can use both await and yield in an async generator function, async generator functions are unique. In contrast to async functions and generator functions, async generator functions return an async iterator rather than a promise or iterator. An async iterator is a type of iterator that always delivers a promise from its next() function.

Example:

async function* run() {
   await new Promise(resolve => setTimeout(resolve, 100));
   yield 'Hello';
   console.log('World');
}
const asyncIterator = run();
 
asyncIterator.next().
   then(obj => console.log(obj.value)). // Prints "Hello"
   then(() => asyncIterator.next());  // Prints "World"

Output:

Hello
World
Share The Tutorial With Your Friends
Twiter
Facebook
LinkedIn
Email
WhatsApp
Skype
Reddit

Check Our Ebook for This Online Course

Advanced topics are covered in this ebook with many practical examples.