This time, understand Promise thoroughly

Posted by tomms on Wed, 02 Mar 2022 06:33:21 +0100

1, Why introduce Promise

Before introducing this chapter, first raise a few questions:

  • What problem did Promise solve?
  • What are the specific usage scenarios for Promise?

What problem did Promise solve?

  1. Callback hell problem

Before Promise, the front end often needs to solve the asynchronous problem by nesting callback functions layer by layer to obtain data. For example, the following code example:

// Callback hell instance

// Milk tea function
function getTea(fn) {
  setTimeout(() => {
    fn('Get a cup of milk tea')
  },2000)
}

// Bread function
function getBread(fn) {
  setTimeout(() => {
    fn('Get a bread')
  },100)
}

// If it must be obtained in order, rather than according to time, it is required to obtain milk tea first and then bread.
getTea(function(data) {
  console.log(data);
  getBread(function(data) {
    console.log(data);
  }) 
})
  1. readability issues

Through Promise, we can rewrite the above code into the following way, which is obviously more readable.

// Let's explain how to solve the problem of callback hell through Promise
function getTea() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Get a cup of milk tea')
    }, 2000)
  })
}

function getBread() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Get a bread')
    }, 500)
  })
}

getTea()
  .then(res => {
    console.log(res);
    return getBread();
  })
  .then(res => {
    console.log(res);
  })
  1. Trust problem (also known as callback multiple execution problem)

The traditional callback function cannot be guaranteed to be executed only once, and the callback function may also be used to perform other operations. However, Promise calls and only calls resolve once, which will not cause the problem of callback executing multiple times. Therefore, Promise solves the problem of callback calling multiple times by a third-party library.

What are the specific usage scenarios for Promise?

  • Scenario 1: write the picture loading as a Promise. Once the picture is loaded, the Promise state will change.
  • Scenario 2: when the next asynchronous request needs to rely on the result of the previous request, the problem can be solved through chain operation.
  • Scenario 3: merge multiple requests through all() and summarize all the request results. Just set one loading.
  • Scenario 4: you can set the timeout of image request through race().

2, The method of handwritten Prromise

Handwritten promise all

Promise.all is characterized by receiving an iteratable object. When all elements in the iteratable object are successfully executed, an array will be returned, and an error will be returned immediately.

function myPromiseAll(iterable) {
  // First, specify that the object to be returned is a Promise
  return new Promise((resolve,reject) => {
    // First, convert the iteratable object into an array
    const promises = Array.from(iterable);
    let flag = 0;
    const result = [];
    // Start traversal execution
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(res => {
        result[i] = res;
        flag++;
        if (flag === promises.length) {
          resolve(result)
        }
      }).catch(err => {
        reject(err)
      })
    }
  })  
}

Handwritten promise race

Promise. The race function receives an iteratable object, which is equivalent to racing all promise objects in the iteratable object. As long as the state of one promise object changes, it directly returns the result returned by the promise object.

// Handwritten promise race
function myPromiseRace(iterator) {
  // The first thing to return is a promise object
  return new Promise((resolve,reject) => {
    for (let item of iterator) {
      Promise.resolve(item).then(res => {
        resolve(item);
      }).catch(err => {
        reject(err);
      })
    }
  })
}

let p1 = new Promise(resolve => {
  setTimeout(resolve, 105, 'p1 done')
})
let p2 = new Promise(resolve => {
  setTimeout(resolve, 100, 'p2 done')
})
myPromiseRace([p1, p2]).then(data => {
  console.log(data); // p2 done
})

Handwritten promise finally

Promise.finally features

  • This method is executed regardless of success or failure
  • A Promise is returned

Promise.finally execution example

let p = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve(111);
  },2000)
})

p.then(res => {
  console.log(res);  // 111
}).finally(() => {
  console.log('It will be executed here anyway');  // It will be executed here anyway
})

Handwritten promise Finally (Promise.finally is essentially a then method, which needs to execute the parameters we passed in and then return the formal parameters)

Promise.prototype.finally = function(f) {
  return this.then((value) => {
    return Promise.resolve(f()).then(() => value)
  },(err) => {
    return Promise.resolve(f()).then(() => {
      throw err;
    })
  })
}

Promise.all and promise The difference between race

Promise. The return values of all() success and failure are different. When successful, it returns a result array, while when failed, it returns the first reject ed value. When promise When the result of all() is successful, return the data order and promise in the array of the result The promise order received by all() is consistent.

