Before you get to know promises, you need to have a few basics up front

Task queue (message queue)

  • A queue is a data structure that holds tasks to be executed
  • The types of tasks in the task queue include mouse scrolling, click, move, timer, websocket, file reading and writing, DOM parsing, style calculation, layout calculation, JS execution, and so on
  • The above tasks are executed in the main thread. Due to the single thread mechanism of JS, a task can only be executed after all the tasks in front of it are executed. It may take a long time for a single task to execute and occupy the main thread.
  • Specific business scenarios: For JS tasks that frequently change DOM elements, different JS interfaces need to be called for each change, resulting in a longer task time. If the task is executed asynchronously, there may be many queued tasks before it is added to the task queue

Therefore, in order to deal with high-priority tasks and solve the problem that the execution time of single-threaded tasks is too long, tasks are divided into macro tasks and micro tasks.

Asynchronous tasks

  • When a process executes a task, it takes a while for the task to return, so it is placed in a module dedicated to asynchronous tasks and continues to execute other modules in the task queue to prevent message queues from blocking.
  • Common asynchronous tasks: timers, Ajax, event bindings, async await, Promise

Microtasks and macro tasks

  • Each task in the task queue is a macro task. In the process of execution, if any microtask is generated, it is added to the microtask queue

Asynchronous requests (such as timeout errors) have many applications

Macro task Micro tasks
Render event promise[then/catch/finally]
request proxy
The script code block MutationObserver
setTimeout process.nextTick
setInterval QueueMicrotask (for creating microtasks)
setimemediate/ I/O async/await

Note: the next macro task will be executed only after all the microtasks in the current macro task are completed, forming an event loop (execution is to push the task onto the execution stack to perform the corresponding function operation).

Promise term

  1. A promise is an object or function that has then methods and behaves according to this specification
  2. Thenable is an object or function that has then methods
  3. Value is the successful value of the promise state, which is also the resolve parameter. It can be a variety of data types, including undefined/thenable or promise
  4. Reason is the reject value when the promise state fails
  5. Exception is an exception thrown with a throw

Promise specification

Promise States

Promises should have three states. Pay attention to the fluid relationship between them.

1. pending

1.1 The initial state can be changed.

1.2 A Promise is in this state before either resolve or Reject.

1.3 Can be fulfilled through the resolve -> depressing state;

1.4 Reject -> Reject;

2. fulfilled

2.1 Final state, immutable.

2.2 A Promise that is resolved becomes this state.

2.3 Must have a value

3. rejected

3.1 Final state, immutable.

3.2 A promise becomes this state when it is rejected

3.3 You must have a Reason

State of the circulation

Tips: To summarize, the promise state flow looks like this

pending -> resolve(value) -> fulfilled

pending -> reject(reason) -> rejected

Implement promises manually

// 2. Define three state types
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 1. Initialize class
class MPromise {
    FULFILLED_CALLBACK_LIST = [];
    REJECTED_CALLBACK_LIST = [];
    _status = PENDING; // Private variables

    constructor(fn) {
        // 3. Set the initial state
        this.status = PENDING;
        this.value = null;
        this.reason = null;

        The promise constructor takes arguments * 5.1. This is a function that accepts two arguments, resolve, reject * 5.2.new promise, and any errors are rejected */
        try{
            // Execute immediately after initialization
            fn(this.resolve.bind(this), this.reject.bind(this));
        }catch(e){
            this.reject(e)
        }
   }

