This article summary

Promise is very commonly used in work and often asked in interviews. In this paper, I will re-learn Promise from the following aspects to have a deeper understanding of it and help us to face work and interview more easily.

  1. Promise/A+Normative interpretation
  2. The es6Promsieintroduce
  3. Implement a matchPromsie/A+Specification of thePromsieAnd some important new methods in ES6
  4. conclusion

Promise specification

Before Promise, asynchronous JS has been relying on the callback function to achieve, when multiple asynchronous operations have a dependency relationship, there will be multi-layer nesting, so many asynchronous operations form a strong coupling, affect the whole, it is difficult to maintain, this is the “callback function hell”.

The Promise specification was first proposed and implemented by the community. CommonJS successively proposed the Promise/A, Promise/B and Promise/D specifications. Later, an organization named Promise/A+ modified and supplemented the Promise/A specification based on the Promise/A specification. The Promise/A+ specification was developed. The Promise implementation in ES6 is based on this specification.

The Promise/A+ specification is divided into terms and requirements. Let’s take A look at the core of the specification.

  1. The term

    Promise an object or function that contains compliant specification then methods. Thenable An object or function that contains the THEN method. Value Any Javascript value. Exception is the value thrown by the throw expression. Reason is a value that describes why a Promise was rejected.

  2. requirements

    A Promise must be one of the following states: Pending, depressing or Rejected. The state can only be transferred from Pending to fulfilled or Rejected, and the state cannot be changed again. The state must have a value when switching from pending to depressing. The state must have a reason when switching from pending to Rejected.

    2.2 then method

    A Promise must provide a THEN method to get its value or reason. The then method takes two arguments:

    promise.then(onFulfilled, onRejected)
    Copy the code
    1. OnFulfilled and onRejected are both optional: If onFulfilled or onRejected is not a function, just ignore it

    2. If ondepressing is a function, it must be called after the state becomes depressing and cannot be called more than once, and the promise’s value is its first parameter. If onRejected is a function, it must be called after the state has changed to Rejected and cannot be called more than once. The reason of the promise is its first argument.

    3. For a promise, its then method can be called multiple times. After the status fulfilled, all onFulfilled must be performed in the order of their registration. When the status Rejected is entered, all OnRejected must be executed in the order in which they are registered.

The promise es6

Let’s take a look at es6’s promise implementation with reference to the Promise specification.

In ES6, the Promise object is a constructor that produces a Promise instance. The Promise constructor takes a function as an argument, resolve and reject. Resolve: called when the status changes from Pending to Resolved and the result of the asynchronous operation is taken as an argument. Reject: Called when the state changes from pending to Rejected, taking the error reported by the asynchronous operation as an argument.

const promise = new Promise(function(resolve, reject) {
  // ... some code

  ifResolve (value); }else{ reject(error); }});Copy the code

Then method

The then method is defined on promise. prototype. After the Promise instance is generated, the then method can be used to specify the resolved state callback function and the Rejected state callback function. The callback function of the Rejected state (the second argument) is optional.

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
Copy the code

The then method returns a new Promise instance, so you can use the chain notation.

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 1);
  });
}

timeout(5000)
.then((value) => {
  console.log(value);
  return 2
})
.then((value) => {
	console.log(value)
})
.then((value) => {
	console.log(value)
})

// 1
// 2
// undefined
Copy the code

The code above takes the chained form of then, in which the callbacks are called in order. After the first callback completes, the second callback is passed the return value 2 as an argument, so the second callback prints 2. Since the second callback has no return value, the third callback prints undefined.

Catch method

The catch method is also defined on promise.prototype. Rejection is an alias of. Then (null, Rejection) or. Then (undefined, Rejection) that specifies the callback when an error occurs.

$.post("/path1")
.then(() => {
    return $.post("/path2")
})
.then(() => {
    return $.post("/path3")
})
.catch((err) => {
    console.log(err)
})
Copy the code

In the above code, the callback in the catch method is triggered when the status changes to Rejected if an asynchronous operation fails, and the catch method is also executed if the code fails. An error in a Promise object is always caught by the next catch, which means that any one of the three promises in the code above can be caught with an error.

The catch method is not specified in the Promise/A+ specification, and some of the following methods are not specified in Promise/A+, but are new to ES6.

When used, it is better to use the catch method instead of the second argument to the THEN method to handle exception cases. First, catch catches execution errors in all of the above THEN methods. Second, using the catch method is closer to synchronous writing.

resolve()

Turn an existing object into a Promise object

reject()

