Mastering Asynchronous JavaScript: Simplified Promises with Handy Utility Functions
Srijan Karki
Posted on June 24, 2024
It’s been nine years since the groundbreaking ES6 standard was introduced, bringing a plethora of useful features to JavaScript. Among these, the new way of handling asynchronous operations through Promises stands out.
Initially, developers struggled with "callback hell," which was somewhat alleviated by the .then/.catch
API. This evolved into the async/await
syntax, making JavaScript more readable. However, even with async/await
, the code could still be cumbersome:
let result;
try {
result = await someWork();
} catch (err) {
// handle the error
}
// do something with the result
Two main issues persist:
- The need to define a variable outside of the try/catch block.
- Unnecessary indentation from the try/catch block.
To streamline this, inspired by Golang's error-handling, I created a simple function for JavaScript. This evolved into a set of utilities that I’ve now compiled into a package.
One-Line Promise Handling with to
Instead of handling errors within try/catch blocks, you can use the to
function from the @mrspartak/promises
package:
import { to } from '@mrspartak/promises'
const [error, result] = await to(somePromise)
Benefits:
- Infers proper return types.
- Does not throw errors.
- Allows flexible variable naming via array destructuring.
- Works on both back-end and front-end, supporting the
finally
API.
let isLoading = true;
const [error, result] = await to(fetchData(), () => {
isLoading = false;
});
Timeout Promise Execution
Sometimes, you need to ensure that a promise completes within a certain timeframe, such as network requests. The timeout
function in our utility library ensures a promise resolves within a specified time:
import { timeout } from "@mrspartak/promises"
import { api } from "./api"
const [error, user] = await timeout(api.getUser(), 1000, 'Timedout');
if (error) {
// handle the error
}
Benefits:
- Prevents hanging operations.
- Improves user experience by providing timely feedback.
Delay the Execution
Introducing the delay
function, a simple utility to pause code execution for a specified time. This is useful for rate limiting, retry mechanisms, or creating artificial delays for testing.
import { delay, sleep } from "@mrspartak/promises"
import { parsePage } from "./parser"
for (let i = 0; i < 10; i++) {
const pageData = await parsePage(i);
await delay(1000);
}
Benefits:
- Enhances code reusability.
- Simplifies the introduction of delays.
- Adds flexibility for various scenarios like retry mechanisms and rate limiting.
Defer the Execution
The deferred
function provides a way to manually control the resolution or rejection of a promise, giving you full control over its lifecycle.
import { deferred } from "@mrspartak/promises"
import { readStream } from "./stream"
const { promise, resolve, reject } = deferred<void>();
const stream = await readStream();
let data = '';
stream.on('data', (chunk) => {
data += chunk;
});
stream.on('end', () => {
resolve();
});
await promise;
console.log(data); // Data is ready
Benefits:
- Allows manual control over promise resolution/rejection.
- Provides flexibility for operations where results aren't immediately available.
Retry the Execution
The retry
function retries a promise-returning function a specified number of times, optionally with a delay between attempts.
import { retry } from "@mrspartak/promises"
import { apiCall } from "./api"
const [error, result] = await retry(() => apiCall(), 3, { delay: 1000 });
if (error) {
// handle the error
}
Benefits:
- Retries failed operations, improving resilience.
- Can be customized with additional options like backoff algorithms and hooks.
Measure the Duration of Execution
The duration
function measures how long a promise takes to resolve or reject, providing insights into performance.
import { duration } from "@mrspartak/promises"
import { migrate } from "./migrate"
const migratePromise = migrate();
const [error, result, duration] = await duration(migratePromise);
console.log(`Migration took ${duration}ms`);
Benefits:
- Helps identify performance bottlenecks.
- Facilitates optimization of asynchronous operations.
Conclusion
By using these utility functions from the @mrspartak/promises
package, you can handle asynchronous operations in JavaScript more efficiently and elegantly. These tools not only simplify your code but also improve its readability and maintainability, making it easier to manage complex asynchronous workflows.
Posted on June 24, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.