WPDD

WordPress Develop & Design

Promise

What is a Promise?

A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It can be in one of three states:

  1. Pending: The initial state. The operation is still in progress.
  2. Fulfilled: The operation completed successfully, and the promise has a resolved value.
  3. Rejected: The operation failed, and the promise has a reason for the failure (usually an error).

Creating a Promise

You can create a promise using the Promise constructor, which takes a function (called the executor) with two parameters: resolve and reject.

const myPromise = new Promise((resolve, reject) => {
  // Asynchronous operation
  setTimeout(() => {
    const success = true; // Simulate success or failure
    if (success) {
      resolve("Operation successful!");
    } else {
      reject("Operation failed!");
    }
  }, 1000);
});
  • resolve(value): Called when the operation succeeds. The value is the result of the operation.
  • reject(reason): Called when the operation fails. The reason is typically an error object or message.

Consuming a Promise

Once a promise is created, you can handle its result using .then(), .catch(), and .finally().

  1. .then(onFulfilled, onRejected): Attaches callbacks for when the promise is fulfilled or rejected.
    • onFulfilled: A function to handle the resolved value.
    • onRejected: A function to handle the rejection reason (optional).
  2. .catch(onRejected): A shorthand for handling only rejections.
  3. .finally(onFinally): Executes a callback when the promise is settled (either fulfilled or rejected). Useful for cleanup.
myPromise
  .then((result) => {
    console.log(result); // "Operation successful!"
  })
  .catch((error) => {
    console.error(error); // "Operation failed!"
  })
  .finally(() => {
    console.log("Promise settled."); // Runs regardless of success or failure
  });

Chaining Promises

Promises can be chained to perform sequential asynchronous operations. Each .then() returns a new promise, allowing you to chain multiple .then() calls.

const fetchData = () => {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Data fetched"), 1000);
  });
};

