如何手写一个Promise

  1. Promise 基本结构

我们知道实例化Promise对象时传入一个函数作为执行器,有两个参数(resolve和reject)分别将结果变为成功态和失败态。

据此我们可以写出基本结构:

  function Promise(excutor){
            let self = this;
            self.status = 'pending';    // 状态 等待 => 成功或失败
            self.value = null;          // 成功结果
            self.reason = null;         // 失败原因
 
            function resolve(value){
            
            };
 
            function reject(reason){
            
            };
   }
  1. then方法

每一个Promise实例都有一个then方法,它用来处理异步返回的结果,它是定义在原型上的方法。

// 一个成功 一个失败
Promise.prototype.then = function(onFulfilled, onRejected){
    
}

当我们自己实例化一个Promise时,其执行器函数(executor)会立即执行,这是一定的:

 let demo = new Promise((resolve, reject)=>{ 
                console.log("在座的各位彭于晏,大家好");
            })

因此,当实例化Promise时,构造函数中就要马上调用传入的excutor函数执行,为了防止出错,加入try,catch:

// 此段代码放在Promise方法里
try {
        excutor(resolve, reject);
    } catch(err) {
        reject(err)
    }

3. 状态管理

已经是成功态或是失败态不可再更新状态,Promise 规范中规定:当Promise对象已经由pending状态改变为了成功态(resolved)或是失败态(rejected)就不能再次更改状态。

因此我们在更新状态时要判断,如果当前状态是pending(等待态)才可更新。

function resolve(value){
         if (self.status === 'pending') {
                 self.value = value; //保存成功结果
                 self.status = 'fulfilled';
          }
 }
 
 function reject(reason){
          if (self.status === 'pending') {
                  self.reason = reason;
                  self.status = 'rejected';
           }
}

以上可以看到:

1. 在resolve和reject函数中分别加入了判断

2. 只有当前状态是pending才可进行操作

3. 将成功的结果和失败的原因都保存到对应的属性上。之后将state属性置为更新后的状态

  1. then方法完善

当Promise的状态发生了改变,不论是成功或是失败都会调用then方法。所以,then方法的实现也很简单,根据state状态来调用不同的回调函数即可。

 Promise.prototype.then = function(onFulfilled, onRejected){
            onFulfilled = typeof onFulfilled === 'function' ? 
            onFulfilled : function (data) { resolve(data) }
            onRejected = typeof onRejected === 'function' ? 
            onRejected : function (err) { throw err }
 }

代码写到这里似乎基本功能都实现了,可是还有一个很大的问题:目前此Promise还不支持异步代码,如果Promise中封装的是异步操作,then方法无能为力,例:

 let demo = new Promise((resolve, reject)=>{ 
                console.log("在座的各位彭于晏大家好");
            
                setTimeout(()=>{
                    resolve(1);
                }, 500)
            })
        // 没有打印
        demo.then(data => console.log(data)); 

第一步是正常打印的,但是 setTimeout 里面的代码不会执行,最后一行也不会打印 “1”。

问题所在:原因是 setTimeout 函数使得 resolve 是异步执行的,有延迟,当调用then方法的时候,此时此刻的状态还是等待态(pending),因此then方法即没有调用 onFulfilled 也没有调用 onRejected。

  1. 发布订阅模式登场

这个问题如何解决?我们可以参照发布订阅模式,在执行 then 方法时如果还在等待态(pending),就把回调函数临时寄存到一个数组里,当状态发生改变时依次从数组中取出执行就好,是不是很Nice!

清楚这个思路我们开始实现它,首先在类上新增两个Array类型的数组,用于存放回调函数:

function Promise(excutor){
    // 其它代码略
     self.onFulfilledCallbacks = [];
     self.onRejectedCallbacks = [];
}

在then方法里添加进去:

Promise.prototype.then = function(onFulfilled, onRejected){
           // 其它代码略
            let self = this;
 
            
            if(self.status === "pending"){
                 self.onFulfilledCallbacks.push(onFulfilled);
                 self.onRejectedCallbacks.push(onRejected);
            }
}

在resolve() 与reject() 里逐一遍历:

function resolve(value){
    if (self.status === 'pending') {
        // 其它代码略
        self.onFulfilledCallbacks.forEach(item => item(value));
    }
}
 
 
function reject(reason){
    if (self.status === 'pending') {
        // 其它代码略
        self.onRejectedCallbacks.forEach(item => item(reason));
    }
}

到这一步,异步也搞定了!再是如何实现链式调用。

6. 经典的链式调用实现

then方法接收的两个函数中,可以通过return把值传给下一个步,也可以返回一个新的Promise把值传给下一步,then方法执行的时候有个特点:

为了保证链式调用,上一次then中不管你是成功还是失败都会把参数作为下一个then中成功时回调的参数

话不多说,开干(只需要再new一个Promise即可):

 Promise.prototype.then = function(onFulfilled, onRejected){
             onFulfilled = typeof onFulfilled === 'function' ? 
             onFulfilled : function (data) { resolve(data) }
             onRejected = typeof onRejected === 'function' ? 
             onRejected : function (err) { throw err }
             let self = this;
            if(self.status === "fulfilled"){
                return new Promise((resolve, reject) => {
                    try{
                        let x = onFulfilled(self.value);
                        x instanceof Promise ? x.then(resolve, reject) : resolve(x);
                    } catch (err){
                        reject(err)
                    }
 
                })
            }
 
            if(self.status === "rejected"){
                return new Promise((resolve, reject) => {
                    try{
                        let x = onRejected(self.reason);
                        x instanceof Promise ? x.then(resolve, reject) : resolve(x);
                    } catch (err){
                        reject(err)
                    }
                })
            }
 
            if(self.status === "pending"){
                return new Promise((resolve, reject) => {
                    self.onFulfilledCallbacks.push(()=>{
                        let x = onFulfilled(self.value);
                        x instanceof Promise ? x.then(resolve, reject) : resolve(x);
                    })
                    self.onRejectedCallbacks.push(()=>{
                        let x = onRejected(self.reason);
                        x instanceof Promise ? x.then(resolve, reject) : resolve(x);
                    })
                })
            }
        }
 }

7. 最后的catch方法

其实catch方法就是then方法的简写

Promise.prototype.catch = function(fn){
            return this.then(null, fn);
        }

现在,一个完整的Promise就实现了,大家可以去试试。有啥不对的可以在评论区指出,感谢。

考虑到一些新手基础可能差点,本篇文章可以说讲解的非常非常详细了,拆分了多个小步骤,一步一步来。相信大家跟着思路走都是能搞定的。排版啥的也都尽量搞得好看些,提高各位客官的用户体验。

javascript
67 views
Comments
登录后评论
Sign In
·

为啥要手写?Promise 是 ECMAScript 6 规范的一部分,已经有很多的第三方库提供了与 Promise 相关的实现。bluebird就是一个功能丰富而且性能优异的 Promise 实现。你可以在未实现 Promise 规范的环境下抢先体验 Promise,优化自己的代码,没必要自己手写 grinning