March 2020.3 Formally enter the front end, target senior front end engineer, experience is not yet, if the content of the article is wrong, welcome to correct.

The Promise object is a container that manages the state and data resulting from synchronous/asynchronous operations. Its core logic is that the external operation reasonably sends its running result state and data to the container for storage and management, and then the external operation takes out the state and data of the external operation from the container for the next operation.

This article begins with the following five key understandings of handwritten promises, and finally completes handwritten promises:

  • The basic look of the Promise container (properties and behavior of the container)
  • How the Promise container is constructed (the container hosts the resulting state and data of the Excutor function)
  • Write data to the Promise container (resolve, reject)
  • Reading data from the Promise container (the then method takes care of that)
  • Implement chain calls to then functions

I: Basic appearance of the Promise container (container properties and behavior)

1. Container-managed data (attributes)

  • State: Pending, resolved/regrettable, rejected
  • Result Value: data in container resolved/depressing state
  • Reason: Indicates the data in the container Rejected state
  • OnResolvedTodoList: Array of callback actions added when in the pending state and container Resolved/fulfilled state
  • OnRejectedTodoList: An array of callback actions added when the Rejected state is pending

2. Read and write (behavior) of container data

  • Write operations: Inside the container define resolve and Reject methods that set the internal state and data for external writing (value/reason) to the container. In fact, the Promise container has constraints on writes, allowing only initial writes, not overwrites.
  • Read operation: Defines a then method inside the container that can read internal state and data, so that the external reads the state and data in the container can trigger the corresponding callback behavior.

3. Code implementation

class Container {
    state = undefined;
    value = undefined;
    reason = undefined;
    
