• 5 Better Practices for JavaScript Promises in Real Projects
  • Original post by Bitfish
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: febrainqu
  • Proofreader: NiayyY-S, IAMSHENSH

5 Best Practices for Promises in JavaScript in real projects

After learning the basics of Promise usage, this article hopes to help you make better use of promises in real projects.

Use promise.all, promise.race, and promise.prototype. then to improve code quality.

Promise.all

Promise.all is actually a Promise that takes an array of promises (or an iterable object) as an argument. The callback is then completed when all of the promises change to the Resolved state, or when one of the Promises changes to the Rejected state.

For example, suppose you have ten Promises that perform asynchronous operations on network requests or database connections. You need to know when promises are resolved or when promises are fulfilled. So you make ten promises to fulfill promises.all. Then, once all ten promises switch to the Resolved state or any of them switch to the Rejected state because of an exception, promise.All itself as a promise will switch to the Resolved state.

Let’s understand it in code:

Promise.all([promise1, promise2, promise3])
 .then(result) => {
   console.log(result)
 })
 .catch(error= > console.log(`Error in promises ${error}`))
Copy the code

As you can see, we passed an array to Promise.all. And when all three promises move to the resolved state, promise.all is finished and output to the console.

Let’s look at an example:

// A simple promise that will execute resolve at a given time
const timeOut = (t) = > {
  return new Promise((resolve, reject) = > {
    setTimeout((a)= > {
      resolve(`Completed in ${t}`)
    }, t)
  })
}
// Resolve a normal promise.
timeOut(1000)
 .then(result= > console.log(result)) // Completed in 1000
// Promise.all
Promise.all([timeOut(1000), timeOut(2000)])
 .then(result= > console.log(result)) // ["Completed in 1000", "Completed in 2000"]
Copy the code

In the example above, Promise.all is resolved after 2000ms and outputs the result array on the console.

One interesting thing about promise.all is that the order of promises is fixed. The first Promise in the array is changed to Resolved and output as the first element of the array, the second Promise is changed to Resolved as the second element of the array, and so on.

Ok, so that’s the basic use of promise.all. Let me show you how it’s used in a real project.

1. Synchronize multiple asynchronous requests

In a real project, pages often need to send multiple asynchronous requests to the background. Then wait for the background results to return before rendering the page.

Some programmers might write code like this:

function getBannerList(){
  return new Promise((resolve,reject) = >{
      // Suppose we make an asynchronous request to the server
      setTimeout(function(){
          resolve('BannerList')},300)})}function getStoreList(){
 return new Promise((resolve,reject) = >{
      // Suppose we make an asynchronous request to the server
      setTimeout(function(){
          resolve('StoreList')},500)})}function getCategoryList(){
 return new Promise((resolve,reject) = >{
      // Suppose we make an asynchronous request to the server
      setTimeout(function(){
          resolve('CategoryList')},700)
  })
}

getBannerList().then(function(data){
  // Render data
})
getStoreList().then(function(data){
  // Render data
})
getCategoryList().then(function(data){
  // Render data
})
Copy the code

The above code does work, but there are two flaws:

  • Each time we request data from the server, we need to write a separate function to process the data. This leads to code redundancy and makes future upgrades and extensions difficult.
  • Each request takes a different amount of time, resulting in the function rendering the page three times asynchronously, which makes the user feel like the page is stuck.

Now we can use promise.all to optimize our code.

function getBannerList(){
  // ...
}
function getStoreList(){
  // ...
}
function getCategoryList(){
  // ...
}

function initLoad(){
  Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res= >{
      // Render data
  }).catch(err= >{
      // ...
  })
}
initLoad()
Copy the code

After all requests are completed, we will process the data uniformly.

2. Handle exceptions

In the example above, we apply this approach to exception handling very directly:

Promise.all([p1, p2]).then(res= > {
  // ...
}).catch(error= > {
  // Exception handling
})
Copy the code

As we all know, the promise. all mechanism is that whenever any Promise in the array of promises as parameters throws an exception, the entire promise. all function goes into a catch method regardless of whether any other Promise succeeds or fails.

But in practice, we often need this: even if one or more promises throws an exception, we still want promise.all to continue executing normally. For example, in the example above, even if an exception occurs in getBannerList(), we want the program to continue executing as long as no exception occurs in getStoreList() or getCategoryList().

To meet this requirement, we can use a trick to enhance promise.all. We can write code like this:

Promise.all([p1.catch(error= > error), p2.catch(error= > error)]).then(res= > {
  // ...
}))
Copy the code

This way, if one Promise fails, it does not interrupt the execution of other promises in promise.all.

