前言

手写Promise始终是前端童鞋十分头疼的问题,也是面试的高频题。网上有很多手写Promise的博客,但大部分都存在或多或少的问题。
上面咱们依据A+标准,手写一个Promise

根底构造

在此局部,先把Promise的根底构造写进去。
间接上代码

// 标准2.1:A promise must be in one of three states: pending, fulfilled, or rejected.// 三个状态:PENDING、FULFILLED、REJECTEDconst PENDING = Symbol();const FULFILLED = Symbol();const REJECTED = Symbol();// 依据标准2.2.1到2.2.3class _Promise {    constructor(executor) {        // 默认状态为 PENDING        this.status = PENDING;        // 寄存胜利状态的值,默认为 undefined        this.value = undefined;        // 寄存失败状态的值,默认为 undefined        this.reason = undefined;        // 胜利时,调用此办法        let resolve = (value) => {            // 状态为 PENDING 时才能够更新状态,避免 executor 中调用了两次 resovle/reject 办法            if (this.status === PENDING) {                this.status = FULFILLED;                this.value = value;            }        };        // 失败时,调用此办法        let reject = (reason) => {            // 状态为 PENDING 时才能够更新状态,避免 executor 中调用了两次 resovle/reject 办法            if (this.status === PENDING) {                this.status = REJECTED;                this.reason = reason;            }        };        try {            // 立刻执行,将 resolve 和 reject 函数传给使用者            executor(resolve, reject);        } catch (error) {            // 产生异样时执行失败逻辑            reject(error);        }    }    // 蕴含一个 then 办法,并接管两个参数 onFulfilled、onRejected    then(onFulfilled, onRejected) {        if (this.status === FULFILLED) {            onFulfilled(this.value);        }        if (this.status === REJECTED) {            onRejected(this.reason);        }    }}export default _Promise;

接下来咱们用测试代码测一下

const promise = new _Promise((resolve, reject) => {        resolve('胜利');        setTimeout(() => {            console.log('settimeout1');        }, 0);    })        .then(            (data) => {                console.log('success', data);                setTimeout(() => {                    console.log('settimeout2');                }, 0);            },            (err) => {                console.log('faild', err);            }        )        .then((data) => {            console.log('success2', data);        });

控制台打印

能够看到,在executor办法中的异步行为在最初才执行
而且如果把resolve办法放到setTimeout中,会无奈执行
这当然是不妥的。
接下来咱们优化一下异步

executor办法中的异步

在上一大节中,咱们将resolve的后果值寄存到了this.value里。
优化后的代码如下:

// 标准2.1:A promise must be in one of three states: pending, fulfilled, or rejected.// 三个状态:PENDING、FULFILLED、REJECTEDconst PENDING = Symbol();const FULFILLED = Symbol();const REJECTED = Symbol();class _Promise {    constructor(executor) {        this.status = PENDING;        this.value = undefined;        this.reason = undefined;        // 寄存胜利的回调        this.onResolvedCallbacks = [];        // 寄存失败的回调        this.onRejectedCallbacks = [];        // 这里应用数组,是因为如果屡次调用then,会把办法都放到数组中。        // 然而目前这个版本还不反对then的链式调用        let resolve = (value) => {            if (this.status === PENDING) {                this.status = FULFILLED;                this.value = value;                // 顺次将对应的函数执行                // 在此版本中,这个数组实际上长度最多只为1                this.onResolvedCallbacks.forEach(fn => fn());            }        }        let reject = (reason) => {            if (this.status === PENDING) {                this.status = REJECTED;                this.reason = reason;                // 顺次将对应的函数执行                // 在此版本中,这个数组实际上长度最多只为1                this.onRejectedCallbacks.forEach(fn => fn());            }        }        try {            executor(resolve, reject)        } catch (error) {            reject(error)        }    }    then(onFulfilled, onRejected) {        if (this.status === FULFILLED) {            onFulfilled(this.value)        }        if (this.status === REJECTED) {            onRejected(this.reason)        }        // 下面两个分支是:反对resolve函数执行的时候,如果不在异步行为里执行resolve的话,会立刻执行onFulfilled办法        if (this.status === PENDING) {            // 如果promise的状态是 pending,须要将 onFulfilled 和 onRejected 函数寄存起来,期待状态确定后,再顺次将对应的函数执行            this.onResolvedCallbacks.push(() => {                onFulfilled(this.value)            });            // 如果promise的状态是 pending,须要将 onFulfilled 和 onRejected 函数寄存起来,期待状态确定后,再顺次将对应的函数执行            this.onRejectedCallbacks.push(() => {                onRejected(this.reason);            })        }    }}

咱们用测试方法测一下:

const promise = new _Promise((resolve, reject) => {            setTimeout(() => {                console.log('settimeout1');                resolve('胜利');            }, 0);        })        .then(            (data) => {                console.log('success', data);                setTimeout(() => {                    console.log('settimeout2');                }, 0);                return data;            },            (err) => {                console.log('faild', err);            }        )        .then((data) => {            console.log('success2', data);        });

控制台后果:

能够看到,异步程序是正确的,先执行settimeout1,再执行success
然而不反对链式的then调用,也不反对在then中返回一个新的Promise

反对链式调用的Promise

接下来咱们将残缺实现一个反对链式调用的Promis

