Preface πŸŽ™

Hello everyone, I’m Pan Xiao ‘an! A forever on the road to weight loss front er🐷!

This article is a supplement to the previous article written Promise 🩹. It mainly discusses some static methods and exception handling commonly used in Promise with demo, so that we can better understand and use Promise in daily development. Without further ado, let’s start with static functions!

Static function 🀐

According to my understanding, the static function can be divided into two parts, one is the Promise encapsulation method, including promise.resolve and promise.reject. All, Promise.allSettled, Promise.any, promise.race. We’ll start with the package promise.resolve.

Promise encapsulation method

Promise.resolve

Promise. Resolve returns a resolved Promise object.

The promise. resolve method argument is divided into three cases, corresponding to three different return values:

parameter The return value
Promise instance Return the parameter itself
Thenable object Flatten the Thenable object and return
other Returns an instance of a Promise that completes with parameters

We can use a few demos to help understand how the promise.resolve method handles different arguments:

Parameter is promise:

let param = new Promise((resolve, reject) = > {
  resolve('param is resoved promise')})let p = Promise.resolve(param)
console.log(p === param)//true
Copy the code

The parameter is thenable object

(For some fun with thenable objects, check out page 188 of JS You Didn’t know, and get a link at the end of the article.)

Since we rarely use the Thenable object when we use promises on a regular basis, let’s take a look at the thenable object definition before we write the demo:

Thenable: Any object or function that contains the then() method.

By referring to the resolvePromise code in handwritten Promises, we can see that the bale object needs to look like this if we want the Promise chain to have a result:

let thenable = {
  then: (resolve, reject) = > {
      resolve('1')}}Copy the code

If you’re confused by resolve and reject in the then method, you can jump to the resolvePromise method in Handwritten Promises. And x. Chen exists the code, I believe can answer your doubts.

Resolve flattens the Thenable object and takes its final state when we know thenable as a parameter. Consider the following case:

let thenable = {
    then: (resolve, reject) = > {
        onFulfill({
            then: (resolve, reject) = > {
                resolve('1')}})}}let p = Promise.resolve(thenable)
console.log(p)//promise:{status:fulfilled,result:1}
Copy the code

So we need to avoid using thenable itself as an argument to call resolve in thenable’s then method, as this would result in infinite recursion. Consider the following case:

let thenable = {
  then: (resolve, reject) = > {
    resolve(thenable)
  }
}

Promise.resolve(thenable)  / / death cycle
Copy the code

It is important to note here that the final state of thenable adoption includes cases where Thenable throws exceptions, which we can test using the following demo:

let thenable = {
    then: (resolve, reject) = > {
       throw new Error('I am an erro')}}let p = Promise.resolve(thenable)
console.log(p)//Promise:promiseStatus:rejected,promiseResult:Erro,I am an erro
Copy the code

Parameters are other values

The Promise. Resolve method will return a fulfilled Promise instance with parameters as promiseResult, promiseStatus which is fulfilled. We can use different basic types to write the test demo and view the results:

let p=Promise.resolve(1)
let p1=Promise.resolve('1')
let p2=Promise.resolve(null)
let p3=Promise.resolve(undefined)
let p4=Promise.resolve(true)
console.log(p)
console.log(p1)
console.log(p2)
console.log(p3)
console.log(p4)
Copy the code

The print result is as follows:

Written Promise. Resolve ()

With the demo above in mind, we can try the promise.resolve source code by hand:

Promise.resolve = function (value) {
    // Handle the case where the argument is Promise
    if (value instanceof Promise) return value;
    // Handle other cases
    return new Promise((resolve) = > {
      resolve(value);
    });
}
Copy the code

If the argument is a Promise instance, return the instance itself, otherwise return a new Promise instance, and the argument is called by resolve as its value, supplemented by the resolve method of the earlier handwritten Promise, which automatically expands thenbale.

Promise.reject

Promise.reject() returns a Promise object with a reason for the rejection.

Written Promise. Reject ()

The promise.reject () method is easy to understand, which returns a Promise instance of rejected (reason) and its state is Rejected. We can try to write its implementation source code:

Promise.reject = function (reason) {
    return new Promise((resolve, reject) = > {
        reject(reason)
    })
}
Copy the code

Promise batch method

The common denominator of batch processing methods of Promise instances is that the parameters are a bunch of Promise instances, and the return value is a new Promise, which depends on the state and result of each Promise instance in the parameters. The difference lies in the rules. The four methods have different rules for the states and results of multiple promises in parameters. A summary can be commonly understood as the following table (which is gradually forgotten by pending=> depressing). Pending => Rejected () :

methods Return values are commonly understood
Promise.all allrighttheright.wrongA iswrong
Promise.allSettled No matterRight and wrong,Return the status and result of each
Promise.race No matterRight and wrong,The first one to finish the execution will be returned
Promise.any There is arightIs returned,All wrongDid not return towrong