The promise.Reject (Reason) method also returns a new Promise instance with a state of Rejected.

The finally method

The ES2018 standard was introduced to specify actions that will be performed regardless of the final state of a Promise object.

The finally method does not depend on the result of the Promise’s execution, and its callback takes no arguments.

Promise. Then (result = > {...}). The catch (error = > {...}), finally (() = > {...});Copy the code

The code above will eventually execute the callback in the finally method, no matter how the promise state changes.

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}

timeout(100).then((value) => {
  console.log(value);
});
Copy the code

promise.all

The promise.all () method wraps multiple Promise instances into a new Promise instance.

const p = Promise.all([p1, p2, p3]);
Copy the code

In the code above, P1, P2, and P3 are all promise instances. The state of the packaged Promise instance is determined by P1, P2, and P3 in two cases.

  1. Only when the states of P1, P2 and p3 are all fulfilled, the state of P will become fulfilled. At this time, the parameters passed to the callback function of P will be the return values of P1, P2 and P3 to form an array.

  2. If P1, P2, or P3 is rejected, p changes to Rejected, and the callback passed to P is the return value of the first reject instance.

Promise.race

The promise.race () method also wraps multiple Promise instances into a new Promise instance.

const p = Promise.race([p1, p2, p3]);
Copy the code

In the above code, the state of P changes whenever the state of any instance of P1, P2, or P3 changes. The return value of the first state change Promise instance is passed as an argument to p’s callback.

Promise.allSettled

The ES2020 standard is introduced, which also accepts a set of Promise instances as parameters and wraps them into a new Promise instance.

const p = Promise.allSettled([p1, p2, p3]);
Copy the code

In the code above, the state of instance P will change only after all the states of P1, P2 and P3 change, no matter the state is fulfilled or Rejected.

Promise.any

The approach is currently a phase iii proposal. The method also takes a set of Promise instances as parameters and wraps them into a new Promise instance.

const p = Promise.any([p1, p2, p3]);
Copy the code

As long as any one of parameters P1, P2 and P3 becomes the depressing state, the packaged instance P will become the depressing state. If all parameter instances become the Rejected state, the wrapper instance becomes the Rejected state.

Write a Promise

Below is a step by step implementation of Promsie. The purpose is not to fully implement a Promise with the same functionality as ES6, but to understand the principle of Promise more deeply by writing a Promise by hand. Let’s start by implementing A Promsie that complies with the Promise/A+ specification.

Utility function definition

Start by defining the promise’s state constants and some utility functions

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'/ / tools function, determine whether for function const isFunction = fn = > Object. The prototype. ToString. Call (fn) = = ='[object Function]'Const isPending = status => status === pending Const isPromise = target => target instanceof PromiseCopy the code

The Promise class accepts a function that takes resolve and reject, which are also methods.

Promise class to create

Class Promise {constractor(executor){// The Promise argument must be a function, or throw an error if it is notif(! isFunction(executor)){ throw new Error('Promise must accept a function as a parameter'} this._status = PENDING // Initialize the status value as PENDING this._value = undefined // Set the default value for value this._reason = undefined // // Execute the executor method. Resole, Try {executor(this._resolve.bind(this), This._reject. Bind (this))} catch (err) {// executor calls rejected method this._reject(err)}}, _resolve(value){const {_status: status} = thisif(isPending(status)) {// State can only be changed from pending to two other states, This._status = FULFILLED // This._status = FULFILLED // This. Reject (reason){const {_status: status} = thisif(isPending(status)){// The status can only be changed from pending to two other states and can only be changed once this._reason = reason this._status = REJECTED}}}Copy the code

Then method implementation

Before implementing the THEN method, I encourage you to read the THEN section of the Promise/A+ specification above to better understand the code.

The then method accepts two parameters, onFulfilled and onRejected. In addition, the then method must return a Promise object, which can be called many times by the same promise object. When the promise is successful, all onFulfilled shall be called back successively according to its registration order. When the promise fails, All onRejected must be called back in the order in which they are registered.

Add two arrays to constractor, ledqueues and rejectedQueues, which are used to maintain callbacks for each THEN registration.

This._rejectedQueues = [] // Add failed callbacksCopy the code
// thenMethod implementationthen(onpity, onRejected) {// this is a pitythenMethod returns a promisereturnNew Promise((resolve, reject) => {// The function to execute on successletThis is a big pity. (value) => {try {// If onforget is not a function, ignore thisif(isFunction(ondepressing)) {const result = ondepressing (value) // If the current callback function returns a promise instance, wait until its state changes before executing the next callbackif (isPromise(result)) {
                    result.then(resolve, reject)
                } else{// If the current callback does not return a Promise instance, execute the next onethenResolve (result)}}} catch (err) {// If the new promise fails, the new promise state changes to Rejected, reject (reject). Reject (err)}} // The same function is executed on successletRejectedHandle = (reason) => {try {// If ondepressing is not a function, ignore itif (isFunction(onRejected)) {
                	const result = onRejected(reason)
                    if (isPromise(result)) {
                        result.then(resolve, reject)
                    } else{ reject(reason) } } } catch (err) { reject(err) } } const { _value: value, _status: status, _reason: Reason, _is_ledqueues: Is_ledqueues, _rejectedQueues: rejectedQueues} = This switch (status) {// When the status is pending, this switch will be executedthenThe method callback is queued for executioncase PENDING:
        fulfilledQueues.push(fulfilledHandle)
        rejectedQueues.push(rejectedHandle)
        break// When the state is depressing, directly perform the ondepressing methodcase FULFILLED:
        fulfilledHandle(value)
        break// Execute the onRejected method when the status is Rejectedcase REJECTED:
        rejectedHandle(reason)
        break}})}Copy the code

