Link copied!
Simplifying Asynchronous JavaScript with async/await Technical Log

TechiesAIE Journal

Simplifying Asynchronous JavaScript with async/await

TechiesAIE
TechiesAIE
Lead Developer · TechiesAIE
4 min read 822 words

Based on the sources linked below.

Cover image: Martin Vorel · CC BY-SA 4.0 · License · Image source

The async/await syntax in JavaScript provides a cleaner and more intuitive way to handle asynchronous operations, offering a significant improvement over traditional promise chaining for readability and error handling. It allows developers to write asynchronous code that looks and behaves much like synchronous code, while still leveraging the underlying promise mechanism.

An async function is declared using the `async` keyword, and within its body, the `await` keyword can be used to pause execution until a Promise settles (either resolves or rejects). When an async function is called, it always returns a new Promise, which will resolve with the value returned by the async function or reject with any uncaught exception within it.

How async/await Works

The core idea behind async/await is to simplify the consumption of promise-based APIs. The `await` keyword can only be used inside an `async` function (or at the top-level within JavaScript modules). It effectively makes a promise-returning function behave synchronously by suspending the execution of the async function until the awaited Promise is fulfilled or rejected. The resolved value of the Promise then becomes the return value of the `await` expression.

Consider a function that generates an audio file asynchronously, `createAudioFileAsync(audioSettings)`. If this function returns a promise, you would traditionally attach callbacks using `.then()` for success and `.catch()` for failure. With async/await, this pattern becomes:

```javascript async function generateAudio() { try { const audioUrl = await createAudioFileAsync(audioSettings); console.log(`Audio file ready at URL: ${audioUrl}`); } catch (error) { console.error(`Error generating audio file: ${error}`); } } ```

This code looks much like synchronous code, with the `await` keyword indicating points where the function will pause until the asynchronous operation completes. The `try...catch` block provides a familiar way to handle errors that occur during these operations, similar to how synchronous exceptions are caught.

Distinguishing Asynchronous Execution Flow

It's important to understand that while `await` makes code appear synchronous, the underlying operations are still asynchronous. An `async` function without an `await` expression will run synchronously. However, if an `await` expression is present, the `async` function will always complete asynchronously.

The body of an `async` function is logically split by `await` expressions. Code up to the first `await` runs synchronously. When an `await` is encountered, execution of the `async` function is suspended, and control is returned to the function that called it. Once the awaited Promise settles, control re-enters the `async` function to continue from where it left off, processing the result and proceeding to the next statement or `await` expression. This progressive construction of a promise chain ensures that asynchronous operations are managed efficiently without blocking the main thread.

Error Handling with async/await

One of the significant advantages of async/await is the simplified error handling. In traditional promise chains, if an error occurs, the browser searches down the chain for a `.catch()` handler. With async/await, a standard `try...catch` block can encompass multiple `await` calls, centralizing error management.

```javascript async function processData() { try { const result = await doSomething(); const newResult = await doSomethingElse(result); const finalResult = await doThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch (error) { console.error(`Operation failed: ${error.message}`); } } ```

This structure mirrors synchronous error handling, catching all errors, including thrown exceptions and programming errors, at a single point. This eliminates the

complexities often associated with 'callback hell' or deeply nested promise `.catch()` clauses.

Concurrency and Composition

While `await` processes operations sequentially, async/await can still be used effectively for concurrent operations by combining it with `Promise.all()` or `Promise.allSettled()`. These methods allow multiple promises to run in parallel and wait for all of them to complete or settle before proceeding.

```javascript async function concurrentOperations() { console.log("== concurrentOperations starts =="); const [slowResult, fastResult] = await Promise.all([ resolveAfter2Seconds(), resolveAfter1Second(), ]); console.log(`Slow result: ${slowResult}, Fast result: ${fastResult}`); console.log("== concurrentOperations done =="); } ```

In this example, `resolveAfter2Seconds()` and `resolveAfter1Second()` start concurrently. The `await Promise.all([...])` then waits for both promises to resolve before logging their combined results. This ensures that the overall execution time is determined by the slowest promise, rather than the sum of all promise durations if they were awaited sequentially. Using `Promise.all()` with `async/await` is crucial for robust error handling in concurrent scenarios, as `Promise.all` wires up the promise chain in one go, allowing errors to be caught reliably.

Practical Uses and Limitations

Async/await is widely available in modern JavaScript environments and is the preferred way to handle asynchronous code due to its readability. It is particularly useful for sequences of asynchronous operations where each step depends on the result of the previous one, or for complex workflows involving multiple API calls. While it simplifies syntax, developers must remember that `await` only pauses the `async` function it is within, not the entire program. Other asynchronous tasks can still run concurrently. Forgetting `await` can lead to unintended race conditions or `undefined` values, as the next `then` handler or subsequent code might execute before the promise has resolved.

Sources