Javascript: Promise Complete Guide

Luke - Aug 25 - - Dev Community

What is Promise?

Promise is a new solution for asynchronous programming. ES6 has incorporated it into the language standard, unifying its usage and natively providing the Promise object.

Its introduction greatly improved the predicament of asynchronous programming, avoiding callback hell. It is more reasonable and powerful than traditional solutions like callback functions and events.

A Promise, simply put, is a constructor that holds the result of an event that will complete in the future (usually an asynchronous operation). Syntactically, a Promise is an object from which asynchronous operation messages can be retrieved. Promise provides a unified API, allowing various asynchronous operations to be handled in the same way.

Why should we use Promise?

  • It can effectively solve the callback hell problem in ES5 (avoiding deeply nested callback functions).

  • It follows a unified standard, with concise syntax, strong readability, and maintainability.

  • The Promise object provides a simple API, making it more convenient and flexible to manage asynchronous tasks.

Promise's States

When using a Promise, we can classify it into three states:

  1. pending: Pending. This is the initial state, where the Promise is neither fulfilled nor rejected.

  2. fulfilled: Fulfilled/Resolved/Successful. When resolve() is executed, the Promise immediately enters this state, indicating that it has been resolved and the task was successfully completed.

  3. rejected: Rejected/Failed. When reject() is executed, the Promise immediately enters this state, indicating that it has been rejected and the task has failed.

When new Promise() is executed, the state of the promise object is initialized to pending, which is its initial state. The content inside the parentheses of the new Promise() line is executed synchronously. Within the parentheses, you can define a function for an asynchronous task, which has two parameters: resolve and reject. For example:

// Create a new promise
const promise = new Promise((resolve, reject) => {
  //promise's state is pending as soon as entering the function
  console.log('Synchronous Operations');
  //Begin to execute asynchronous operations
  if (Success) {
    console.log('success');
    // If successful, execute resolve() to switch the promise's state to Fulfilled
    resolve(Success);
  } else {
    // If failed, excecute reject() to pass the error and switch the state to Rejected
    reject(Failure);
  }
});
console.log('LukeW');

//Execute promise's then():to manage success and failure situations
promise.then(
  successValue => {
    // Process promise's fulfilled state
    console.log(successValue, 'successfully callback'); // The successMsg here is the Success in resolve(Success)
  },
  errorMsg => {
    //Process promise's rejected state
    console.log(errorMsg, 'rejected'); // The errorMsg here is the Failure in reject(Failure)
  }
);
Enter fullscreen mode Exit fullscreen mode

Basic Usage of Promise

Create a new Promise Object

The Promise constructor takes a function as its parameter, which has two arguments: resolve and reject.

const promise = new Promise((resolve, reject) => {
  // ... some code
  if (/* Success */){
    resolve(value);
  } else {
    reject(error);
  }
});
Enter fullscreen mode Exit fullscreen mode

Promise.resolve

The return value of Promise.resolve(value) is also a promise object, which can be chained with a .then call. The code is as follows:

Promise.resolve(11).then(function(value){
  console.log(value); // print 11
});

Enter fullscreen mode Exit fullscreen mode

In the resolve(11) code, it will cause the promise object to transition to the resolved state, passing the argument 11 to the onFulfilled function specified in the subsequent .then. A promise object can be created using the new Promise syntax, or by using Promise.resolve(value).

Promise.reject

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};

testPromise(true).then(function(msg){
  console.log(msg);
},function(error){
  console.log(error);
});

Enter fullscreen mode Exit fullscreen mode

The meaning of the above code is to pass an argument to the testPromise method, which returns a promise object. If the argument is true, the resolve() method of the promise object is called, and the parameter passed to it is then passed to the first function in the subsequent .then, resulting in the output "hello world." If the argument is false, the reject() method of the promise object is called, which triggers the second function in the .then, leading to the output "No thanks."

Methods in Promise

then()