// 标准2.1:A promise must be in one of three states: pending, fulfilled, or rejected.// 三个状态:PENDING、FULFILLED、REJECTEDconst PENDING = Symbol();const FULFILLED = Symbol();const REJECTED = Symbol();class _Promise {    constructor(executor) {        this.status = PENDING;        this.value = undefined;        this.reason = undefined;        // 寄存胜利的回调        this.onResolvedCallbacks = [];        // 寄存失败的回调        this.onRejectedCallbacks = [];        // 这里应用数组,是因为如果屡次调用then,会把办法都放到数组中。        // 然而目前这个版本还不反对then的链式调用        let resolve = (value) => {            if (this.status === PENDING) {                this.status = FULFILLED;                this.value = value;                // 顺次将对应的函数执行                // 在此版本中,这个数组实际上长度最多只为1                this.onResolvedCallbacks.forEach(fn => fn());            }        }        let reject = (reason) => {            if (this.status === PENDING) {                this.status = REJECTED;                this.reason = reason;                // 顺次将对应的函数执行                // 在此版本中,这个数组实际上长度最多只为1                this.onRejectedCallbacks.forEach(fn => fn());            }        }        try {            // 立刻执行executor办法            executor(resolve, reject)        } catch (error) {            reject(error)        }    }        // 这里就是最要害的then办法    then(onFulfilled, onRejected) {        // 克隆this,因为之后的this就不是原promise的this了        const self = this;                // 判断两个传入的办法是不是funcion,如果不是,那么给一个function的初始值        onnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;        onRejected = typeof onRejected === 'function'?onRejected:reason => { throw new Error(reason instanceof Error ? reason.message:reason) }                // 返回一个新的promise,剩下的逻辑都在这个新的promise里进行        return new _Promise((resolve, reject) => {            if (this.status === PENDING) {                // 如果promise的状态是 pending,须要将 onFulfilled 和 onRejected 函数寄存起来,期待状态确定后,再顺次将对应的函数执行                self.onResolvedCallbacks.push(() => {                    // 应用settimeout模仿微工作                    setTimeout((0 => {                        // self.value是之前存在value里的值                        const result = onFulfilled(self.value);                        // 这里要思考两种状况,如果onFulfilled返回的是Promise,则执行then                        // 如果返回的是一个值,那么间接把值交给resolve就行                        result instanceof _Promise ? result.then(resolve, reject) : resolve(result);                    }, 0)                    onFulfilled(self.value)                });                // 如果promise的状态是 pending,须要将 onFulfilled 和 onRejected 函数寄存起来,期待状态确定后,再顺次将对应的函数执行                                                                // reject也要进行一样的事                self.onRejectedCallbacks.push(() => {                    setTimeout(() => {                        const result = onRejected(self.reason);                        // 不同点:此时是reject                        result instanceof _Promise ? result.then(resolve, reject) : reject(result);                    }, 0)                })            }                        // 如果不是PENDING状态,也须要判断是不是promise的返回值            if (self.status === FULFILLED) {                setTimeout(() => {                    const result = onFulfilled(self.value);                    result instanceof _Promise ? result.then(resolve, reject) : resolve(result);                });            }            if (self.status === REJECTED) {                setTimeout(() => {                    const result = onRejected(self.reason);                    result instanceof _Promise ? result.then(resolve, reject) : reject(result);                })            }                })        // 到这里,最难的then办法曾经写完了    }}

额定补充

catch、动态resolve、动态reject办法

catch办法的个别用法是
new _Promise(() => {...}).then(() => {...}).catch(e => {...})
所以它是一个和then同级的办法,它实现起来非常简单:

class _Promise{    ...    catch(onRejected) {        return this.then(null, onRejected);    }}

动态resolve、动态reject的用法:
_Promise.resolve(() => {})
这样能够间接返回一个_Promise
这块的实现,参考then中返回_Promise的那一段,就能实现
reject相似

class _Promise{    ...        static resolve(value) {        if (value instanceof Promise) {            // 如果是Promise实例,间接返回            return value;        } else {            // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED            return new Promise((resolve, reject) => resolve(value));        }    }    static reject(reason) {        return new Promise((resolve, reject) => {            reject(reason);        })    }}

优化setTimeout变成微工作

最初再说一个对于微工作的
setTimeout毕竟是个宏工作,咱们能够用MutationObserver来模仿一个微工作,只有将上面的nextTick办法替换setTimeout办法即可

function nextTick(fn) {  if (process !== undefined && typeof process.nextTick === "function") {    return process.nextTick(fn);  } else {    // 实现浏览器上的nextTick    var counter = 1;    var observer = new MutationObserver(fn);    var textNode = document.createTextNode(String(counter));    observer.observe(textNode, {      characterData: true,    });    counter += 1;    textNode.data = String(counter);  }}

这个办法的原理不难看懂,就是在dom里创立了一个textNode,用MutationObserver监控这个node的变动。在执行nextTick办法的时候手动批改这个textNode,触发MutationObserver的callback,这个callback就会在微工作队列中执行。
留神MutationObserver的兼容性。

总结

我个人感觉残缺了解Promise的源码还是比拟考验代码功底的,一开始倡议把源码放在编译器里一点一点调试着看,如果切实不晓得怎么下手,也能够把代码背下来,缓缓咀嚼。实际上,背下来之后,人脑对这个货色会有一个迟缓的了解过程,到了某一天会感觉豁然开朗。

参考文章

https://zhuanlan.zhihu.com/p/183801144

Promise/A+标准