Let’s start with the promise.all method and go into the details and source code implementation of each method.

Promise.all

Promise.all has the following rules:

  • Parameters need to be iterable可迭代, such asArray ζˆ– SringOtherwise, a type error is thrown.
  • If the iterable is empty, synchronization returns a state offulfilled ηš„ PromiseInstance.
  • Iterable is not empty, but does not containPromiseInstance, and return one asynchronouslyfulfilled ηš„ PromiseInstance.
  • The iterable is not empty and containsPromiseInstance, returns onependingThe state of thePromiseInstance, whose state change conditions are:
    • Of all parametersPromisInstance frompendingState intofulfilledStatus, return value frompendingState intofulfilledstate
    • Parameters include a Promise instance that changes from pending to Rejected, and a return value that changes from pending to Rejected
  • The return value will be the same as that in the argumentpromiseOrder, not by callpromiseTo determine the order of completion.

Let’s use demo to verify these rules:

// Arguments need to be iterable
let p = Promise.all(1)
console.log(p)//number 1 is not iterable 

// The parameter is the iterator, but is empty. Synchronously return a Promise instance with the state of fulfilled
let p = Promise.all([])
console.log(p)//promise:{promiseState:fulfilled,promiseResult:[]}

// The parameter is an iterative object, but is not empty and does not contain a Promise instance. Asynchronously, a Promise instance is returned, and the state is fulfilled

let p = Promise.all([1.2.3])
console.log(p)//promise:{ promiseState:pending }
setTimeout(function () {
    console.log(p)/ / promise: {promiseState: fulfilled, promiseResult: [1, 2, 3]}
})

// If the iterable is not empty and contains a Promise instance, a Promise instance is returned in a pending state. The state changes according to how the Promise in the parameter changes.
// This will be a big pity
var p1 = Promise.resolve(1);
var p2 = Promise.resolve(2);
var p3 = Promise.resolve(3);
let pall = Promise.all([p1, p2, p3])
console.log(pall)//promise:{ promiseState:pending }
setTimeout(function () {
    console.log(pall)/ / promise: {promiseState: fulfilled, promiseResult: [1, 2, 3]}})// There is a promise in the Rejected state
