PromiseA+规范/原理 及 实现方式

1,563 阅读8分钟

在了解promise之前,需要有一些前置基本知识点

任务队列(消息队列)

  • 就是一个队列的数据结构,里面存放要执行的任务
  • 任务队列的任务类型有鼠标滚动、点击、移动、计时器、websocket、文件的读写、DOM解析、样式计算、布 局计算、js的执行等等
  • 上述任务都在主线程中执行,由于js单线程的机制,一个任务得等它前面的任务都执行完毕它才能执行,可能会遇到单个任务执行时间过长,一直占用主线程的情况。
  • 具体的业务场景:对于频繁改变DOM元素的js任务来说,每次变化都需要调用不同的js接口,导致任务的时间拉长,而如果把这个任务做成异步执行,在添加到任务队列之前,它前面也可能存在很多任务的排队 所以,为了处理高优先级的任务,解决单线程任务执行时间过长的问题,将任务切分为宏任务和微任务.

异步任务

  • 在进程执行某个任务时,任务要等待一段时间才能返回,所以就把这个任务放到专门处理异步任务的模块,继续执行任务队列中的其他模块,防止消息队列发生阻塞。
  • 常见的异步任务:定时器、ajax、事件绑定、async await、promise

微任务和宏任务

  • 任务队列中的每一个任务都是宏任务,在执行的过程中,如果有微任务产生,就添加到微任务队列中去

异步请求(如超时报错等功能 具有很多的应用场景)

宏任务微任务
渲染事件promise[then/catch/finally]
请求proxy
script代码块MutationObserver
setTimeoutprocess.nextTick
setIntervalqueueMicrotask(用来创建微任务)
setimemediate/ I/Oasync/await

注意:当前宏任务里的微任务全部执行完,才会执行下一个宏任务,构成一个事件循环(执行都是把任务压入执行栈来执行对应的函数操作的)

Promise 术语

  1. promise 是一个有then方法的对象或者是函数,行为遵循本规范
  2. thenable 是一个有then方法的对象或者是函数
  3. value 是promise状态成功时的值,也就是resolve的参数, 包括各种数据类型, 也包括undefined/thenable或者是 promise
  4. reason 是promise状态失败时的值, 也就是reject的参数, 表示拒绝的原因
  5. exception 是一个使用throw抛出的异常值

Promise 规范

WeChat87908fc531c7ea5c72058a316031c22a.png

Promise States

promise应该有三种状态. 要注意他们之间的流转关系.

1. pending

1.1 初始的状态, 可改变.

1.2 一个promise在resolve或者reject前都处于这个状态。

1.3 可以通过 resolve -> fulfilled 状态;

1.4 可以通过 reject -> rejected 状态;

2. fulfilled

2.1 最终态, 不可变.

2.2 一个promise被resolve后会变成这个状态.

2.3 必须拥有一个value值

3. rejected

3.1 最终态, 不可变.

3.2 一个promise被reject后会变成这个状态

3.3 必须拥有一个reason

状态流转

Tips: 总结一下, 就是promise的状态流转是这样的

pending -> resolve(value) -> fulfilled

pending -> reject(reason) -> rejected

then

promise应该有个then方法,用来访问最终的结果,无论是value还是reason。

promise.then(onFulfilled, onRejected)

1、参数要求

  • onFulfilled必须是函数类型,可选,如果不是函数,应该被忽略
  • onRejected必须是函数类型,可选,如果不是函数,应该被忽略

2、onFulfilled特性

  • 在promise变为fulfilled时,应该调用onFulfilled,参数是value
  • 在promise变成fulfilled之前,不应该被调用
  • 只能被调用一次(实现时需使用变量来限制执行次数)

3、onRejected特性

  • 在promise变成rejected时,应该调用onRejected,参数是reason
  • 在promise变成rejected之前,不应该被调用
  • 只能被调用一次

4、onFulfilled和onRejected应该是微任务

在执行上下文堆栈仅包含平台代码之前,不得调onFulfilled 或 onRejected函数,onFulfilled 和 onRejected 必须被作为普通函数调用(即非实例化调用,这样函数内部 this 非严格模式下指向 window),使用queueMicrotask或者setTimeout来实现微任务的调用

5、then方法可以被调用多次

  • promise状态变成fulfilled后,所有的onFulfilled回调都需要按照then的顺序执行,也就是按照注册顺序执行(实现时用数组存储多个onFulfilled的回调)
  • promise状态变成rejected后,所有的onRejected回调都需要按照then的顺序执行,也就是按照注册顺序执行(实现时用数组存储多个onRejected的回调)