Implement chain calls to THEN

To implement chained calls to the THEN methods, we need to modify the _resolve and _reject methods under constractor to execute the functions in the success/failure task queue in turn.

 _resolve(value) {
        const { _status: status } = this
    	const resolveFun = () => {
    	if(isPending(status)) {// State can only be changed from pending to two other states, // Save the parameters passed by the resove method. This._status = thislet cb
            while(cb = this._implemented ledqueues. Shift ()) {cb(value)}}} // PasssetTimeout 0 simulates the synchronization promise casesetTimeout(resolveFun, 0)
    }

    _reject(reason) {
        const { _status: status } = this
        const rejectFun = () => {
        if(isPending(status)) {// The status can only be changed from pending to two other states and can only be changed once this._reason = reason this._status = REJECTEDlet cb
            while (cb = this._rejectedQueues.shift()) {
                cb(reason)
            }
        }
        }
        setTimeout(rejectFun, 0)
    }
Copy the code

This allows us to implement A Promise that conforms to the PROMsie /A+ specification.

Here are some more ways to implement ES6 promises

The finally method

finally(callback){
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );

}
Copy the code

Promise. Resolve and Promise. Reject

static resolve (value) {
  if (value instanceof Promise) return value
  return new Promise(resolve => resolve(value))
}

static reject (value) {
  return new Promise((resolve ,reject) => reject(value))
}
Copy the code

Promise.all

Promise.all is a static method of the Promise class.

