关于javascript:手写Promise从易到难一步一步补完Promise讲解每一行代码的作用

38次阅读

共计 6826 个字符,预计需要花费 18 分钟才能阅读完成。

前言

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

根底构造

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

// 标准 2.1:A promise must be in one of three states: pending, fulfilled, or rejected.
// 三个状态:PENDING、FULFILLED、REJECTED
const PENDING = Symbol();
const FULFILLED = Symbol();
const REJECTED = Symbol();

// 依据标准 2.2.1 到 2.2.3
class _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、REJECTED
const 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、REJECTED
const 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+ 标准

正文完
 0