6、then必须返回一个promise

then必须返回一个promise

promise2 = promise1.then(onFulfilled, onRejected)
  • onFulfilled或onRejected执行的结果是x,调用resolvePromise
  • 如果onFulfilled或者onRejected执行时抛出异常e,promise2需要被reject,其reason为e
  • 如果onFulfilled不是一个函数且promise1已经fulfilled,promise2以promise1的value触发onFulfilled
  • 如果onRejected不是一个函数且promise1已经rejected,promise2以promise1的reason触发onRejected

7、Promise的解决过程resolvePromise

resolvePromise(promise2, x, resolve, reject)
  • 如果 x是当前 promise 本身(promise2和x相等),那么reject TypeError
  • 如果 x是另一个 promise(即x是一个promise),那么沿用它的 state 和 result 状态
    • 如果x是pending态,那么promise必须要在pending,直到x变成fulfilled或者rejected
    • 如果x是fulfilled态,用相同的value执行promise
    • 如果x是rejected态,用相同的reason拒绝promise
  • 如果x是一个object或者是一个function(不常见)
    • 首先取x.then的值,let then = x.then
    • 如果取x.then这步出错抛出e,那么以e为reason拒绝promise
    • 如果then是一个函数,将x作为函数的作用域this调用,即then.call(x, resolvePromise, rejectPromise),第一个参数叫resolvePromise,第二个参数叫rejectPromise
      • 如果resolvePromise以y为参数被调用,则执行resolvePromise(promise2, y, resolve, reject)
      • 如果rejectPromise 以 r为参数被调用,则以r为reason拒绝 promise
      • 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
      • 如果调用then抛出异常e:若 resolvePromise 或 rejectPromise 已经被调用,那么忽略,否则以e为reason拒绝promise
    • 如果then不是一个function,以x为value执行promise
  • 如果x不是object或者function,以x为value执行promise

手动实现 Promise

// 2.定义三种状态类型
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 1.初始化class
class MPromise {
    FULFILLED_CALLBACK_LIST = [];
    REJECTED_CALLBACK_LIST = [];
    _status = PENDING; //私有变量

    constructor(fn) {
        // 3.设置初始状态
        this.status = PENDING;
        this.value = null;
        this.reason = null;

        /**
        * 5.promise构造函数入参
        * 5.1.入参是一个函数,函数接收两个参数,resolve,reject
        * 5.2.new promise的时候,就要执行这个函数,并且有任何错误都要被reject出去
        */
        try{
            // 初始化后立马会执行
            fn(this.resolve.bind(this), this.reject.bind(this));
        }catch(e){
            this.reject(e)
        }
   }

    // 7. 监听状态 实现get set 额外的值存储,避免死循环
    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 -> fulfilled/rejected; 入参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;
        }
    }

    // 6.实现then函数
    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
    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)) {
                //限制调用次数
                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)
        }
    }

    // 判断接收的参数是否是函数
    isFunction (param) {
        return typeof param === 'function'
    }
    
    //静态方法
    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);
        })
    }
}

调用 Promise 总结问题

1. 为什么promise resolve了一个value, 最后输出的value值确是undefined

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

setTimeout(() => {
    console.log(test);
}, 3000)
  1. 因为现在这种写法, 相当于在.then里return undefined, 所以最后的value是undefined。
  2. 如果显式return一个值, 就不是undefined了;比如return value.

2. 为什么我在catch的回调里, 打印promise, 显示状态是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)
  1. catch 函数会返回一个新的promise, 而test就是这个新promise
  2. catch 的回调里, 打印promise的时候, 整个回调还并没有执行完成(所以此时的状态是pending), 只有当整个回调完成了, 才会更改状态
  3. catch 的回调函数, 如果成功执行完成了, 会改变这个新Promise的状态为fulfilled

实现promise.all 方法

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) => {
            	//坑点1:切记不能使用res.push(value); 因为此时属于异步,如果使用push,则会将原数组循序混乱,所以使用赋值方式避免
                res[i] = value;
               	//坑点2:切记不能直接使用res.length === arr.length;因为异步如果先执行了arr[1],arr.length长度会直接是2,所以需要记录,在判断记录值是否等于arr.length
                count ++;
                if(count === arr.length) {
                    resolve(res);
                }
            }).catch((reason) => {
                reject(reason);
            })
        }
    })
}

实现promise.prototype.finally 方法

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

实现Promise.allSeettled 方法, 需要返回所有promise的状态和结果

function PromiseAllSettled(promiseArray) {
    return new Promise(function (resolve, reject) {
        //判断参数类型
        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)
                    }
                })
        }
    })
}