static all(list){
	return new Promise((resolve, reject) => {
		letCount = 0 // Number of promise state ends, initialized to 0letResValues = [] // Value when the status changes to Fufilled const listLength = list.lengthfor (leti = 0; i < list.length; Const p = isPromise(list[I]) {// If not a Promise, call promise. resolve const p = isPromise(list[I])? List [I] : this. Resolve (list[I]) p.tenn ((res)=>{resvalues.push (res) count++ // resolve if all states become fufilledif(count === listLength){resolve(resValues)}}, (e)=> {// if there is a reject, directly reject reject(e)})}})}Copy the code

Promsie.race

static race(list){
	return new Promise((resolve, reject) => {
    	for(leti = 0; i < list.length; i++){ const p = isPromise(list[i]) ? list[i] : This. Resolve (list[I]) // If there is a state transition, the new promsie state will be changed.Copy the code

Here is the promsie completion code that was just implemented:

// Define promise's state constant const PENDING ='pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'/ / tools function, determine whether for function const isFunction = fn = > Object. The prototype. ToString. Call (fn) = = ='[object Function]'Const isPending = status => status === pending Const isPromise = target => target instanceof promise const isPromise = target => target instanceof promise Reject, both parameters are also methods. Class Promise {constructor(executor) {// Require that the Promise argument be a function, or throw an error if it is notif(! isFunction(executor)) { throw new Error('Promise must accept a function as a parameter'} this._status = PENDING // Initialize the status value as PENDING this._value = undefined // Set the default value for value this._reason = undefined // This._rejectedQueues = [] This._rejectedQueues = [] This. Try {executor(this._resolve.bind(this), This._reject. Bind (this))} catch (err) {// Executor calls rejected method thisthenMethod, we need to modify the _resolve and _reject methods under constractor to execute the functions in the success/failure task queue in turn. _resolve(value) { const { _status: status } = this const resolveFun = () => {if(isPending(status)) {// State can only be changed from pending to two other states, // Save the parameters passed by the resove method. This._status = thislet cb
            while(cb = this._implemented ledqueues. Shift ()) {cb(value)}}} // PasssetTimeout 0 simulates the synchronization promise casesetTimeout(resolveFun, 0)
     
    }

    _reject(reason) {
        const { _status: status } = this
        const rejectFun = () => {
        if(isPending(status)) {// The status can only be changed from pending to two other states and can only be changed once this._reason = reason this._status = REJECTEDlet cb
            while (cb = this._rejectedQueues.shift()) {
                cb(reason)
            }
        }
        }
        setTimeout(rejectFun, 0)

    }

    // thenMethod implementationthen(onpity, onRejected) {// this is a pitythenMethod returns a promisereturnNew Promise((resolve, reject) => {// The function to execute on successlet// This will be a big pity. This will be a big pity. // This will be a big pityif(isFunction(ondepressing)) {const result = ondepressing (value) // If the current callback function returns a promise instance, wait until its state changes before executing the next callbackif (isPromise(result)) {
                        result.then(resolve, reject)
                    } else{// If the current callback does not return a Promise instance, execute the next onethenResolve (result)}}} catch (err) {// If the new promise fails, the new promise state changes to Rejected, reject (reject). Reject (err)}} // The same function is executed on successletRejectedHandle = (Reason) => {try {// If onRejected is not a function, ignore itif (isFunction(onFulfilled)) {
                    	const result = onRejected(reason)
                        if (isPromise(result)) {
                            result.then(resolve, reject)
                        } else{ reject(reason) } } } catch (err) { reject(err) } } const { _value: value, _status: status, _reason: Reason, _is_ledqueues: Is_ledqueues, _rejectedQueues: rejectedQueues} = This switch (status) {// When the status is pending, this switch will be executedthenThe method callback is queued for executioncase PENDING:
            fulfilledQueues.push(fulfilledHandle)
            rejectedQueues.push(rejectedHandle)
            break// When the state is depressing, directly perform the ondepressing methodcase FULFILLED:
            fulfilledHandle(value)
            break// Execute the onRejected method when the status is Rejectedcase REJECTED:
            rejectedHandle(reason)
            break
        }

    })

    }

    finially(callback){
      let P = this.constructor;
	  return this.then(
	    value  => P.resolve(callback()).then(() => value),
	    reason => P.resolve(callback()).then(() => { throw reason })
	  );

    }

    static resolve (value) {
	  if (value instanceof Promise) return value
	  return new Promise(resolve => resolve(value))
	}

	static reject (value) {
	  return new Promise((resolve ,reject) => reject(value))
	}

	static all(list){
    	return new Promise((resolve, reject) => {
    		letCount = 0 // Number of promise state ends, initialized to 0letResValues = [] // Value when the status changes to Fufilled const listLength = list.lengthfor (leti = 0; i < list.length; Const p = isPromise(list[I]) {// If not a Promise, call promise. resolve const p = isPromise(list[I])? List [I] : this. Resolve (list[I]) p.tenn ((res)=>{resvalues.push (res) count++ // resolve if all states become fufilledif(count === listLength){
    					resolve(resValues)
    				}
    			}, (e)=> {
    				// 如果有一个reject,直接reject
    				reject(e)
    			})
    		}
    	})
    }

    static race(list){
    	return new Promise((resolve, reject) => {
        	for(leti = 0; i < list.length; i++){ const p = isPromise(list[i]) ? list[i] : This. Resolve (list[I]) // If there is a state transition, the new promsie state will be changed.Copy the code

conclusion

In contrast to callback functions, promises can be chained to express asynchronous actions as a flow of synchronous actions. A bigger feature of Promise is that it provides a unified API for all asynchronous operations, which allows us to handle asynchronous operations more uniformly.

Promsie, however, is essentially a callback function that has been modified to be executed in THEN to make it more explicit. The downside is also obvious. The original code is wrapped in a promsie layer, which is then at first glance, making the semantics of the original not very clear.

So is there a better way to write it than Promise? What are the advantages and disadvantages? Welcome to relearn JS asynchronous series of articles.

reference

Reference segmentfault.com/a/119000000… Promisesaplus.com/ segmentfault.com/a/119000000… Es6.ruanyifeng.com/#docs/promi…

Welcome to my public account “front-end xiaoyuan”, I will update the original articles on it regularly. You can also add our wechat yu_shihu_ common communication technology.