const processData = (data) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${data} and processed`), 1000);
  });
};

fetchData()
  .then((data) => processData(data))
  .then((result) => {
    console.log(result); // "Data fetched and processed"
  })
  .catch((error) => {
    console.error(error);
  });

Promise Static Methods

ES6+ provides several static methods on the Promise object:

  1. Promise.resolve(value): Returns a promise that is resolved with the given value.
  2. Promise.reject(reason): Returns a promise that is rejected with the given reason.
  3. Promise.all(iterable): Waits for all promises in the iterable to resolve, or rejects if any promise rejects.
  4. Promise.race(iterable): Waits for the first promise in the iterable to settle (either resolve or reject).
  5. Promise.allSettled(iterable): Waits for all promises to settle, regardless of whether they resolve or reject.
  6. Promise.any(iterable): Waits for the first promise to resolve, or rejects if all promises reject.
// Example of Promise.all
const promise1 = Promise.resolve("First");
const promise2 = new Promise((resolve) => setTimeout(resolve, 1000, "Second"));
const promise3 = Promise.reject("Error");

Promise.all([promise1, promise2])
  .then((values) => {
    console.log(values); // ["First", "Second"]
  })
  .catch((error) => {
    console.error(error); // Not called
  });

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values); // Not called
  })
  .catch((error) => {
    console.error(error); // "Error"
  });

Async/Await (Syntactic Sugar for Promises)

ES8 introduced async and await, which make working with promises even more intuitive. An async function automatically returns a promise, and await pauses the execution of the function until the promise is settled.

const fetchData = () => {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Data fetched"), 1000);
  });
};

const processData = async () => {
  try {
    const data = await fetchData();
    console.log(data); // "Data fetched"
  } catch (error) {
    console.error(error);
  }
};

processData();

Summary

  • Promises represent asynchronous operations and have three states: pending, fulfilled, and rejected.
  • Use .then(), .catch(), and .finally() to handle promise results.
  • Chain promises for sequential asynchronous tasks.
  • Use static methods like Promise.all, Promise.race, etc., for advanced use cases.
  • async/await provides a cleaner syntax for working with promises.

Promises are a powerful tool for managing asynchronous code in JavaScript, making it easier to write and maintain complex workflows.

Examples

Example 1: Fetching Data from an API

One of the most common use cases for Promises is making HTTP requests to an API using the fetch function.

// Fetch data from a public API
fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then((response) => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json(); // Parse the JSON data
  })
  .then((data) => {
    console.log('Post:', data); // Log the fetched data
  })
  .catch((error) => {
    console.error('Error:', error); // Handle any errors
  });

Explanation:

  1. fetch returns a Promise that resolves to the Response object.
  2. The first .then() checks if the response is OK and parses the JSON data.
  3. The second .then() logs the parsed data.
  4. .catch() handles any errors that occur during the process.

Example 2: Simulating a Database Query

Promises are often used to simulate asynchronous operations like querying a database.

// Simulate a database query
const queryDatabase = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { id: 1, name: 'John Doe', email: '[email protected]' };
      const error = Math.random() > 0.5; // Simulate a random error
      if (error) {
        reject('Database query failed!');
      } else {
        resolve(data);
      }
    }, 1000); // Simulate a 1-second delay
  });
};

// Use the queryDatabase function
queryDatabase()
  .then((user) => {
    console.log('User:', user); // Log the user data
  })
  .catch((error) => {
    console.error('Error:', error); // Handle the error
  });

Explanation:

  1. queryDatabase simulates a database query that takes 1 second to complete.
  2. It randomly succeeds or fails to demonstrate error handling.
  3. .then() handles the resolved data, and .catch() handles the rejection.

Example 3: Chaining Promises

Promises can be chained to perform sequential asynchronous tasks.

// Simulate fetching user data and then their posts
const fetchUser = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: 1, name: 'Alice' });
    }, 1000);
  });
};

const fetchPosts = (userId) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }]);
    }, 1000);
  });
};

// Chain the promises
fetchUser()
  .then((user) => {
    console.log('User:', user);
    return fetchPosts(user.id); // Return a new promise
  })
  .then((posts) => {
    console.log('Posts:', posts);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

Explanation:

  1. fetchUser simulates fetching user data.
  2. Once the user data is resolved, fetchPosts is called with the user’s ID.
  3. The second .then() logs the posts.
  4. .catch() handles any errors in the chain.

Example 4: Using Promise.all

Promise.all is useful when you need to wait for multiple asynchronous operations to complete.

// Simulate multiple API calls
const fetchUser = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: 1, name: 'Bob' });
    }, 1000);
  });
};

const fetchPosts = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }]);
    }, 1500);
  });
};

// Wait for both promises to resolve
Promise.all([fetchUser(), fetchPosts()])
  .then(([user, posts]) => {
    console.log('User:', user);
    console.log('Posts:', posts);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

Explanation:

  1. Promise.all takes an array of promises and waits for all of them to resolve.
  2. The .then() callback receives an array of results in the same order as the input promises.
  3. If any promise rejects, .catch() handles the error.

Example 5: Using async/await with Promises

async/await provides a cleaner way to work with Promises.

// Simulate an asynchronous operation
const fetchData = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Data fetched!');
    }, 1000);
  });
};

// Use async/await to handle the promise
const processData = async () => {
  try {
    const data = await fetchData(); // Wait for the promise to resolve
    console.log(data); // Log the result
  } catch (error) {
    console.error('Error:', error); // Handle errors
  }
};

processData();

Explanation:

  1. fetchData simulates an asynchronous operation.
  2. processData is an async function that uses await to wait for the promise to resolve.
  3. try/catch handles errors gracefully.

Example 6: Real-World File Reading with fs.promises

In Node.js, the fs.promises module provides Promise-based file system operations.

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

// Read a file asynchronously
fs.readFile('example.txt', 'utf8')
  .then((data) => {
    console.log('File content:', data);
  })
  .catch((error) => {
    console.error('Error reading file:', error);
  });

Explanation:

  1. fs.promises.readFile reads a file and returns a Promise.
  2. The .then() callback logs the file content.
  3. .catch() handles any errors, such as the file not existing.

Example 7: Real-World API Call with axios

axios is a popular library for making HTTP requests and uses Promises under the hood.

const axios = require('axios');

// Fetch data from an API
axios.get('https://jsonplaceholder.typicode.com/posts/1')
  .then((response) => {
    console.log('Post:', response.data);
  })
  .catch((error) => {
    console.error('Error:', error.message);
  });

Explanation:

  1. axios.get returns a Promise that resolves to the API response.
  2. The .then() callback logs the response data.
  3. .catch() handles any errors, such as network issues.

These examples demonstrate how Promises are used in real-world scenarios, from fetching data to handling file operations and more. They provide a clean and structured way to manage asynchronous code in JavaScript.