JavaScript Promises – The Complete Guide

JavaScript promises are a powerful feature for handling asynchronous operations. They allow you to write cleaner and more manageable code by dealing with tasks like data fetching, file reading, or any other time-consuming operations. This guide will explore everything you need to know about JavaScript promises, including what they are, why they are important, where and how to use them, and when they are most beneficial.

What are JavaScript Promises?

A promise is an object representing the eventual completion or failure of an asynchronous operation. It provides a way to handle asynchronous results in a more predictable and readable manner.

Syntax

Creating a promise:

JavaScript
let promise = new Promise(function(resolve, reject) {
  // async code here
});

Example

JavaScript
let myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Success! 🎉");
  }, 1000);
});

myPromise.then((message) => {
  console.log(message); // Output: Success! 🎉
});

In this example, the promise resolves with a message after 1 second.

Why Use JavaScript Promises?

Promises offer several advantages over traditional callback-based asynchronous programming:

  1. Cleaner Code: Promises help avoid deeply nested callbacks, often referred to as “callback hell.”
  2. Better Error Handling: Promises provide a structured way to handle errors using .catch().
  3. Chaining: Promises allow chaining of multiple asynchronous operations in a readable manner.

Cleaner Code Example

Without promises:

JavaScript
function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;
  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`Script load error for ${src}`));
  document.head.append(script);
}

loadScript('/my/script.js', function(error, script) {
  if (error) {
    console.log(error);
  } else {
    loadScript('/my/secondScript.js', function(error, script) {
      if (error) {
        console.log(error);
      } else {
        // further processing
      }
    });
  }
});

With promises:

JavaScript
function loadScript(src) {
  return new Promise((resolve, reject) => {
    let script = document.createElement('script');
    script.src = src;
    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error(`Script load error for ${src}`));
    document.head.append(script);
  });
}

loadScript('/my/script.js')
  .then(script => loadScript('/my/secondScript.js'))
  .then(script => {
    // further processing
  })
  .catch(error => console.log(error));

Where to Use JavaScript Promises?

Promises can be used in various scenarios to handle asynchronous operations:

  1. Data Fetching: Fetch data from APIs or servers.
  2. File Reading/Writing: Handle file operations.
  3. Event Handling: Wait for events to occur.

Data Fetching Example

JavaScript
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.log('Error:', error));

File Reading/Writing Example

JavaScript
const fs = require('fs').promises;

fs.readFile('example.txt', 'utf8')
  .then(data => console.log(data))
  .catch(error => console.log('Error:', error));

Event Handling Example

HTML
<button id="myButton">Click Me!</button>
<script>
function waitForEvent(element, event) {
  return new Promise((resolve) => {
    element.addEventListener(event, resolve);
  });
}

waitForEvent(document.getElementById('myButton'), 'click').then(() => {
  console.log('Button was clicked! 🖱️');
});
</script>

How to Use JavaScript Promises?

Basic Usage

A promise has three states: pending, fulfilled, and rejected. You can handle these states using .then() for fulfillment and .catch() for rejection.

JavaScript
let promise = new Promise((resolve, reject) => {
  let success = true; // change to false to test rejection
  if (success) {
    resolve('It worked! 🎉');
  } else {
    reject('It failed! 😢');
  }
});

promise
  .then(result => console.log(result))
  .catch(error => console.log(error));

Chaining Promises

You can chain multiple promises to handle sequences of asynchronous operations.

JavaScript
let fetchUser = () => Promise.resolve({ id: 1, name: 'John' });

let fetchPosts = (userId) => Promise.resolve(['Post 1', 'Post 2']);

fetchUser()
  .then(user => {
    console.log(`User: ${user.name}`);
    return fetchPosts(user.id);
  })
  .then(posts => {
    console.log('Posts:', posts);
  })
  .catch(error => console.log(error));

Using Promise.all()

Promise.all() is used to run multiple promises in parallel and wait for all of them to finish.

JavaScript
let promise1 = Promise.resolve(1);
let promise2 = Promise.resolve(2);
let promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results); // Output: [1, 2, 3]
  })
  .catch(error => console.log(error));

Using Promise.race()

Promise.race() returns the first promise that resolves or rejects.

JavaScript
let slowPromise = new Promise((resolve) => {
  setTimeout(() => resolve('Slow Promise 🐢'), 2000);
});

let fastPromise = new Promise((resolve) => {
  setTimeout(() => resolve('Fast Promise 🏃'), 1000);
});

Promise.race([slowPromise, fastPromise])
  .then(result => console.log(result)) // Output: Fast Promise 🏃
  .catch(error => console.log(error));

When to Use JavaScript Promises?

When Handling Asynchronous Operations

Use promises whenever you need to handle asynchronous operations such as fetching data, reading files, or waiting for events.

JavaScript
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

delay(1000).then(() => console.log('1 second delay completed ⏳'));

When Avoiding Callback Hell

Promises help avoid the pyramid of doom created by nested callbacks.

JavaScript
function firstTask() {
  return new Promise(resolve => setTimeout(() => resolve('First Task Done ✅'), 1000));
}

function secondTask() {
  return new Promise(resolve => setTimeout(() => resolve('Second Task Done ✅'), 1000));
}

firstTask()
  .then(result => {
    console.log(result);
    return secondTask();
  })
  .then(result => {
    console.log(result);
  })
  .catch(error => console.log(error));

Creating a Promise Wrapper

You can create a wrapper function that returns a promise.

JavaScript
function asyncOperation(success) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) {
        resolve('Operation succeeded! 🎉');
      } else {
        reject('Operation failed! 😢');
      }
    }, 1000);
  });
}

asyncOperation(true)
  .then(result => console.log(result))
  .catch(error => console.log(error));

Using Promises with async/await

async/await provides a syntactic sugar for working with promises, making the code look synchronous.

JavaScript
async function fetchData() {
  try {
    let response = await fetch('https://api.example.com/data');
    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.log('Error:', error);
  }
}

fetchData();

Promises in Error Handling

Promises provide a structured way to handle errors.

JavaScript
function riskyOperation() {
  return new Promise((resolve, reject) => {
    let success = Math.random() > 0.5;
    if (success) {
      resolve('Operation succeeded! 🎉');
    } else {
      reject('Operation failed! 😢');
    }
  });
}

riskyOperation()
  .then(result => console.log(result))
  .catch(error => console.log(error));

Chaining Multiple Promises

JavaScript
function task1() {
  return new Promise(resolve => setTimeout(() => resolve('Task 1 complete ✅'), 1000));
}

function task2() {
  return new Promise(resolve => setTimeout(() => resolve('Task 2 complete ✅'), 1000));
}

function task3() {
  return new Promise(resolve => setTimeout(() => resolve('Task 3 complete ✅'), 1000));
}

task1()
  .then(result => {
    console.log(result);
    return task2();
  })
  .then(result => {
    console.log(result);
    return task3();
  })
  .then(result => {
    console.log(result);
  })
  .catch(error => console.log(error));

Summary

JavaScript promises are a powerful feature for

managing asynchronous operations. They provide a cleaner, more readable way to handle tasks like data fetching, file reading, and event handling. By understanding and using promises effectively, you can write better, more maintainable JavaScript code. Practice using promises in different scenarios to see their full potential and improve your programming skills.

Leave a Reply