var p1 = Promise.resolve(1);
var p2 = Promise.reject(2);
var p3 = Promise.resolve(3);
let pall = Promise.all([p1, p2, p3])
console.log(pall)//promise:{ promiseState:pending }
setTimeout(function () {
    console.log(pall)//promise:{promiseState:rejected,promiseResult:2}})Copy the code

The last test case is listed separately:

let p1 = new Promise((resolve, reject) = > {
    resolve('1')})let p2 = new Promise((resolve, reject) = > {
    resolve(new Promise((resolve, reject) = > {
        setTimeout(function () {
            resolve('3')})}})))let result = Promise.all([p1, p2, 3, {
    then: (resolve, reject) = > {
        resolve('4')}},])console.log(result)//promise:{ promiseStatus:pending }
setTimeout(function () {
    console.log(result)// Promise: ["1","2","3",4,"5"]}
})
Copy the code

In this demo, parameters have the following characteristics:

  • Array first variablep1: a state isfulfilled ηš„ PromiseInstance,promiseResult δΈΊ 1
  • Array second variablep2Nested:promise, and nested timer, the final result is in the state offulfilled ηš„ PromiseInstance,promiseResultThe value of2
  • The third variable in the array is the number 3
  • Array fourth variable: onethenableobject

Based on the printed results, we can get a new rule, namely:

If the promiseResult of the promise instance in the argument is still a Promise instance or thenable object, it is expanded.

With these demos, we can try writing promise.all code by hand, with comments marking each piece of logic.

Promise.all = function (params) {
    if(! (typeof params[Symbol.iterator] === 'function')) {
        throw new TypeError('params is not an iterator')}return new Promise((resolve, reject) = > {
        // When the parameter is empty, the state will be returned directly, which is a pity promise. Result is []
        const final=[]
        let len = args.length;
        if (len === 0) return resolve(final);
        for (let i = 0; i < params.length; i++) {
            const item = params[i];
            // Non-promises are converted to promises, and the then method expands all nested promises
            Promise.resolve(item).then((result) = > {
                final[i] = result;
                if (--len === 0) { resolve(final); }},(reason) = > {
                // Whenever a promise is rejected, it is rejected immediatelyreject(reason); }); }}); };Copy the code

Promise.allSettled

The Promise. AllSettled method returns an array of objects. Each object represents the state and value of the Promise in the parameters. Value is used to represent the promiseresult of the promise, otherwise reason is used to represent the promise.

We can use a demo to try using the Promise.allSettled method:

let p1 = new Promise((resolve, reject) = > {
    resolve('1')})let p2 = new Promise((resolve, reject) = > {
    reject('2')})let p3 = new Promise((resolve, reject) = > {
    resolve(new Promise((resolve, reject) = > {
        setTimeout(function () {
            resolve('3')})}})))let p4 = new Promise((resolve, reject) = > {
    reject(new Promise((resolve, reject) = > {
        setTimeout(function () {
            resolve('3')})}})))let p5 = {
    then: (resolve, reject) = > {
        resolve({
            then: (resolve, reject) = > {
                resolve('5')}})}}let p6 = {
    then: (resolve, reject) = > {
        reject({
            then: (resolve, reject) = > {
                resolve('5')}})}}let result = Promise.allSettled([p1, p2, p3, p4, p5, p6])
console.log(result)//promise:{promiseStatus:pending}
setTimeout(function () {
    console.log(result)
})
  /*promise:{promiseStatus:'fulfilled',promiseResult:[ [ { "status": "fulfilled", "value": "1" }, { "status": "rejected", "reason": "2" }, { "status": "fulfilled", "value": "3" }, { "status": "rejected", "reason": promise:{promiseStatus:'fulfilled',promiseResult:3} }, { "status": "fulfilled", "value": "5" }, { "status": "rejected", "reason": {then:function(resolve,reject){ resolve('5') }} } ]}*/
Copy the code

Here’s another demo:

let p=Promise.allSettled([])

console.log(p)//promise:{promiseStatus:'fulfilled',promiseResult:[]}
Copy the code

Combining the two demos, you can see that the promise. allSettled and Promise.all methods have a lot in common:

  • When the parameter iterator is null, the synchronization return status isfulfilledAnd has a value of[] ηš„ PromiseInstance.
  • When a parameter iterator contains nestingpromiseOr nestedthenableObject, if the state isfulfilledIn other words, it recurses to get the final value; If the state isrejected, will return directly.

Difference:

  • whenPromise.allMethods meetrejectedState, directly modify the resultPromiseInstance state isrejected, cause and first encountered in parametersrejectedThe cause of the state remains consistent;
  • whenPromise.allSettledMethods meetrejectedThe state of thePromiseInstance, simply write down, etc., in all iteratorsPromiseAfter the status change is complete, return the record;

Next we can try writing the source code for Promise.allSettled:

Promise.allSettled = function (params) {
    if(! (typeof params[Symbol.iterator] === 'function')) {
        throw new TypeError('params is not an iterator')}const result = [];
    let count = 0;
    for (let i = 0; i < params.length; i++) {
        const item = params[i];
        Resolve handles non-promise objects in iterators and makes them promises
        // The then method gets the result of the promise and can unwrap the loop
        Promise.resolve(item).then((result) = > {
            result[i] = { status: 'fulfilled'.value: result };
            if(++count === params.length) { resolve(result); }},(reason) = > {
            ++count;
            result[i] = { status: 'rejected', reason }; }); }}Copy the code

Promise.race

“Race” translates as “first come, first return.” Within the set of promises received, the state and outcome of the returned promises are determined based on the fastest set. Take a look at the following demo:

let p= Promise.race([new Promise((resolve,reject) = >{
   setTimeout(() = >{
    resolve(1)})}),new Promise((resolve,reject) = >{
    resolve(2)})])setTimeout(() = >{
    console.log(p)/ / promise: {promiseStatus: fulfilled, promiseResul: 2}
})
Copy the code

We can try handwritten promise.race source code:

Promise.race = function (params) {
    return new Promise((resolve, reject) = > {
        for (let i = 0; i < params.length; i++) {
            const item = params[i];
            Promise.resolve(item).then(
                (value) = > {
                    resolve(value)
                }, (reason) = > {
                    reject(reason)
                })
        }
    })
}
Copy the code

Resolve transforms non-promise arguments in a parameter iterator into promises. Based on the handwritten source code, we know that the THEN method does two things for us:

  • Exposing the presentPromiseThe instanceresult/reason
  • Flattened out the nestedPromiseThe instance

Promise.any

The promise. any method is the opposite of the promise. all method:

  • allMethods that are all pairs return all results, one error returns an error
  • anyMethods return error for all errors and correct for one

So we can modify the handwritten source code of Promise.all to get the following code:

Promise.any = function (params) {
    if(! (typeof params[Symbol.iterator] === 'function')) {
        throw new TypeError('params is not an iterator')}return new Promise((resolve, reject) = > {
        // When the parameter is empty, the state will be returned directly, which is a pity promise. Result is []
        const final = []
        let len = args.length;
        if (len === 0) return resolve(final);
        for (let i = 0; i < params.length; i++) {
            const item = params[i];
            // If resolve is encountered, change the outcome promise's promiseresult and promisestatus directly, subsequent changes will not take effect
            Promise.resolve(item).then((result) = > {
                resolve(result);
            }, (reason) = > {
                // Once a promise is rejected, it is put into the array and returned when it is full
                final[i] = reason;
                if (--len === 0) { reject(final); }}); }}); };Copy the code

Error handling ❌

The catch and finally methods, defined on the prototype of a Promise, can be inherited by a Promise instance and are important in the chain invocation of a Promise.

Promise.prototype.catch

One frequently mentioned concept is exception passthrough.

What is abnormal pass through? Let’s use demo to understand:

new Promise((resolve, reject) = > {
    reject('I failed.')
}).then(
    (value) = > { console.log('onfulfilled1', value) },
).then(
    (value) = > { console.log('onfulfilled2', value) },
).then(
    (value) = > { console.log('onfulfilled3', value) },
).catch(
    (err) = > { console.log('onRejected1', err) },// I failed
)
Copy the code

If you don’t use the onRejected method to catch an error, the error will be passed down until it is caught by a catch. After learning how to write the source code of the promise by hand, we can see that in the then method, the error will be passed down until it is caught. When the onRejected method is undefined, an exception is thrown by default:

onRejected = typeof onRejected === 'function' ? onRejected : reason= > { throw reason; };
Copy the code

In other words, we see that the last catch gets the error message from the first promise, but the error message is actually passed down through the first promise, so we can think that the catch method is actually equivalent to the following:

new Promise((resolve, reject) = > {
    reject('I failed.')
}).then(
    (value) = > { console.log('onfulfilled1', value) },
).then(
    (value) = > { console.log('onfulfilled2', value) },
).then(
    (value) = > { console.log('onfulfilled3', value) },
).then(  
    (value) = >{ console.log('onfulfilled4', value)},
    (err) = > { console.log('onRejected1', err) },// I failed
)
Copy the code

So we can try to write the source of the catch method:

class Promise {...catch (onRejected) {
    return this.then(null, onRejected); }... }Copy the code

Promise.prototype.finally

The Finnally method returns a Promise instance. At the end of the chain call, this function is executed regardless of the result.

We can take a look at the basic use of finally with a demo:

let presolve = Promise.resolve('success')
let preject = Promise.reject('fail')
let p1 = presolve.finally(() = >{})let p2 = preject.finally(() = >{})setTimeout(function () {
    console.log(p1)//promise:{promiseStatus:fulfilled,promiseResult:'success'}
    console.log(p2)//promise:{promiseStatus:rejected,promiseResult:'fail'}
    console.log(p1 === presolve)//false
    console.log(p2 === preject)//false
})
Copy the code

From this demo we can see the basic use of the finally method, with two minor details:

  • finllyThe return value and the call tofinallymethodsPromiseThe instancepromiseStatus ε’Œ promiseResultYou need to be consistent.
  • finllyThe return value and the call tofinallymethodsPromiseThe instance is not the same object; the new object is returnedPromiseInstance.

So we can try to write what Promise. Prototype. Finally the source:

class Promise {
   // ...  
   finally(onFinally) {
       return new Promise((resolve, reject) = > {
           this.then((result) = > {
               onFinally();
               resolve(result);
           }, (reason) = > {
               onFinally();
               reject(reason);
           });
       });
   }
   // ...  
}
Copy the code

Compatibility query ❓

Compatibility with all of the Promise static methods and error handling apis can be checked at this site:

Can I use?

The small voice BB 🀑

It’s time for my favorite whisper BB segment again, the New Year 🎏 Flag 🎏 is in continuous and steady progress.

Shenzhen epidemic is serious, a few days ago, I saw a colleague of Kexing carrying a desktop machine to run poison almost laughed, laughed suddenly thought of wu a few days ago sudden death of colleagues, can not help but start to think about the programmer this post, to think about the meaning of life, but still did not think out why.

Years ago to do a year-end physical examination, a few days ago to take a physical examination report:

Uric acid, blood sugar, blood lipids all alarm, may not wait to be optimized by the society, their own first to optimize.

Recently, the low-key youth group also came into contact with mindfulness meditation, downloaded a related APP, and started to try it.

In terms of reading, I found that many netizens are having fun with lu Xun’s The Scream. With a buff historical background, the meaning of the scream is so heavy.

To be continued with this series in March, I also want to share some of my recent experiences with low code platforms, and what I learned from learning Sketch. There’s more to share.

πŸŽ‰ πŸŽ‰ if you think this article is helpful to you, please don’t hesitate to give your thumbs up ~πŸŽ‰ πŸŽ‰

πŸŽ‰ πŸŽ‰ If you have any questions or suggestions about the wording, knowledge and format of the article, please leave a comment ~πŸŽ‰ πŸŽ‰

Refer to the article πŸ“

  • JS you Don’t Know

  • wangkaiwd/js-deep

  • MDN-Promise

  • Handwritten Promise- New Year’s Eve Edition