Understanding JavaScript Promises: A Guide for Non-Programmers

Understanding JavaScript Promises: A Guide for Non-Programmers

Promises in JavaScript might sound like a mystical concept, but they're just a way to handle things that might take some time, like fetching data from the web. It's like ordering a pizza: you make the order (start an action) and you're given a time frame. You don’t get your pizza immediately, but you get a promise that it'll come in, say, 20 minutes.

What is a Promise?

In JavaScript, a Promise is an object representing the eventual completion or failure of an asynchronous operation. Asynchronous just means that you can start something (like fetching data) and continue doing other things without waiting for that initial task to complete.

Think of it as if you've set a pot of water to boil. You don't have to stand there and watch it. Instead, you set it, and when it's boiling (done), you can make your tea.

A Promise has three states:

  1. Pending: The initial state. It's neither fulfilled nor rejected.
  2. Fulfilled: The action relating to the promise succeeded.
  3. Rejected: The action relating to the promise failed.

How to Create a Promise?

A basic promise can be created using the Promise constructor:

let myFirstPromise = new Promise((resolve, reject) => {
  // some asynchronous operation
  if (everythingIsFine) {
    resolve('Success!');
  } else {
    reject('Error!');
  }
});

Here, resolve and reject are functions that get called when the operation is successful or fails, respectively.

Handling Promises

After creating a promise, you can specify what should happen when it's successful or when it fails.

Example 1: Basic Promise Usage

myFirstPromise
  .then(result => {
    console.log(result);  // This will print "Success!" if everything was fine
  })
  .catch(error => {
    console.error(error); // This will print "Error!" if there was an issue
  });

Example 2: Simulating a Delay with setTimeout

Here's a promise that simulates a delay using setTimeout:

let delayedHello = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Hello after 2 seconds');
  }, 2000);
});

delayedHello.then(alert);  // This will show an alert with the message after 2 seconds.

Example 3: Using fetch to Get Data from the Web

The fetch function in JavaScript returns a promise. This function fetches data from a URL:

fetch('https://api.example.com/data')
  .then(response => response.json())  // Convert the response to JSON format
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('There was an error fetching data:', error);
  });

Example 4: Chaining Promises

You can chain .then() to transform values or run more async actions one after another:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    return data.length;  // Return the length of data
  })
  .then(length => {
    console.log('Data count:', length);
  });

Example 5: Error Propagation

If a promise in a chain rejects, subsequent .then handlers are skipped until a .catch is found:

new Promise((resolve, reject) => {
    reject('Error occurred!');
})
.then(() => {
    console.log('This will not be called');
})
.catch(error => {
    console.error(error); // This will print "Error occurred!"
});

Example 6: Handling Multiple Promises

Sometimes, you might want to run multiple promises simultaneously. Promise.all() helps with that:

let promise1 = fetch('https://api.example.com/data1');
let promise2 = fetch('https://api.example.com/data2');

Promise.all([promise1, promise2])
  .then(responses => {
    return Promise.all(responses.map(r => r.json()));
  })
  .then(datas => {
    console.log('Data1:', datas[0]);
    console.log('Data2:', datas[1]);
  });

Example 7: The First Promise to Complete

If you want to act on whichever promise completes first, you can use Promise.race():

let promiseFast = new Promise((resolve) => setTimeout(resolve, 100, 'Fast'));
let promiseSlow = new Promise((resolve) => setTimeout(resolve, 500, 'Slow'));

Promise.race([promiseFast, promiseSlow]).then(result => {
  console.log(result); // Will print "Fast"
});

Example 8: Creating an Immediately Resolved Promise

Sometimes, for testing or other reasons, you might want a promise that resolves immediately:

Promise.resolve('Instant').then(console.log); // Will print "Instant"

Example 9: Creating an Immediately Rejected Promise

Similarly, you can create a promise that rejects immediately:

Promise.reject('Immediate Error').catch(console.error); // Will print "Immediate Error"

Example 10: Handling Finally

Regardless of success or failure, you can use .finally to run some code:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('There was an error fetching data:', error);
  })
  .finally(() => {
    console.log('Finished fetching data.');
  });

In Conclusion

Promises in JavaScript make handling asynchronous tasks simpler and more readable. They allow you to perform tasks without waiting for previous tasks to complete, making your programs more efficient. As you can see from the examples above, promises offer a structured approach to handle both the success and failure cases, giving developers better control over the flow of their programs.

More to read