    // 7. Implement get set extra value storage to avoid an infinite loop
    get status() {
        return this._status;
    }
    set status(newStatus) {
        this._status = newStatus;
        switch(newStatus) {
            case FULFILLED:
                this.FULFILLED_CALLBACK_LIST.forEach(callback= > {
                    callback(this.value);
                })
                break;
            case REJECTED:
                this.REJECTED_CALLBACK_LIST.forEach(callback= > {
                    callback(this.reason);
                })
                break; }}// 4. 改 变 status, pending -> pity /rejected; The value, "reason
    resolve(value) {
        if(this.status === PENDING){
            this.value = value;
            this.status = FULFILLED; }}reject(reason) {
        if(this.status === PENDING){
            this.reason = reason;
            this.status = REJECTED; }}// implement the then function
    then(onFulfilled, onRejected) {
        const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > value;
        const realOnRejected = this.isFunction(onRejected) ? onRejected: (reason) = > {
            throw reason;
        }
        const promise2 = new MPromise((resolve, reject) = > {
            / / 8
            const fulfilledMicroTack = () = > {
                queueMicrotask(() = > {
                    try {
                        const x = realOnFulfilled(this.value);
                        this.resolvePromise(promise2, x, resolve, reject)
                    } catch(e) { reject(e); }})}const rejectedMicroTack = () = > {
                queueMicrotask(() = > {
                    try {
                        const x = realOnRejected(this.reason);
                        this.resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }

            switch(this.status) {
                case FULFILLED:
                    fulfilledMicroTack();
                    break;
                case REJECTED:
                    rejectedMicroTack()
                    break;
                case PENDING:
                    this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled);
                    this.REJECTED_CALLBACK_LIST.push(realOnRejected);
                    break; }});return promise2;
    }

    // 10
    catch(onRejected) {
        return this.then(null, onRejected);
    }
    
    / / 9.
    resolvePromise(promise2, x, resolve, reject) {
        if(promise2 === x) {
            return reject(new TypeError("The promise and the return value are the same"))}if(x instanceof MPromise) {
            queueMicrotask(() = > {
                x.then((y) = > {
                    this.resolvePromise(promise2, y, resolve, reject); })})}else if(typeof x==='object' || this.isFunction(x)) {
            if(x === null) {
                return resolve(x);
            }
            let then = null;
            try {
                then = x.then;
            } catch (e) {
                return reject(e);
            }

            if(this.isFunction(then)) {
                // Limit the number of calls
                let called = false;
                try {
                    then.call(x,(y) = > {
                        if(called) {
                            return;
                        }
                        called = true;
                        this.resolvePromise(promise2,y,resolve,reject);
                    },(r) = > {
                        if(called){
                            return;
                        }
                        called = true; reject(r); })}catch (error) {
                    if(called) {
                        return; } reject(error); }}else{ resolve(x); }}else{
            resolve(x)
        }
    }

    // Check whether the received argument is a function
    isFunction (param) {
        return typeof param === 'function'
    }
    
    // Static method
    static resolve(value){
        if(value instanceof MPromise){
            return value;
        }
        return new MPromise((resolve) = >{ resolve(value); })}static reject(reason){
        return new MPromise((resolve,reject) = >{ reject(reason); }}})Copy the code

Call Promise to summarize the problem

1. Why is the output value of promise resolve undefined

const test = new MPromise((resolve, reject) = > {
    setTimeout(() = > {
        resolve(111);
    }, 1000);
}).then((value) = > {
    console.log('then');
});

setTimeout(() = > {
    console.log(test);
}, 3000)
Copy the code
  1. Because we’re writing it this way, we’re saying return undefined in dot then, so the final value is undefined.
  2. If you explicitly return a value, it is not undefined; Such as the return value.

2. Why do I print promise in the catch callback and show the state as pending

const test = new MPromise((resolve, reject) = > {
    setTimeout(() = > {
        reject(111);
    }, 1000);
}).catch((reason) = > {
    console.log(`[catch] reason=${reason}`);
    console.log(test)  //status: pending
});

setTimeout(() = > {
    console.log(test); //status: fulfilled
}, 3000)
Copy the code
  1. The catch function returns a new promise, and test is the new promise
  2. In the catch callback, when the promise is printed, the entire callback is not complete (so the state is pending), and the state changes only when the entire callback is complete
  3. The callback function of catch, if successfully executed, will change the state of this new Promise to depressing

Implement the promise.all method

Promise.all = (arr) = > {
    return new Promise((resolve, reject) = > {
        let res = [];
        let count = 0;
        for (let i = 0; i < arr.length; i++) {
            //
            Promise.resolve(arr[i]).then((value) = > {
            	// error 1: do not use res.push(value); Because this is asynchronous, if you use push, the array will be sequential chaos, so use assignment to avoid
                res[i] = value;
               	Res.length === arr.length; If arr[1] is executed first, the length of arr. Length will be 2. Therefore, a record is required to determine whether the record value is equal to arr
                count ++;
                if(count === arr.length) {
                    resolve(res);
                }
            }).catch((reason) = >{ reject(reason); })}})}Copy the code

Realize the promise. Prototype. Finally

Promise.prototype.finally = function(callback) {
    return this.then((value) = > {
        return Promise.resolve(callback()).then(() = > value);
    }),
    (err) = > {
        return Promise.resolve(callback()).then(() = > {throwerr}); }}Copy the code

To implement the promise.allseettled method, you need to return the status and results of all promises

function PromiseAllSettled(promiseArray) {
    return new Promise(function (resolve, reject) {
        // Determine the parameter type
        if (!Array.isArray(promiseArray)) {
            return reject(new TypeError('arguments muse be an array'))}let counter = 0;
        const promiseNum = promiseArray.length;
        const resolvedArray = [];
        for (let i = 0; i < promiseNum; i++) {
            Promise.resolve(promiseArray[i])
                .then((value) = > {
                    resolvedArray[i] = {
                        status: 'fulfilled',
                        value
                    };

                })
                .catch(reason= > {
                    resolvedArray[i] = {
                        status: 'rejected',
                        reason
                    };
                })
                .finally(() = > {
                    counter++;
                    if (counter == promiseNum) {
                        resolve(resolvedArray)
                    }
                })
        }
    })
}

Copy the code