    constructor(excutor) { // construct the container}
    
    resolve = value= > { // Write container data}
    reject = reason= > { // Write container data}
    
    onResolvedTodoList = [];
    onRejectedTodoList = [];
    then(onResolved, onRejected) { // Read the container data}
}

Container.PENDING = 'pending';
Container.RESOLVED = 'resolved';
Container.REJECTED = 'rejected';
Copy the code

Ii. Construction method of Promise container instance (the container hosts the running result state and data of Excutor function)

1. The definition of the constructor’s parameter function excutor

  • Where is it defined: outside the container
  • Function responsibilities: 1 is the carrier of business operations. 2 is to give the status and data of Excutor to the container for management.

Examples of constructor calls outside the container:

The excutor function defines the resolve and reject parameters
const p1 = new Container((resolve, reject) = > {
  // Internal logic determines when and what container data is initialized
  setTimeout(() = > {
    resolve(0)})})Copy the code

2. The constructor argument function excutor call

  • Where to call: inside the container
  • Call timing: When the container instance is constructed

An example of a constructor implemented inside a container:

class Container {
    state = undefined;
    value = undefined;
    reason = undefined;
	
	// Focus: construct the excutor function call, its call timing, and argument passing.
    constructor(excutor) {
        try {
            excutor(this.resolve, this.reject);
            this.state = Container.PENDING;
        } catch (e) {
            this.reject(e)
        }
    }

    resolve = value= > { // Write container data}
    reject = reason= > { // Write container data}
    
    then(onResolved, onRejected) { // Read the container data}
}

Container.PENDING = 'pending';
Container.RESOLVED = 'resolved';
Container.REJECTED = 'rejected';
Copy the code

Write data to Promise (resolve, reject)

1. Define resolve and reject functions

  • Where is it defined: inside the container
  • The excutor function returns the state and data to the container for management.
  • Container state and data updates are not allowed: if the container state is not pending, it is considered an invalid notification and nothing is done.
  • Callback arrays that handle state subscriptions: The onResolvedTodoList and onRejectedTodoList arrays store callback functions that subscribe to a state. Once the container-managed state and data are initialized, the callback functions can be extracted and executed in order of insertion according to the container state.
  • Defined inside the container: Defined inside the container for easy access and setting of the container state and data.
  • Defined as an arrow function: Defined as an arrow function, the caller cannot change its internal this pointer in any way, even if the call, apply, and bind functions are used.

An example implementation of resolve and reject inside a container:

class Container {
    constructor(excutor) {} 

    resolve = value= > {
        if (this.state ! = Container.PENDING)return
        this.status = Container.RESOLVED;
        this.value = value;
        while (this.onResolvedTodoList.length) this.onResolvedTodoList.shift()() // get the first one
    }

    reject = reason= > {
        if (this.state ! = Container.PENDING)return
        this.status = Container.REJECTED;
        this.reason = reason;
        while (this.onRejectedTodoList.length) this.onRejectedTodoList.shift()()
    }
    
    then(onResolved, onRejected){}}Copy the code

2. Invoke the resolve and reject functions

  • Where to call: outside the container
  • Call timing: Supports synchronous and asynchronous invocation. In asynchronous invocation, the callback function is invoked.

Examples of calls to resolve and reject outside the container:

const p1 = new Promise((resolve, reject) = > {
  // Write data synchronously to the container
  // resolve(0)
  // Write data to the container asynchronously
  setTimeout(() = > {
    resolve(1)})})Copy the code

Read data from the Promise container (then)

1. Definition of then function

  • Where is it defined: inside the container
  • Function responsibilities: Receives two callback functions passed in by the caller, determines which function to execute based on the state stored in the container, and executes the function with the data stored in the container as input.
  • Optional state handler: allows the caller to operate on only one state.
  • Pending state processing: While pending, the container does not have the state or data of the excutor result, so it puts the two callback functions into onResolvedTodoList and onRejectedTodoList respectively. Wait until the state and data of excutor execution results are delivered to the container for management, after the container’s resolve and Reject method calls.

An example of an internal container implementation of the then function:

class Container {
    constructor(excutor) {}

    resolve = value= > {}
    reject = reason= > {}
    
    onResolvedTodoList = [];
    onRejectedTodoList = [];
    
    then(onResolved, onRejected) {
        onResolved = onResolved ? onResolved : value= > value;
        onRejected = onRejected ? onRejected : reason= > { throw reason };
        switch (this.state) {
            case Container.PENDING:
                this.onResolvedTodoList.push(() = > {
                    try {
                        const value = onResolved(this.value);
                        resolve(value);
                    } catch(e) { reject(e); }});this.onRejectedTodoList.push(() = > {
                    try {
                        const value = onRejected(this.reason);
                        reject(value);
                    } catch(e) { reject(e); }});break;
            case Container.RESOLVED:
                try {
                    const value = onResolved(this.value);
                    resolve(value);
                } catch (e) {
                    reject(e);
                }
                break;
            case Container.REJECTED:
                try {
                    const value = onRejected(this.reason);
                    resolve(value);
                } catch (e) {
                    reject(e);
                }
                break; }}}Copy the code

2. Call the then function

  • Where to call: outside the container
  • Call time: after the container instance is constructed. Note: After the container instance is constructed, the state and data in the container are not ready for the time lag between asynchronously writing state and data to the container.

An example of a call to the then function outside the container:

p1.then((value) = > {
  console.log(value)
}, (reason) = > {
  console.log(reason)
})
Copy the code

Five: then function chain call

  • The drawback of the then function not supporting chain calls is that the THEN function does not return a value. Although it can be called multiple times, it can only do one operation based on the container state and data, which cannot be pipelined. This still does not solve the problem of nested callback function loops.
  • The then function has the advantage of supporting chained calls: the then function returns a new container based on the result of the callback, so that the external can do the next operation on the container state and data returned by the THEN call. This iteration enables pipelining of operations and solves the problem of nested callback functions.

1. Modify the definition of the then function to support chain calls

The then method returns a container object whose state and data are governed by the result of the callback.
  • If the callback function run returns a value, the resolve method is called to initialize the state and data of the new container.
  • If a callback is run that returns a container and not the new container itself, the then method of the returned container is called to read its internally managed state and data, and the resolve or Reject methods of the new container are called to initialize the new container’s state and data. This read-and-write communication is done by returning the resolve or Reject methods of the new container as callbacks to the next operation of the container (that is, onResolved and onRejected parameters of the THEN method, respectively).
  • If the callback function runs and returns the new container itself, it will never end up reading the state and data of the new container as the state and data of the new container, if nothing special is done, in an infinite loop. The solution to this problem is to have the callback function run (excutor of the new container) as an asynchronous task in the task queue. This has the advantage of having the new container object to judge before initializing the state and data of the new container.

An example of a container that supports chained calls to then functions:

class Container {
    constructor(excutor) {}

    resolve = value= > {}
    reject = reason= > {}

    onResolvedTodoList = [];
    onRejectedTodoList = [];

    then(onResolved, onRejected) {
        onResolved = onResolved ? onResolved : value= > value;
        onRejected = onRejected ? onRejected : reason= > { throw reason };
        let containerBack = new Container((resolve, reject) = > {
            switch (this.state) {
                case Container.PENDING:
                    this.onResolvedTodoList.push(() = > {
                        setTimeout(() = > {
                            try {
                                const value = onResolved(this.value);
                                resolveContainer(containerBack, value, resolve, reject);
                            } catch(e) { reject(e); }})});this.onRejectedTodoList.push(() = > {
                        setTimeout(function () {
                            try {
                                const value = onRejected(this.reason);
                                resolveContainer(containerBack, value, resolve, reject);
                            } catch(e) { reject(e); }})});break;
                case Container.RESOLVED:
                    setTimeout(() = > {
                        try {
                            const value = onResolved(this.value);
                            resolveContainer(containerBack, value, resolve, reject);
                        } catch(e) { reject(e); }})break;
                case Container.REJECTED:
                    setTimeout(function () {
                        try {
                            const value = onRejected(this.reason);
                            resolveContainer(containerBack, value, resolve, reject);
                        } catch(e) { reject(e); }})break; }});return containerBack
    }
}

function resolveContainer(containerBack, value, resolve, reject) {
    if(! (valueinstanceof Container)) {
      resolve(value)
    } else {
      if (value === containerBack) {
        reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}else{ value.then(resolve, reject); }}}Copy the code

2. Chain calls to the then function

  • The purpose of the THEN chain call: After calling the THEN method, the caller can do the next operation based on the result of the callback function execution, that is, the callback function that listens for the result of the callback function execution.

Example of a chaining call to the then function from outside the container:

const p2 = p1.then((value) = > {
  console.log(value)
}, (reason) = > {
  console.log(reason)
})
p2.then((value) = > {
  console.log('p2', value)
}, (reason) = > {
  console.log('p2', reason)
}).then((value) = > {
  console.log('p3', value)
}, (reason) = > {
  console.log('p3', reason)
})
Copy the code

The end of this article, the audience master go, welcome next time.