es6 promise the king of knowledge points comes to challenge

Posted by leeandrew on Sat, 19 Feb 2022 20:54:50 +0100

If you are Xiaobai, this set of information can help you become a big bull. If you have rich development experience, this set of information can help you break through the bottleneck
2022web full set of video tutorial front-end architecture H5 vue node applet Video + data + code + interview questions.

The knowledge of promise can be divided into bronze, silver, gold, platinum, diamond, Xingyao and king. Maybe the king has played too much. Which stage have you been to?

bronze

You have used promise to solve asynchronous programming and know how to call then() and catch() to handle the callback function.

Basic Usage

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* Asynchronous operation succeeded */){
    resolve(value);
  } else {
  	// Asynchronous operation failed
    reject(error);
  }
});
promise.then((value) => {
	// value passed by resolve
	// Do some callback processing after success
}).catch((error) => {
	// Error is passed from reject(error);
	// Do some callback processing after asynchronous operation fails
})

silver

You should know two characteristics and three disadvantages of promise.

Two characteristics

  • The promise object has three states: pending, completed and rejected. The states of the three objects are not disturbed by the outside world
  • Once the state changes, it will not change again. This result can be obtained at any time. There are only two possibilities for the state of Promise object to change: from pending to fully and from pending to rejected. As long as these two situations occur, the state will solidify and will not change again. This result will be maintained all the time. At this time, it is called resolved.

Three shortcomings

  • Promise cannot be cancelled. Once it is created, it will be executed immediately. It cannot be cancelled halfway.
  • Secondly, if the callback function is not set, the errors thrown by Promise will not be reflected to the outside.
  • When it is in the pending state, it is impossible to know which stage it has reached (just started or about to be completed).

Promise.prototype.finally()

The finally() method is used to specify the operation that will be performed regardless of the final state of the Promise object. This method is the standard introduced by ES2018.
It's important to learn to use finally().
In the daily use of the front end, when we request the data of an interface through promise, when the request starts, we will put the page in the loading state, but after the interface request ends (whether successful or failed), we should end the loading state of the page.

// Make the page in loading state before calling the interface
commit('setLoading', true);
let checkResult = checkWarranty({SerialNumber: data.SN, CountryCode:config.user.Country})
.then((result: any) => {
    return result.data;
}).finally(() => {
	// Cancel the loading status of the page after the interface is called
    commit('setLoading', false);
});

I corrected many bug s because I didn't put the processing after the interface call in the finally callback, but processed it in then, without considering the failure of the interface request.

gold

promise.all

Promise. The all () method is used to wrap multiple promise instances into a new promise instance.

const p1 = new Promise((resolve, reject) => {
	if (/* Asynchronous operation succeeded */){
	    resolve('value1');
	  } else {
	    reject('error1');
	  }
});
const p2 = new Promise((resolve, reject) => {
	if (/* Asynchronous operation succeeded */){
	    resolve('value2');
	  } else {
	    reject('error2');
	  }
});
const p3 = new Promise((resolve, reject) => {
	if (/* Asynchronous operation succeeded */){
	    resolve('value3');
	  } else {
	    reject('error3');
	  }
});
const p = Promise.all([p1, p2, p3]);

In addition, Promise The parameter of the all () method can not be an array, but it must have an Iterator interface, and each member returned is a Promise instance.
The state of p is determined by p1, p2 and p3, which can be divided into two cases.

  1. Only when the states of p1, p2 and p3 become fully, the state of p will become fully. At this time, the return values of p1, p2 and p3 form an array and are passed to the callback function of p.

    p.then((result) => {
    console.log(result) // [value1,value2,value3]
    })

  2. As long as one of p1, p2 and p3 is rejected, the state of p becomes rejected. At this time, the return value of the first rejected instance will be passed to the callback function of p.

    p.catch((error) => {
    console.log(error) // error1 or error2 or error3
    })

Note that if the Promise instance as a parameter defines its own catch method, once it is rejected, it will not trigger Promise Catch method of all().

const p1 = new Promise((resolve, reject) => {
	reject('error1');
}).catch((error) => {
    console.log('p1',error)
});

const p2 = new Promise((resolve, reject) => {
	resolve('value2')
});

Promise.all([p1,p2]).catch((error) => {
    console.log('promise.all', error )
})

// p1 error1

If p1 does not have its own catch method, it will execute promise Catch method of all

const p1 = new Promise((resolve, reject) => {
	reject('error1');
})
const p2 = new Promise((resolve, reject) => {
	resolve('value2')
});