promise.race means multiple Promise races. Which result will be returned as soon as it is executed. No matter whether the result itself is successful or failed, other Promise codes will be executed, but will not be returned.

Promise.all and promise Application scenario of race

promise. Application scenario of all()

  • The scene to display when multiple asynchronous tasks get results

For example, when the user clicks the button, a dialog box will pop up. The data in this dialog box comes from the data obtained by two different back-end interfaces. When the user just clicks, the state in data loading will be displayed. When these two parts of data are obtained from the interface, the state in data loading will disappear. At this time, promise can be used All method.

Promise. Application scenario of race()

  • Prompt user request timeout

For example, when the user clicks the button to send a request, and the back-end interface has not obtained data beyond the time we set, we can prompt the user that the request times out.

3, How does Promise solve serial and parallel problems?

What is parallelism? What is serial?

Parallel: refers to the simultaneous execution of multiple asynchronous requests.

Serial: an asynchronous request is completed before the next request is made.

Promise implements parallel requests

Promise mainly relies on promise All method and promise Race method, we can promise by handwriting All method or promise Race method to achieve this goal.

Promise implements serial requests

Promise realizes serial request mainly with the help of reduce function. You can refer to my article How to control the serial execution of Promise?

// The serial execution of Promise is realized with the help of reduce function
const funcArr = [
  () => {
    return new Promise((resolve) => {
      setTimeout(() => {resolve(1)},2000)
    })
  },
  () => {
    return new Promise((resolve) => {
      setTimeout(() => {resolve(2)},1000)
    })
  },
  () => {
    return new Promise((resolve) => {
      setTimeout(() => {resolve(3)},3000)
    })
  },
];

function inOrder(arr) {
  const res = [];
  return new Promise((resolve) => {
    arr.reduce((pre,cur) => {
      return pre.then(cur).then(data => res.push(data))
    },Promise.resolve()).then(data => resolve(res))
  })
}

inOrder(funcArr).then(data => console.log(data))   // [1,2,3]

4, What is Promise penetration?

The so-called Promise value penetration refers to then or The parameter of catch is expected to be a function. If it is not a function, value penetration may occur. The Promise method passes values through return. Without return, it's just independent tasks. Taking a look at the output of the following example may better help us understand what value penetration is?

Promise.resolve(1)
  .then(function(){return 2})
  .then(Promise.resolve(3))
  .then(console.log)   // 2

The reason why value penetration occurs is that what is passed in the second then is not in the form of a function.

5, Encapsulating Ajax requests with Promise

The key steps of using Promise to encapsulate Ajax requests are all in the comments in the following code. Please see the following code for details.

// Encapsulating Ajax requests with Promise
const res = new Promise((resolve,reject) => {
  // 1. Create an XMLHttpRequest object
  const xhr = new XMLHttpRequest();
  // 2. Initialization request method and URL
  xhr.open('GET','https://api.apiopen.top/getJoke');
  // 3. Send request
  xhr.send();
  // 4. Bind the event and process the response result
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      // Here 4 means that the server returns all the results
      // If the status code returned by the server starts with 2, we will resolve the returned result. Otherwise, we will reject the corresponding status code
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.response)
      } else {
        reject(xhr.status)
      }
    }
  }
})
res.then(function(value) {
  console.log(value);
},function(err) {
  console.log(err);
})

6, What are the states of Promise?

Promise mainly has the following three states:

  • pending state (initial state)
  • Completed status
  • rejected status (status that has failed)

Promise state change process

  1. Switching from pending to fulfilled state

Before resolve, it is in pending state, and after resolve, it is in full state

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('resolve Status before:', p);
    resolve();
    console.log('resolve Status after', p);
  })
})

  1. From pending state to rejected state

The status before reject is pending, and the status after reject is rejected.

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('reject Status before:', p);
    reject();
    console.log('reject Status after', p);
  })
})

7, Rewrite callback to Promise

  1. Traditional callback form

    const fs = require('fs');

    fs.readFile('./temp.md',(err,data) => {
    console.log(data.toString());
    })

  2. Change the form of callback to promise

The core is to obtain callback data through resolve.

const fs = require('fs');

async function myReadFile() {
  let result = await new Promise((resolve,reject) => {
    fs.readFile('./temp.md',(err,data) => {
      resolve(data.toString());
    })
  })
  console.log(result);   // xxxxx
  return result;
}

myReadFile()

Topics: Javascript Front-end Vue.js html