preface

A basic promise that handles synchronization states was completed in the previous post, so if you haven’t seen it, click on the link below.

Complete A+ specification Promise in three steps, one comment per line of code on average (part 1)

Asynchronous processing is done here with Promise 1.5, and Promise 2.0 does the chain-calling and the resolvePromise that handles the then return value

The final conclusion is the real essence, which is my written description of the promise principle after watching the video for at least five times and reviewing my code countless times.

1.5 transition myPromise

Before moving on to Promise 2.0, I thought I might need a 1.5 as a transition, specifically for asynchronous processing, which promises handle in a publish-subscribe mode.

This section is relatively simple, directly into the code, all the improvements over 1.0 have comments, the key is to understand the idea.

const RESOLVED = 'RESOLVED'; / / success
const REJECTED = 'REJECTED'; / / fail
const PENDING = 'PENDING'; / / wait state

class Promise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        // Used to store successful callbacks
        this.onResolvedCallbacks = []; 
        // Since promise can be multiple times, then receives multiple callbacks, so it uses the stack form
        this.onRejectedCallbacks = []; 
        let resolve = (value) = > {
            if(this.status === PENDING){
                this.value = value;
                this.status = RESOLVED;
                // Executor calls resolve to execute a callback stored in the stack
                this.onResolvedCallbacks.forEach(fn= >fn()); }}let reject = (reason) = > {
            if(this.status === PENDING){
                this.reason = reason;
                this.status = REJECTED;
                this.onRejectedCallbacks.forEach(fn= >fn()); }}try{
            executor(resolve,reject);
        }catch(e){
            reject(e);
        }
    }
    then(onFulfilled,onRejected){
        if(this.status === RESOLVED){
            onFulfilled(this.value);
        }
        if(this.status === REJECTED ){
            onRejected(this.reason)
        }

        if(this.status === PENDING){// The promise state is still pending, indicating that the executor is asynchronous
            this.onResolvedCallbacks.push((a)= >{// When asynchronous, save the ondepressing function passed in by the user (closure)
              // Some operations will be performed before the ondepressing passed in by the user (which will be added in the next version)
                onFulfilled(this.value);
            });
            / / here cannot directly this. OnResolvedCallbacks. Push (onFulfilled ()) onFulfilled will be implemented immediately
            / / didn't use this. OnResolvedCallbacks. Push (onFulfilled) although not direct execution, is not convenient to expand
            this.onRejectedCallbacks.push((a)= >{
               onRejected(this.reason); }); }}}module.exports = Promise
Copy the code

To summarize

In the asynchronous case, the promise itself (or the executor passed in by the user within the promise) is executed immediately, but the result of the executor function is returned asynchronously.

The then method is executed synchronously, so the THEN method is always executed before the executor, so the methods passed in the THEN method are stored for the executor callback.

In other words, in the asynchronous case, the promise state is uncertain. First execute the THEN, store the onFulfilled/onRejected function in the THEN, and then supply the executor, which will call the promise after confirming the state according to the result returned by the asyncio.

In ‘MyPromise1.0’, the executor is executed, the promise state is determined, and then the state is used to determine which function is passed in by the user. This is the opposite of how asynchrony was handled in 1.5.

2.0 introduce myPromise

Version 2.0 of myPromise addresses two major challenges to implementing promises:

  • Chain calls

  • Then three kinds of return value processing

As in the previous installment, let’s take a look at the promise features in Node for a few examples.

  1. thenThe return value of theonFulfilledoronRejectedIs passed to the next one.thentheonFulfilled. That’s the chain call
read('./na1me.txt').then((data) = > {// This is a deliberate error into the first layer, reject
  // A normal return in first resolve is accepted by then's resolve at the next level
  return 'then1 [resolve] return.'
},(err) => {
  // A normal return in the first reject layer is accepted by the resolve of the next then layer.
  return 'then1 [reject] return.' 
}).then((data) = > {
  console.log(data,'then2 [resolve] worked.');// The result is called here
},(err) => {err
  console.log(err,'then2 [reject] worked.');
})
Copy the code
  1. The value returned by the callback then is not the same as the value returned by the callbackthentheonFulfilled), error value (pass to the nextthentheonRejected),promise(Execute this firstpromiseAnd based on thispromiseThe return value of thethentheonFulfilledoronRejected) to be treated separately. Implement the resolvePromise function for processing