Promise.all([p1,p2]).catch((error) => {
    console.log('promise.all', error )
})
// promise.all error1

promise.race

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

const p1 = new Promise((resolve, reject) => {
	if (/* Asynchronous operation succeeded */){
	    resolve('value1');
	  } else {
	    reject('error1');
	  }
});
const p2 = new Promise((resolve, reject) => {
	if (/* Asynchronous operation succeeded */){
	    resolve('value2');
	  } else {
	    reject('error2');
	  }
});
const p3 = new Promise((resolve, reject) => {
	if (/* Asynchronous operation succeeded */){
	    resolve('value3');
	  } else {
	    reject('error3');
	  }
});
const p = Promise.race([p1, p2, p3]);

In the above code, as long as one instance of p1, p2 and p3 changes the state first, the state of p will change accordingly. The return value of the Promise instance that changed first is passed to the callback function of p.

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error1');
    }, 1000);
})

const p2 = new Promise((resolve, reject) => {
    resolve('value2')
});

Promise.race([p1, p2]).then((result) => {
    console.log('promise.race', result)
}).catch((error) => {
    console.log('promise.race', error)
})
// promise.race value2


const p1 = new Promise((resolve, reject) => {

    reject('error1');
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('value2')
    }, 1000);
});

Promise.race([p1, p2]).then((result) => {
    console.log('promise.race', result)
}).catch((error) => {
    console.log('promise.race', error)
})
// promise.race error1

If the promise instance uses catch or then to intercept, then in promise In the result corresponding to race, the value cannot be obtained

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error1');
    }, 1000);
})

const p2 = new Promise((resolve, reject) => {
    resolve('value2')
}).then((val) => {
    console.log(val)
})

Promise.race([p1, p2]).then((result) => {
    console.log('promise.race', result)
}).catch((error) => {
    console.log('promise.race', error)
})

/** value2
promise.race undefined
*/

Recognize that promise should be combined with EventLoop

promise itself handles asynchrony, but do you really understand the sequence of code execution?
In chain call, only after the previous then, catch and finally callbacks are executed, the following callbacks will be added to the micro task queue.

console.log('promise External 1')
const p1 = new Promise((resolve, reject) => {
    console.log('promise Internal 1')
    resolve()
}).then(() => {
    console.log('promise then1')
}).finally(() => {
    console.log('promise finally1')
})

console.log('promise External 2')

const p2 = new Promise((resolve, reject) => {
    console.log('promise Internal 2')
    reject()
}).catch(() => {
    console.log('promise catch2')
}).finally(() => {
    console.log('promise finally2')
});

console.log('promise External 3')
const p3 = new Promise((resolve, reject) => {
    console.log('promise Internal 3')
    reject()
}).catch(() => {
    console.log('promise catch3')
}).finally(() => {
    console.log('promise finally3')
});
console.log('promise External 4')

const p4 = new Promise((resolve, reject) => {
    console.log('promise Internal 4')
    resolve()
}).then(() => {
    console.log('promise then4')
}).finally(() => {
    console.log('promise finally4')
});

What do you think of the execution order of the above code?

/**
promise External 1
promise Internal 1
promise External 2
promise Internal 2
promise External 3
promise Internal 3
promise External 4
promise Internal 4
promise then1
promise catch2
promise catch3
promise then4
promise finally1
promise finally2
promise finally3
promise finally4
*/

As can be seen from the above example

  • promise's then catch finally belongs to micro tasks in eventLoop, and other codes are executed synchronously from top to bottom.
  • Similarly, micro tasks, which are called in a chain, are executed first. For example, finally is always called after then or catch, so all finally belong to the second batch of micro tasks, which are executed later. The first batch of micro tasks are promise then1, promise catch2, promise catch3 and promise then4
  • The execution sequence of the same batch of micro tasks is naturally carried out from top to bottom.

Platinum

Maybe you often use promise and have seen the promise specification of es6 before, but do you pay attention to the two latest promise methods? Those who engage in technology always need to keep learning. I believe you are all volume kings!

Promise.allSettled

Promise. The allsettled () method takes a set of promise instances as parameters and wraps them into a new promise instance. The wrapper instance will not end until all of these parameter instances return results, whether fully or rejected. This method was introduced by ES2020.

Once the new promise instance returned by this method is completed, the state is always fully and will not become rejected. After the status changes to full, the parameters received by promise's listening function are an array, and each member corresponds to one passed in promise Promise instance of allsettled().

