Promise/A + specification

The specification provides several important messages:

  1. Promise is a state machine

  2. ** THEN ** method has two parameters: ** Promise. Then (ondepressing, onRejected)**, where:

    • onFulfilledonRejectedAre optional arguments that must be ignored if they are not functions
    • whenonFulfilledIt’s a function
      • This must be implemented after the state of the promise changes to a big pity, and the promise value is taken as the first parameter
      • Can be executed only once
    • whenonRejectedIt’s a function
      • Must be executed after the promise status changes to Rejected, with the Promise value as the first parameter
      • Can be executed only once
    • OnFulfilled or onRejected must not be called until the execution context stack contains only platform code. (Don’t understand this paragraph, probably support asynchronous?)
    • thenThis can be fulfilled many times. After the state of promise changes to pity/Rejected,**onFulfilled* * and**onRejected** is executed in the order in which it was originally bound.
    • thenYou must return a promisepromise2 = promise1.then(onFulfilled, onRejected)
      • whenonFulfilledoronRejectedReturn a value x, and run the Promise resolver[[Resolve]] (promise2, x)
      • When onFulfilled or onRejected throws exception E, promise2 must change to Rejected and regard E as reason
      • when**onFulfilled** is not a function, and the state of promise1 is a big pity. Promise2 must perform the state transition with the same value.
      • when**onRejected** is not a function and the state of promise1 is Rejected. Promise2 must use the same reason to complete the state transition.
  3. [[Resolve]] (promise2, x promise

    The Promise resolution process is an abstract operation that takes a Promise and a value as input, which we express as [[Resolve]](Promise, x). In general, if x is thenable, which includes the then method, it will force a Promise to adopt x’s state on the assumption that X at least behaves like a Promise. Otherwise, the promise will take the value X fulfill.

    Perform the following steps for details

    • If x and promise point to the same object, thenTypeErrorAs a reason, reject, promise
    • If x is a promise, adopt its state
    • If x is object or function
      1. let then = x.then
      2. If retrieving the attribute X.teng causes an exception E to be thrown, reject the promise because e.
      3. If then is a function, x is called as this, with the first argument resolvePromise and the second argument rejectPromise
        1. If the resolvePromise is called with the value y, run **[[Resolve]] (promise, y)阿鲁纳恰尔邦
        2. Reject a rejectPromise with r if it is invoked using reason R
        3. If both resolvePromise and rejectPromise are called, or if multiple calls are made to the same parameter, the first call takes precedence and any other calls are ignored.
        4. If an exception occurs in the THEN call
          1. If resolvePromise or rejectPromise has been invoked, ignore the exception
          2. Otherwise, reject the promise for reason E.

Now that the PromiseA+ specification is clear, let’s start implementing it step by step.

Implementation framework

The Promise constructor takes a function as an argument, resolve and reject. They are two functions provided by the JavaScript engine, so here we first write out the constructor skeleton. Resolve and Reject are the parts that need to be implemented internally. The resolve function changes the state of the Promise object from “unfinished” to “successful” (from pending to Resolved), and the Reject function changes the state from “resolved” to “resolved”. Change the state of the Promise object from “Unfinished” to “Failed” (from Pending to Rejected).

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Mypromise {
	constructor(executor) {
        this.value = null;
        this.reason = null;
        this.state = PENDING;

        const resolve = (value) = > {
            this.value = value
            this.state = FULFILLED
        }
        const reject = (reason) = > {
            this.reason = reason
            this.state = REJECTED          
        }
        try {
            executor(resolve, reject)
        }catch (reason){
            reject(reason)
        }
    }
}
Copy the code

Implement then methods

The then method takes two functions, provides delayed binding, and supports multiple calls, so we need to store the callbacks internally in two arrays, so that resolve and reject can be executed by pulling the callbacks out of the arrays.

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Mypromise {
	constructor(executor) {
        this.value = null;
        this.reason = null;
        this.state = PENDING;
				this.onFullfilledCallbacks = [];   // Save the successful callback
        this.onRejectedCallbacks = [];     // Save the failed callback

        const resolve = (value) = > {
            this.value = value
            this.state = FULFILLED
						// Take the callback functions from the successful callback array and execute them one by one.
						this.onFullfilledCallbacks.forEach(onFullfilledCallback= > {
                onFullfilledCallback(this.value)
            })
        }
        const reject = (reason) = > {
            this.reason = reason
            this.state = REJECTED  
						// Take the callback functions from the failed callback array and execute them one by one
						this.onRejectedCallbacks.forEach(onRejectedCallback= > {
                onRejectedCallback(this.reason)
            })        
        }
        try {
            executor(resolve, reject)
        }catch (reason){
            reject(reason)
        }
    }

    then(onFulfilled, onRejected) {
        if (this.state === FULFILLED) {
            onFulfilled(this.value)
        }else if (this.state === REJECTED) {
            onRejected(this.reason)
        }else {
            this.onFullfilledCallbacks.push(onFulfilled);
            this.onRejectedCallbacks.push(onRejected); }}}Copy the code

The above constructor ensures delayed binding of the callback functions and ensures that the callback functions are used in binding order. All promises are the same, except for the timing of resolve or Reject determined by executor, the executor function that executes immediately. Therefore, the following then method returns the new promisE2 with the same idea, making the executor of the new promise2 strongly relevant to the current promise.

The then method returns a promise

then(onFulfilled, onRejected) {
    console.log('then called')
    const promise2 = new MyPromise((resolve, reject) = > {
        // If the current promise state is pending, wrap the callback into an array,
        // After the promise state transitions, the corresponding callback function is invoked, and the return value is collected to begin parsing promise2
        if (this.state === PENDING) {
            this.onFullfilledCallbacks.push(() = > {
                try {
                    let x = onFulfilled(this.value);
                    this.resolvePromise(promise2, x, resolve, reject);
                }catch(error) { reject(error); }})this.onRejectedCallbacks.push(() = > {
                try {
                    let x = onRejected(this.reason);
                    this.resolvePromise(promise2, x, resolve, reject);
                }catch(error) {
                    reject(error)
                }
            })
        }
        else if (this.state === FULFILLED) {
            try {
                let x = onFulfilled(this.value);
                this.resolvePromise(promise2, x, resolve, reject);
            }catch (error) {
                reject(error)
            }
        }
        else if (this.state === REJECTED) {
            try {
                let x = onRejected(this.value);
                this.resolvePromise(promise2, x, resolve, reject);
            }catch (error) {
                reject(error)
            }
        }
    })
    return promise2;
}
Copy the code

Promise parsing function

resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        reject(new TypeError('Chaining Cycle'));
    }
    if (x && typeof x === 'object' || typeof x === 'function') {
        let used;
        try {
            let then = x.then;
            // x. teng is function. X is a promise
            if (typeof then === 'function') {
                then.call(x, (y) = > {
                    if (used) return;
                    used = true;
                    this.resolvePromise(promise2, y, resolve, reject)
                }, (r) = > {
                    if (used) return;
                    used = true; reject(r); })}else {
                if (used) return;
                used = true; resolve(x); }}catch (error) {
            if (used) return;
            used = true
            reject(error)
        }
    } else{ resolve(x); }}Copy the code