2.1 Return error value

read('./na1me.txt').then((data) = > {// there is a deliberate error in reject
  throw new Error('then1 [resolve] throw error.')// An error in resolve is accepted by then reject at the next level
},(err) => {
  throw new Error('then1 [reject] throw error.')// The error in the first reject layer is also accepted by the next reject layer
}).then((data) = > {
  console.log(data,'\n---then2 [resolve] worked.'); 
},(err) => {
  console.log(err,'\n---then2 [reject] worked.');// The result is called here
})
Copy the code

2.2 return to promise

read('./name.txt').then((data) = > {//name. TXT contains the contents of age.txt
  return read(data)
}, (err) => {})
.then((data) = > {// The second layer performs the state determination of which promise is completed by the first layer
  console.log(data,'\n---then2 [resolve] worked.'); // Execute and print the contents of age.txt
}, (err) => {
  console.log(err,'\n---then2 [reject] worked.');
})
Copy the code
  1. If the error is not caught, it can be caught at any subsequent level and the error will not be passed back
read('./na1me.txt').then((data) = > {Reject = reject; reject = reject
  return read(data) // It doesn't matter whether you write it or not, because there is no first then resolve
})
.then((data) = > {
  console.log(data,'\n---then2 [resolve] worked.');
}, (err) => {
  console.log(err,'\n---then2 [reject] worked.'); No file na1me.txt
})
.then((data) = > {
  console.log(data,'\n---then3 [resolve] worked.'); // This is then called because the error was caught at layer 2
}, (err) => {
  console.log(err,'\n---then3 [reject] worked.');
  // In addition, if the layer 2 then code runs with an error or throw new error, it is executed to catch layer 2 errors instead of layer 1 errors. If the second layer is not passed in reject, the error in the first layer is caught by the third layer here, THEN
})// The code runs with an error if the error is not caught throughout
Copy the code
  1. Each execution of the promise.then method returns a new ‘promise’, which is why. Then
read('./name.txt').then((data) = > {
  return 111
})// Even if 111 is returned, it is wrapped as a promise. The second then is actually the then of the new promise
.then((data) = > {
  console.log(data)
})
Copy the code

Mypromise 2.0 source code parsing

No more words, start code, words are in the comments.

const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
const PENDING = 'PENDING';

class Promise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = []; 
        this.onRejectedCallbacks = []; 
        let resolve = (value) = > {
            if(this.status === PENDING){
                this.value = value;
                this.status = RESOLVED;
                this.onResolvedCallbacks.forEach(fn= >fn()); }}let reject = (reason) = > {
            if(this.status === PENDING){
                this.reason = reason;
                this.status = REJECTED;
                this.onRejectedCallbacks.forEach(fn= >fn()); }}try{
            executor(resolve,reject);
        }catch(e){
            reject(e);
        }
    }
    then(onFulfilled,onRejected){// To implement the chain call, change the entire then function to return a promise, so that the return value has a. Then method
        let promise2 = new Promise((resolve, reject) = > {// New Promise is passed to executor and executed immediately
            if(this.status ===RESOLVED){
                Settimeout = settimeout
                // Because the following resolvePromise is passing in promise2 itself
                // With setTimeout removed, the executor in the constructor of the new operation of promise2 has not yet been executed
                // If the resolvePromise is not initialized, promise2 will not be passed to resolvePromise.
                ResolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise
                // Node implements A promise that doesn't use setTimeout, but rather uses A lower-level method, but using setTimeout is one of the recommended methods in the A+ specification, so don't worry.
                setTimeout((a)= > {
                    try {
                        let x = onFulfilled(this.value); // Then returns the value of the resolve return
                        // This is a big pity. There is no need to worry about ondepressing, which is also a pity that the user will pass in a resolve error, because the executor that is part of promise2 will be caught by the try/catch of promise2
                        // Try /catch cannot catch asynchronous errors, so it is necessary to wrap another try/catch layer
                        resolvePromise(promise2,x,resolve,reject) // There are three cases of x (normal, error, promise), and the function calls resolve or reject based on x
                    } catch (e) {// Make promise2 fail
                        reject(e)
                    }
                    
                },0)}// The reason for the change below is the same as here
            if(this.status ===REJECTED ){
                setTimeout((a)= > {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2,x,resolve,reject)
                    } catch (e) {
                        reject(e)
                    }
                },0)}if(this.status === PENDING){
                this.onResolvedCallbacks.push((a)= >{
                    setTimeout((a)= > {// It is not necessary to use setTimeout because it is asynchronous. Added to keep code consistent
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2,x,resolve,reject)
                        } catch (e) {
                            reject(e)
                        }
                    },0)});this.onRejectedCallbacks.push((a)= >{
                    setTimeout((a)= > {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2,x,resolve,reject)
                        } catch (e) {
                            reject(e)
                        }
                    },0)}); }}}})// resolvePromise All promises should stick to bluebird Q ES6 - Promise