const p1 = new Promise((resolve, reject) => {
    resolve('value1')
})

const p2 = new Promise((resolve, reject) => {
    reject('error2')
})

const p3 = new Promise((resolve, reject) => {
    reject('error3')
})

const p4 = new Promise((resolve, reject) => {
    resolve('value4')
})

const p = Promise.allSettled([p1,p2,p3,p4]).then((result) => {
    console.log(result)
})

/** 
[
  { status: 'fulfilled', value: 'value1' },
  { status: 'rejected', reason: 'error2' },
  { status: 'rejected', reason: 'error3' },
  { status: 'fulfilled', value: 'value4' }
]
*/

If the parameter promise instance calls then or catch, promise The value or reason corresponding to allsettled may be undefined

const p1 = new Promise((resolve, reject) => {
    resolve('value1')
})

const p2 = new Promise((resolve, reject) => {
    reject('error2')
})

const p3 = new Promise((resolve, reject) => {
    reject('error3')
}).catch((error) => {
    console.log(error)
})

const p4 = new Promise((resolve, reject) => {
    resolve('value4')
}).then((value) => {
    console.log(value)
})

const p = Promise.allSettled([p1,p2,p3,p4]).then((result) => {
    console.log(result)
})

/** 
error3
value4
[
  { status: 'fulfilled', value: 'value1' },
  { status: 'rejected', reason: 'error2' },
  { status: 'fulfilled', value: undefined },
  { status: 'fulfilled', value: undefined }
]
*/

Promise.any

Promise. The any () method takes a set of promise instances as parameters and wraps them into a new promise instance.

As long as one of the parameter instances is in the full state, the packaging instance will be in the full state; And return the promise instance whose resolve execution is completed first

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error1');
    }, 1000);
})

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('value2')
    }, 2000);
})

const p3 = new Promise((resolve, reject) => {
    resolve('value3')
})

Promise.any([p1, p2, p3]).then((result) => {
    console.log('promise.any', result)
}).catch((error) => {
    console.log('promise.any', error)
})

// promise.any value3

If all parameter instances become rejected, the wrapper instance will become rejected.

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error1');
    }, 1000);
})

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error2')
    }, 2000);
})

const p3 = new Promise((resolve, reject) => {
    reject('error3')
})

Promise.any([p1, p2, p3]).then((result) => {
    console.log('promise.any', result)
}).catch((error) => {
    console.log('promise.any', error)
})

// promise.any AggregateError: All promises were rejected

If promise uses then interception for resolve, promise Any can only get undefined, but it is still in the full state

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('value1');
    }, 1000);
}).then((res) => {
    console.log(res)
})

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error2')
    }, 2000);
})

const p3 = new Promise((resolve, reject) => {
    reject('error3')
})

Promise.any([p1, p2, p3]).then((result) => {
    console.log('promise.any', result)
}).catch((error) => {
    console.log('promise.any', error)
})
// promise.any undefined

promise and async

Async and promise handle asynchronous functions, but async returns a promise object

If you want to handle success and catch errors, you can write this.

(async () => f())()
.then(...)
.catch(...)

promise code execution order

The beginning of each chain call of the same Promise will first enter the micro task queue in turn.

let p = Promise.resolve();

p.then(() => {
  console.log("then1");
  Promise.resolve().then(() => {
    console.log("then1-1");
  });
}).then(() => {
  console.log("then1-2");
});

p.then(() => {
  console.log("then2");
});

The actual result is then1-1 then1-2 then2
Then will return a new Promise every time. At this time, p is no longer Promise Resolve () is generated by then1-2, so then2 should be executed after then1-2

Diamonds

promise.try

In the daily use of promise, light depends on promise Catch to catch exceptions is not enough, because catch can only catch errors inside promise.

function promiseTest() {
    console.log(dx) // Synchronization error
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('err1'); //Asynchronous error
        }, 1000);
    })
}

promiseTest().then((res) => {
    console.log(res)
}).catch((err) => {
    console.log('p1',err)
})
// ReferenceError: dx is not defined

When we call a method, the method may return a promise, but the method will execute, and some synchronization errors may be written outside the promise. Therefore, we have to use the method of try catch.

function promiseTest() {
    console.log(dx) // Synchronization error
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('err1'); //Asynchronous error
        }, 1000);
    })
}

try {
    promiseTest().then((res) => {
        console.log(res)
    }).catch((err) => {
        console.log('p1', err)
    })
} catch (e) {
    console.log('synchronization',e)
}
// Synchronous ReferenceError: dx is not defined

