


Promises in JavaScript: Understanding, Handling, and Mastering Async Code
Intro
I used to work as Java developer and I remember for the first time when I got in touch with promises in JavaScript. Even though the concept seemed simple, I still couldn’t fully grasp how Promises worked. It changed when I started to use them in projects and understood the cases they solved. Then came the AHA moment and all became more clear. Over time, Promises became a valuable weapon on my toolbelt. It is oddly satisfying when I can use them at work and solve the async handling between functions.
You probably first come across Promises when fetching data from an API, which is also the most common example. Recently, I’ve been interviewed, and guess what was the first question “Can you tell me the difference between Promise and Async Await?”. I welcome that because I see it as a good starting point to know better how the applicant understands how the mechanisms work. However, he or she is mostly using other libraries and frameworks. It let me write down the differences and describe good practices for handling async function errors.
What is the Promise
Let’s start with the initial question: “What is the Promise?” Promise is a placeholder for the value that we don’t know yet but we will get it as a result of asynchronous computation/function. If the promise goes well, then we will get the result. If the promise does not go well, then the promise will return an error.
A basic example of a Promise
Defining a Promise
You define Promise by calling its constructor and passing two callback functions: resolve and reject.
const newPromise = new Promise((resolve, reject) => { resolve('Hello'); // reject('Error'); });
We call resolve function when we want to succesfully resolve the Promise. reject is for rejecting the promise in the case when an error occurs during evaluating our logic.
Retrieving the Promise Result
We use the built-in function then to get the Promise's result. It has two passed callbacks, result and error. The result is called when the Promise is successfully resolved by the function resolve. If the Promise isn’t resolved, the second function error is called. This function is triggered either by reject or by another error that’s thrown.
newPromise.then(result => { console.log(result); // Hello }, error => { console.log("There shouldn't be an error"); });
In our example, we will get the result Hello because we successfully resolved the Promise.
Error handling of promises
When the Promise is rejected then is always invoked its second error callback.
const newPromise1 = new Promise((resolve, reject) => { reject('An error occurred in Promise1'); }); newPromise1.then( (result) => { console.log(result); // It is not invoked }, (error) => { console.log(error); // 'An error occurred in Promise1' } );
A more recommended approach for its clarity is to use the built-in catch method.
const newPromise2 = new Promise((resolve, reject) => { reject('An error occurred in Promise2'); }); newPromise2 .then((result) => { console.log(result); // It is not invoked }) .catch((error) => { console.log(error); // 'An error occurred in Promise2' });
catch method is chained and has provided its own error callback. It gets invoked when the Promise is rejected.
Both versions work well but the chaining is IMO more readable and it is handy when using other built-in methods that we cover further.
Chaining promises
The result of a promise could likely be another promise. In that case, we can chain an arbitrary number of then functions.
getJSON('categories.json') .then(categories => { console.log('Fetched categories:', categories); return getJSON(categories[0].itemsUrl); }) .then(items => { console.log('Fetched items:', items); return getJSON(items[0].detailsUrl); }) .then(details => { console.log('Fetched details:', details); }) .catch(error => { console.error('An error has occurred:', error.message); });
In our example, it serves to narrow down the search results to get details data. Each then function can also have its error callback. If we care only about catching any error in the chain of calls, then we can leverage catch function. It will be evaluated if any of the Promises return an error.
Promise all
Sometimes we want to wait for the results of more independent promises and then act on the results. We can use the built-in function Promise.all if we don’t care about the order of how the Promises got resolved.
Promise.all([ getJSON('categories.json'), getJSON('technology_items.json'), getJSON('science_items.json') ]) .then(results => { const categories = results[0]; const techItems = results[1]; const scienceItems = results[2]; console.log('Fetched categories:', categories); console.log('Fetched technology items:', techItems); console.log('Fetched science items:', scienceItems); // Fetch details of the first item in each category return Promise.all([ getJSON(techItems[0].detailsUrl), getJSON(scienceItems[0].detailsUrl) ]); }) .then(detailsResults => { const laptopDetails = detailsResults[0]; const physicsDetails = detailsResults[1]; console.log('Fetched laptop details:', laptopDetails); console.log('Fetched physics details:', physicsDetails); }) .catch(error => { console.error('An error has occurred:', error.message); });
Promise.all takes an array of Promises and returns an array of results. If one of the Promises is rejected then Promise.all is rejected as well.
Racing promises
Another built-in functionality is Promise.race. It’s used when you have multiple asynchronous functions - Promises - and you want to race them.
Promise.race([ getJSON('technology_items.json'), getJSON('science_items.json') ]) .then(result => { console.log('First resolved data:', result); }) .catch(error => { console.error('An error has occurred:', error.message); });
Execution of the Promises can take different times and Promise.race evaluates the first resolved or rejected Promise from the array. It is used when we don’t care about the order but we want the result of the fastest asynchronous call.
What is Async Await
As you can see, writing Promises requires a lot of boilerplate code. Luckily, we have the native Async Await feature, which makes using Promises even easier. We mark a function by the word async and by that, we say that somewhere in the code we will be calling asynchronous function and we should not wait for it. Then the async function is called with the await word.
Basic example of Async Await
const fetchData = async () => { try { // Fetch the categories const categories = await getJSON('categories.json'); console.log('Fetched categories:', categories); // Fetch items from the first category (Technology) const techItems = await getJSON(categories[0].itemsUrl); console.log('Fetched technology items:', techItems); // Fetch details of the first item in Technology (Laptops) const laptopDetails = await getJSON(techItems[0].detailsUrl); console.log('Fetched laptop details:', laptopDetails); } catch (error) { console.error('An error has occurred:', error.message); } }; fetchData();
Our fetchData is marked as async and it allows us to use await to handle asynchronous calls inside the function. We call more Promises and they will evaluated one after the other.
We use try...catch block if we want handle the errors. Rejected error is then caught in the catch block and we can act on it like logging the error.
What’s different
They are both features of JavaScript handling with asynchronous code. The main difference is in the syntax when Promises use chaining with then and catch but async await syntax is more in synchronous way. It makes it easier to read. Error handling for async await is more straightforward when it leverages try...catch block. This is a question that you can easily get at the interview. During the answer, you can get deeper into the description of both and highlight those differences.
Promise features
Of course, you can use all the features with async await. For example Promise.all.
const fetchAllData = async () => { try { // Use await with Promise.all to fetch multiple JSON files in parallel const [techItems, scienceItems, laptopDetails] = await Promise.all([ getJSON('technology_items.json'), getJSON('science_items.json'), getJSON('laptops_details.json') ]); console.log('Fetched technology items:', techItems); console.log('Fetched science items:', scienceItems); console.log('Fetched laptop details:', laptopDetails); } catch (error) { console.error('An error occurred:', error.message); } };
Practical use cases
Promises are a fundamental feature in JavaScript for handling asynchronous code. Here are the main ways it is used:
Fetching Data from APIs
As was already shown in the examples above, this is one of the most used use cases for Promises and you work with it daily.
Handling file operations
Reading and writing files asynchronously can be done using promises, especially by Node.js module fs.promises
import * as fs from 'fs/promises'; const writeFileAsync = async (filePath, content, options = {}) => { try { await fs.writeFile(filePath, content, options); console.log(`File successfully written to ${filePath}`); } catch (error) { console.error(`Error writing file to ${filePath}:`, error.message); } }; const filePath = 'output.txt'; const fileContent = 'Hello, this is some content to write to the file!'; const fileOptions = { encoding: 'utf8', flag: 'w' }; // Optional file write options writeFileAsync(filePath, fileContent, fileOptions);
Promise based libraries
Axios is library that you should be familiar with. Axios handles HTTP requests in client and is vastly used.
Express is a web framework for Node.js. It makes it easy to build web apps and APIs, and when you use promises with Express, your code stays clean and easy to manage.
Repository with examples
All the examples can be found at: https://github.com/PrincAm/promise-example
Summary
Promises are a fundamental part of JavaScript, essential for handling asynchronous tasks in web development. Whether fetching data, working with files, or using popular libraries like Axios and Express, you’ll frequently use promises in your code.
In this article, we explored what Promises are, how to define and retrieve their results, and how to handle errors effectively. We also covered key features like chaining, Promise.all, and Promise.race. Finally, we introduced async await syntax, which offers a more straightforward way to work with promises.
Understanding these concepts is crucial for any JavaScript developer, as they are tools you’ll rely on daily.
If you haven’t tried it yet, I recommend writing a simple code snippet to fetch data from an API. You can start with a fun API to experiment with. Plus, all the examples and code snippets are available in this repository for you to explore.
The above is the detailed content of Promises in JavaScript: Understanding, Handling, and Mastering Async Code. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

I built a functional multi-tenant SaaS application (an EdTech app) with your everyday tech tool and you can do the same. First, what’s a multi-tenant SaaS application? Multi-tenant SaaS applications let you serve multiple customers from a sing

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.