const resolvePromise = (promise2, x, resolve, reject) = > {
    // 1. Loop references itself and wait for it to finish
    // let p = new Promise((resolve,reject) => {
    // resolve(1)
    // }) 
    // let p2 = p.then(data => {
    // return p2// error because the promise p2 returns p2, and resolvePromise,
    // // When the return value X of onFulfilled/onRejected is a promise, the returned promise will be implemented, and then the state of P2 will be decided according to the return value of this promise
    // // and the value returned here is p2, which is determined by the internal P2, in an infinite loop. So when a user uses a promise this way, an error is reported
    // // So when we write resolvePromise ourselves, we need to identify and report errors if the return value is the promise instance itself.
    // })
    // p2.then(() => {},() => {}) TypeError: Chaining cycle detected for Promise #< promise >
    
    if (promise2 === x) { // I wait for the other person to complete, the condition of the other person to complete is waiting for the other person to call reject
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}// Subsequent conditions must be strictly judged to ensure that the code can be used with other libraries
    let called;
    if ((typeof x === 'object'&& x ! =null) | |typeof x === 'function') { // Check if it is a promise
        try {
            let then = x.then;
            if (typeof then === 'function') { // Check if it is a promise
                // A+ = A+ = A+ = A+ = A+ = A+ = A+ = A+ = A+
                // ↓ = x.teng (y=>{},e=>{})
                then.call(x, y => { // Success or failure depends on the state of the promise
                    if (called) return;// This prevents the promise library from calling resolve/reject more than once
                    called = true;// Call true (true); return (true)
                    resolvePromise(promise2, y, resolve, reject); // The recursive parsing process
                }, e => {
                    if (called) return;
                    called = true;
                    reject(e);
                });
            } else { // 1**resolve(x); }}catch (e) {
            if (called) return;
            called = true;
            reject(e); // The value is incorrect}}else { // 2**
        resolve(x);
    }
    // If the promise condition is not a promise, the else resolve condition is not a promise.
    // Then try/catch does not catch the execution of thene. call.
    // because let then = x.teng; You might get an error here, so there's a try/catch layer, so you can't write it with other promise conditions
    // If you want to get A then, try/catch it. If you want to get A THEN, try/catch it
    // If you want to get a parameter assignment, you can get an error. If you want to get a parameter assignment, you can get an error.
    //Object.defineProperty(x,'then',{get(){throw new Error()}})
}

module.exports = Promise

Copy the code

Mypromise 2.0 source code uncommented version

Add a version without comments, convenient for everyone to switch to see the structure and logic, too many comments I feel confused

So definitely not for water words 🙂

But I think it’s very troublesome to break them apart, and it’s not convenient for you to copy and run them, so I just write them together.

Without the comments, there isn’t much code, and you can see that there are three main blocks: Executor, then, and resolvePromise

const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
const PENDING = 'PENDING';

class Promise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = []; 
        this.onRejectedCallbacks = []; 
        let resolve = (value) = > {
            if(this.status === PENDING){
                this.value = value;
                this.status = RESOLVED;
                this.onResolvedCallbacks.forEach(fn= >fn()); }}let reject = (reason) = > {
            if(this.status === PENDING){
                this.reason = reason;
                this.status = REJECTED;
                this.onRejectedCallbacks.forEach(fn= >fn()); }}try{
            executor(resolve,reject);
        }catch(e){
            reject(e);
        }
    }
    then(onFulfilled,onRejected){
        let promise2 = new Promise((resolve, reject) = > {
            if(this.status ===RESOLVED){
                setTimeout((a)= > {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2,x,resolve,reject)  
                    } catch (e) {
                        reject(e)
                    }
                    
                },0)}if(this.status ===REJECTED ){
                setTimeout((a)= > {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2,x,resolve,reject)
                    } catch (e) {
                        reject(e)
                    }
                },0)}if(this.status === PENDING){
                this.onResolvedCallbacks.push((a)= >{
                    setTimeout((a)= > {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2,x,resolve,reject)
                        } catch (e) {
                            reject(e)
                        }
                    },0)});this.onRejectedCallbacks.push((a)= >{
                    setTimeout((a)= > {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2,x,resolve,reject)
                        } catch (e) {
                            reject(e)
                        }
                    },0)}); }}}})const resolvePromise = (promise2, x, resolve, reject) = > {
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}let called;
    if ((typeof x === 'object'&& x ! =null) | |typeof x === 'function') {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, e => {
                    if (called) return;
                    called = true;
                    reject(e);
                });
            } else{ resolve(x); }}catch (e) {
            if (called) return;
            called = true; reject(e); }}else{ resolve(x); }}module.exports = Promise