In order to solve the problem that synchronous and asynchronous errors need to be captured separately, promise try

var Promise = require('bluebird');
function promiseTest() {
    console.log(dx) // Synchronization error
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('err1'); //Asynchronous error
        }, 1000);
    })
}

Promise.try(() => {
    return promiseTest()
}).catch((e) => {
    console.log(e)
})

Note Promise Try is only a proposal up to now (it was mentioned a long time ago, but there is no following. You need to use Promise library Bluebird, Q, etc., or introduce Polyfill)

Write a minimalist version of promise

// Three statuses: PENDING, full, REJECTED
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    // The default state is PENDING
    this.status = PENDING;
    // Store the value of success status. The default value is undefined
    this.value = undefined;
    // Store the value of failure status. The default value is undefined
    this.reason = undefined;

    // Calling this method is success
    let resolve = (value) => {
      // When the state is PENDING, you can update the state and prevent the executor from calling the two resovle/reject method.
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
      }
    } 

    // Calling this method is a failure
    let reject = (reason) => {
      // When the state is PENDING, you can update the state and prevent the executor from calling the two resovle/reject method.
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
      }
    }

    try {
      // Execute immediately and pass the resolve and reject functions to the user  
      executor(resolve,reject)
    } catch (error) {
      // Execute failed logic when exception occurs
      reject(error)
    }
  }

  // Contains a then method and receives two parameters onFulfilled and onRejected
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      onRejected(this.reason)
    }
  }
}

Xingyao

Write a promise that can be called in a chain

What does a simple version of promise need to do

  1. Promise has three states: pending, fulfilled, or rejected; "Specification Promise/A+ 2.1"
  2. For new promise, you need to pass an executor() executor, which will execute immediately;
  3. The executor accepts two parameters: resolve and reject;
  4. The default state of promise is pending;
  5. Promise has a value to save the value of successful status, which can be undefined/thenable/promise; "Specification Promise/A+ 1.3"
  6. Promise has a reason value to save the failed state; "Specification Promise/A+ 1.5"
  7. promise can only change from pending to rejected, or from pending to fully. Once the status is confirmed, it will not change again;
  8. Promise must have a then method. Then receives two parameters: the successful callback onFulfilled and the failed callback onRejected; "Specification Promise/A+ 2.2"
  9. If promise is successful when then is called, onFulfilled is executed, and the parameter is the value of promise;
  10. If promise has failed when then is called, onRejected is executed, and the parameter is reason of promise;
  11. In what state does the chain call then return then or catch
// For new promise, you need to pass an executor() executor, which will execute immediately;
function MyPromise(executor) {
    // Promise has three states: pending, fulfilled, or rejected; The default state of promise is pending;
    this.state = 'pending';
    // Promise has a value to save the value of successful status, which can be undefined/thenable/promise;
    this.value;
    // promise has a reason value to save the failed state;
    this.reason;

    this.resolve = (result) => {
        // Promise is in an irreversible state. If you call the resolve function and reject function at the same time, the result of the first call will be taken by default.
        if (this.state === 'pending') {
            this.state = 'fulfilled';
            this.value = result
        }
    }

    this.reject = (error) => {
        // Promise is in an irreversible state. If you call the resolve function and reject function at the same time, the result of the first call will be taken by default.
        if (this.state === 'pending') {
            this.state = 'rejected';
            this.reason = error
        }
    }

    // promise must have a then method. Then receives two parameters: the successful callback onFulfilled and the failed callback onRejected;
    this.then = (onFulfilled, onRejected) => {
        try {
            // If promise is successful when then is called, onFulfilled is executed, and the parameter is the value of promise; If promise has failed when then is called, onRejected is executed, and the parameter is reason of promise;
            if (this.state === 'fulfilled') {
                let result = onFulfilled(this.value)
                // When then has been called, the value value is restored to undefined. If there is no new promise instance of then next time (this or this), the value obtained is undefined,
                this.value = undefined
                // If the result of result is also a MyPromise instance
                if (result instanceof MyPromise) {
                    return result
                }

            } else if (this.state === 'rejected') {
                let result = onRejected(this.reason)
                // If the result of result is also a MyPromise instance
                if (result instanceof MyPromise) {
                    return result
                }
            }

            return {
                myCatch: this.myCatch,
                then: this.then,
                finally: this.finally
            }
        } catch {
            // If the state is rejected and onRejected is not passed, an error will be reported
            return {
                myCatch: this.myCatch,
                then: this.then,
                finally: this.finally
            }
        }
    }
    // promise must have a catch method, which is the parameter of the rejected execution function
    this.myCatch = (onRejected) => {
        if (this.state === 'rejected') {
            let result = onRejected(this.reason)
            this.reason = undefined
            // If the result of result is also a MyPromise instance
            if (result instanceof MyPromise) {
                return result
            }
        }

        return {
            myCatch: this.myCatch,
            then: this.then,
            finally: this.finally
        }
    }

    this.finally = (callback) => {
        if (this.state !== 'pending') {
            let result = callback()
            // If the result of result is also a MyPromise instance
            if (result instanceof MyPromise) {
                return result
            }
        }
        // catch returns then when state is full
        return {
            myCatch: this.myCatch,
            then: this.then,
            finally: this.finally
        }
    }

    try {
        executor(this.resolve, this.reject)
    } catch (err) {
        this.reject(err)
    }
}