Complete code example

const PENDING = 'PENDING';

const FULFILLED = 'FULFILLED';

const REJECTED = 'REJECTED';



class MyPromise {

    constructor(executor) {

        this.PID = Math.random();   // It is useless for debugging

        this.value = null

        this.reason = null

        this.state = PENDING;

        this.onFullfilledCallbacks = [];

        this.onRejectedCallbacks = [];



        const resolve = (value) => {

            this.value = value

            this.state = FULFILLED

            this.onFullfilledCallbacks.forEach(onFullfilledCallback => {

                onFullfilledCallback(this.value)

            })

            console.log('Promise state is ' + this.state)

        }

        const reject = (reason) => {

            this.reason = reason

            this.state = REJECTED

            this.onRejectedCallbacks.forEach(onRejectedCallback => {

                onRejectedCallback(this.reason)

            })

            console.log('Promise state is ' + this.state)

        }

        try {

            executor(resolve, reject)

        }catch (reason){

            this.reject(reason)

        }

    }



    then(onFulfilled, onRejected) {

        console.log('then called')

        const promise2 = new MyPromise((resolve, reject) => {

            // If the current promise state is still pending, wrap the callback function into the array and wait until the promise state is changed

            // Invoke the appropriate callback function and begin parsing promise2

            if (this.state === PENDING) {

                this.onFullfilledCallbacks.push(() => {

                    try {

                        let x = onFulfilled(this.value);

                        this.resolvePromise(promise2, x, resolve, reject);

                    }catch(error) {

                        reject(error);

                    }

                })

                this.onRejectedCallbacks.push(() => {

                    try {

                        let x = onRejected(this.reason);

                        this.resolvePromise(promise2, x, resolve, reject);

                    }catch(error) {

                        reject(error)

                    }

                })

            }

            else if (this.state === FULFILLED) {

                try {

                    let x = onFulfilled(this.value);

                    this.resolvePromise(promise2, x, resolve, reject);

                }catch (error) {

                    reject(error)

                }

            }

            else if (this.state === REJECTED) {

                try {

                    let x = onRejected(this.value);

                    this.resolvePromise(promise2, x, resolve, reject);

                }catch (error) {

                    reject(error)

                }

            }

        })



        return promise2;

    }



    resolvePromise(promise2, x, resolve, reject) {

        if (promise2 === x) {

            reject(new TypeError('Chaining Cycle'));

        }

        if (x && typeof x === 'object' || typeof x === 'function') {

            let used;

            try {

                let then = x.then;

                // x. teng is function. X is a promise

                if (typeof then === 'function') {

                    then.call(x, (y) => {

                        if (used) return;

                        used = true;

                        this.resolvePromise(promise2, y, resolve, reject)

                    }, (r) => {

                        if (used) return;

                        used = true;

                        reject(r);

                    })

                } else {

                    if (used) return;

                    used = true;

                    resolve(x);

                }

            }catch (error) {

                // If resolvePromise or rejectPromise has been called, ignore the exception

                if (used) return;

                used = true

                reject(error)

            }

        } else {

            resolve(x);

        }

    }

    // The last catch is onRejected

    catch(onRejected) {

        return this.then(null, onRejected);

    }

}

Copy the code