Copy the code

conclusion

The code starts with p1, P2, and P3 to refer to three successive promises

let p1 = new Promise((resolve,reject) = > {
    resolve('p1')
})
p1
.then((data) = > {// Create a new promise P2
        resolve(data + ' + p2')
    }, (err) => {
        console.log(err);
    })
// Actually, here is the. Then method for the second promise p2, which has nothing to do with P1
.then((data) = > {// create a new promise p3
        console.log(data + ' + p3');
    }, (err) => {
        console.log(err);
    })
Copy the code

1. Turn the then function itself into a promise, and execute it, thus realizing the chain call that can not stop. Then

So what you do is you write the then function that returns a promise, so then has a state, it has its own then method,p1 calls then produces P2, and passes data to P2 or to THEN, so you can think of then as P2

This is a big pity, which is a big pity, and THEN execute P2 immediately, which is perhaps onFulfilled or onRejected. The implementation result represents the status of P2 which is successful or failed.

According to the state of P2, the second. Then method is executed to realize the chain call, which is the principle of the chain call realized by promise in the synchronous state!!!!

(The asynchronous state is the same, but a little detour, back to summarize the repair)

So p1 is a very straightforward state, so the executor calls resolve p1, reject P1, reject

The user will judge and write p1 in the actuator and decide the state of P1, thus deciding the first one. Then this will be called ondepressing or onRejected

This is a big pity. Then onFulfilled or onRejected, which will be decided according to the state of the first. Then or P2

The status of P2 is decided by the return result X of onFulfilled/onRejected. What is X? This is the second difficult point.

2. Implement resolvePromise

In most cases, whether onFulfilled or onRejected, resolve will be called normally.

However, there are also two other cases. One is the onFulfilled/onRejected code written by the user, which will be called reject

This promise will be implemented. According to the state of this promise, the state of P2 will be decided.

Give it a go (to be done)

There is no operating environment at work, and the test results will be displayed within two or three days

And check the code and run the official test (there should be no problem because it’s the teacher’s code)

additional

Thank you for your patience in reading, there may be some errors that have not been checked, please kindly spray, and we will keep modifying and supplementing later.

Again, this is a note and notes, any mistakes and questions welcome to leave a message, we will reply and correct in time!!

I’ve almost finished writing promises in the midpart, and I’ll write some race all catch methods in the next part.

After writing the Promise, I plan to write a JS engine execution sequence. I will continue my style and write very detailed words (at the same time, I should not have pictures, because I am too lazy). But no more notes! .

We created a small group of front-end rookies and invited people of similar age and level to join.

The only requirement and the original intention of the group is that one article must be posted every week and everyone must be cleared on Tuesday to urge everyone in the group to study.