Let's test the three cases of reslove, reject and no call

new MyPromise((resolve, reject) => {
    reject('val1')
}).then((val) => {
    console.log(val)
}).then((val) => {
    console.log(val)
}).myCatch((err) => {
    console.log('Error ', err)
    return new MyPromise((resolve, reject) => {
        resolve('2222')
    })
}).myCatch((err) => {
    console.log('Error ', err)
}).finally(() => {
    console.log('finally')
}).finally(() => {
    console.log('finally')
}).then((res) => {
    console.log(1, res)
})
// Error val1
// finally
// finally
// 1 2222


new MyPromise((resolve, reject) => {
    resolve('val1')
}).then((val) => {
    console.log(val)
}).then((val) => {
    console.log(val)
}).myCatch((err) => {
    console.log('Error ', err)
    return new MyPromise((resolve, reject) => {
        resolve('2222')
    })
}).myCatch((err) => {
    console.log('Error ', err)
}).finally(() => {
    console.log('finally')
}).finally(() => {
    console.log('finally')
}).then((res) => {
    console.log(1, res)
})
// val1
// undefined
// finally
// finally
// 1 undefined


new MyPromise((resolve, reject) => {
}).then((val) => {
    console.log(val)
}).then((val) => {
    console.log(val)
}).myCatch((err) => {
    console.log('Error ', err)
    return new MyPromise((resolve, reject) => {
        resolve('2222')
    })
}).myCatch((err) => {
    console.log('Error ', err)
}).finally(() => {
    console.log('finally')
}).finally(() => {
    console.log('finally')
}).then((res) => {
    console.log(1, res)
})
// 

promise code execution sequence

Promise.resolve()
  .then(() => {
    console.log("then1");
    Promise.resolve()
      .then(() => {
        console.log("then1-1");
        return Promise.resolve();
      })
      .then(() => {
        console.log("then1-2");
      });
  })
  .then(() => {
    console.log("then2");
  })
  .then(() => {
    console.log("then3");
  })
  .then(() => {
    console.log("then4");
  });

When will then1-2 be printed?
then1
then1-1
then2
then3
then4
then1-2

According to specification 25.6.1.3.2 [3], when Promise resolve return s a Promise, a new Promise resolvethenablejob will be generated, which belongs to one of the Promise Jobs, that is, micro tasks.

This Job uses the supplied thenable and its then method to resolve the
given promise. This process must take place as a Job to ensure that
the evaluation of the then method occurs after evaluation of any
surrounding code has completed.

And the job will call the then function once to resolve Promise, which will generate another micro task.

In other words, when a promise is returned in then, catch, or finally, and the answer is to return or reject. This process inserts micro tasks twice.
then1 is the first micro task
then1-1 and then2 are the second micro task
Originally, then1-2 and then3 were the third micro mission, but due to return promise Reslove inserts two micro tasks, so then1-2 has actually become the fifth micro task, so then3 is the third micro task
then4 is the fourth micro mission
then1-2 is the fifth micro mission.

King

The above content only simulates some simple functions of promise, does not deal with the asynchrony of promise, and the way to realize chain call is not elegant. In addition, some other APIs of promise are not encapsulated. I think I can only go to Xingyao.
Come and see the real promise
https://zhuanlan.zhihu.com/p/183801144

The strongest King

In my heart, technology never has the highest level, and I have higher expectations for the future.
But it's still up to those who can provide es6 promise new api proposals or ideas and be officially adopted

Topics: Javascript Front-end