Applied to the previous example, the result looks like this.

function getBannerList(){
  return new Promise((resolve,reject) = >{
      setTimeout(function(){
          // suppose we reject an exception
          reject(new Error('error'))},300)})}function getStoreList(){
 // ...
}

function getCategoryList(){
 // ...
}


function initLoad(){
  Promise.all([
    getBannerList().catch(err= >err),
    getStoreList().catch(err= >err),
    getCategoryList().catch(err= >err)
  ]).then(res= >{

    if(res[0] instanceof Error) {// Handle exceptions
    } else {
      // Render data
    }

    if(res[1] instanceof Error) {// Handle exceptions
    } else {
      // Render data
    }

    if(res[2] instanceof Error) {// Handle exceptions
    } else {
      // Render data
    }
  })
}

initLoad()
Copy the code

3. Make multiple promises work together

When a user uploads or publishes something, we may need to validate what the user uploaded. For example, check whether the content contains violence, sex, fake news and so on. In most cases, these detection behaviors are performed by different apis provided by the back end or by different cloud capabilities provided by SaaS service providers.

Some programmers might write code like this:

function verify1(content){
  return new Promise((resolve,reject) = >{
      // Suppose we perform an asynchronous operation
      setTimeout(function(){
          resolve(true)},200)})}function verify2(content){
  return new Promise((resolve,reject) = >{
      // Suppose we perform an asynchronous operation
      setTimeout(function(){
          resolve(true)},700)})}function verify3(content){
  // Suppose we perform an asynchronous operation
  return new Promise((resolve,reject) = >{
      setTimeout(function(){
          resolve(true)},300)
  })
}

verify1().then((a)= > {
  verify2().then((a)= > {
    verify3().then((a)= > {
      // The content uploaded by the user has been verified and can be published.
    }).catch((a)= > {
      // The content uploaded by the user is not verified and cannot be published.
    })
  }).catch((a)= > {
    // The content uploaded by the user is not verified and cannot be published.
  })
}).catch((a)= > {
  // The content uploaded by the user is not verified and cannot be published.
})
Copy the code

But using promise.all, we can make different Promise tasks work together:

function verify1(content){
  return new Promise((resolve,reject) = >{
      // Suppose we perform an asynchronous operation
      setTimeout(function(){
          resolve(true)},200)})}function verify2(content){
  return new Promise((resolve,reject) = >{
      // Suppose we perform an asynchronous operation
      setTimeout(function(){
          resolve(true)},700)})}function verify3(content){
  // Suppose we perform an asynchronous operation
  return new Promise((resolve,reject) = >{
      setTimeout(function(){
          resolve(true)},300)})}let content = 'some content'
Promise.all([verify1(content),verify2(content),verify3(content)]).then(result= >{
  // The content uploaded by the user has been verified and can be published.
}).catch(err= > {
  // The content uploaded by the user is not verified and cannot be published.
})
Copy the code

Promise.race

Promise.race takes the same parameters as promise.all and can be either an array of promises or an iterable object.

The promise.race () method returns a Promise object. If one of the promises in the iterator is a pity or Rejected state, the result or error message will be returned.

4. Timing function

When we request resources asynchronously from back-end servers, we usually limit the time. If no data is received within the specified time, an exception is thrown.

Think about it, how would you implement this feature? Promise.race can help us solve this problem.

function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
           resolve(img);
        }
        img.src = "https://www.example.com/a.png";
    });
    return p;
}

// Delay function for timing function
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('Picture request timeout');
        }, 5000);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    // The resource request is completed within the specified time
    console.log(results);
})
.catch(function(reason){
    // The resource request was not completed within the specified time
    console.log(reason);
});

Copy the code

Promise.then

We know that promise.then() always returns a Promise object, so promise.then supports chained calls.

Promise.then().then().then()
Copy the code

5. The chain of Promise

Therefore, if the amount of data returned by the interface is large, and the processing of one of the interfaces seems too large, we can consider accessing the processing logic in sequence in multiple THEN methods and executing:

// Assume this is the data returned by the back end
let result = {
    bannerList: [/ /...].storeList: [/ /...].categoryList: [/ /...]./ /...
}

function getInfo(){
    return new Promise((resolve,reject) = >{
        setTimeout((a)= >{
            resolve(result)
        },500)
    })
}

getInfo().then(res= >{

    let { bannerList } = res

    // Use bannerList for operations
    console.log(bannerList)

    // Return res for the next then method
    return res

}).then(res= >{
    let { storeList } = res
    console.log(storeList)
    return res

}).then(res= >{
    let { categoryList } = res
    console.log(categoryList)
    return res
})
Copy the code

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.