The then method can accept two callback functions as parameters. The first callback function is called when the Promise object's state changes to resolved, and the second callback function is called when the Promise object's state changes to rejected. The second parameter is optional and can be omitted.

The then method returns a new Promise instance (not the original Promise instance). Therefore, a chained syntax can be used, where another then method is called after the first one.

When you need to write asynchronous events in sequence, requiring them to be executed serially, you can write them like this:

let promise = new Promise((resolve,reject)=>{
    ajax('first').success(function(res){
        resolve(res);
    })
})
promise.then(res=>{
    return new Promise((resovle,reject)=>{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res=>{
    return new Promise((resovle,reject)=>{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res=>{

})

Enter fullscreen mode Exit fullscreen mode
catch()

In addition to the then method, a Promise object also has a catch method. This method is equivalent to the second parameter of the then method, pointing to the callback function for reject. However, the catch method has an additional function: if an error occurs or an exception is thrown while executing the resolve callback function, it will not stop the execution. Instead, it will enter the catch method.

p.then((data) => {
     console.log('resolved',data);
},(err) => {
     console.log('rejected',err);
     }
); 
p.then((data) => {
    console.log('resolved',data);
}).catch((err) => {
    console.log('rejected',err);
});

Enter fullscreen mode Exit fullscreen mode
all()

The all method can be used to complete parallel tasks. It takes an array as an argument, where each item in the array is a Promise object. When all the Promises in the array have reached the resolved state, the state of the all method will also become resolved. However, if even one of the Promises changes to rejected, the state of the all method will become rejected.

let promise1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(3);
    },3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{
    console.log(res);
    //result:[1,2,3] 
})

Enter fullscreen mode Exit fullscreen mode

When the all method is called and successfully resolves, the result passed to the callback function is also an array. This array stores the values from each Promise object when their respective resolve functions were executed, in the order they were passed to the all method.

race()

The race method, like all, accepts an array where each item is a Promise. However, unlike all, when the first Promise in the array completes, race immediately returns the value of that Promise. If the first Promise's state becomes resolved, the race method's state will also become resolved; conversely, if the first Promise becomes rejected, the race method's state will become rejected.

let promise1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       reject(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(3);
    },3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{
    console.log(res);
    //result:2
},rej=>{
    console.log(rej)};
)

Enter fullscreen mode Exit fullscreen mode

So, what is the practical use of the race method? When you want to do something, but if it takes too long, you want to stop it; this method can be used to solve that problem:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

Enter fullscreen mode Exit fullscreen mode
finally()

The finally method is used to specify an operation that will be executed regardless of the final state of the Promise object. This method was introduced in the ES2018 standard.

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

Enter fullscreen mode Exit fullscreen mode

In the code above, regardless of the final state of the promise, after the then or catch callbacks have been executed, the callback function specified by the finally method will be executed.

What exactly does Promise solve?

In work, you often encounter a requirement like this: for example, after sending an A request using AJAX, you need to pass the obtained data to a B request if the first request is successful; you would need to write the code as follows:

let fs = require('fs')
fs.readFile('./a.txt','utf8',function(err,data){
  fs.readFile(data,'utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){
      console.log(data)
    })
  })
})

Enter fullscreen mode Exit fullscreen mode

The above code has the following drawbacks:

  • The latter request depends on the success of the previous request, where the data needs to be passed down, leading to multiple nested AJAX requests, making the code less intuitive.

  • Even if the two requests don't need to pass parameters between them, the latter request still needs to wait for the success of the former before executing the next step. In this case, you also need to write the code as shown above, which makes the code less intuitive.

After the introduction of Promises, the code becomes like this:

let fs = require('fs')
function read(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,'utf8',function(error,data){
      error && reject(error)
      resolve(data)
    })
  })
}
read('./a.txt').then(data=>{
  return read(data) 
}).then(data=>{
  return read(data)  
}).then(data=>{
  console.log(data)
})

Enter fullscreen mode Exit fullscreen mode

This way, the code becomes much more concise, solving the problem of callback hell.

. . . .
Terabox Video Player