手动实现一个满足promises-aplus-tests的Promise

Promise 简介

(该部分转载自 blog.csdn.net/u010576399/… ) Promise对象是CommonJS工作组提出的一种规范,目的是为异步操作提供统一接口.

那么,什么是Promises?

首先,它是一个对象,也就是说与其他JavaScript对象的用法,没有什么两样;其次,它起到代理作用(proxy),充当异步操作与回调函数之间的中介。它使得异步操作具备同步操作的接口,使得程序具备正常的同步运行的流程,回调函数不必再一层层嵌套。

简单说,它的思想是,每一个异步任务立刻返回一个Promise对象,由于是立刻返回,所以可以采用同步操作的流程。这个Promises对象有一个then方法,允许指定回调函数,在异步任务完成后调用。

比如,异步操作f1返回一个Promise对象,它的回调函数f2写法如下:

(new Promise(f1)).then(f2);
复制代码

这种写法对于多层嵌套的回调函数尤其方便。

// 传统写法
step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // ...
      });
    });
  });
});

// Promises的写法
(new Promise(step1))
  .then(step2)
  .then(step3)
  .then(step4);
复制代码

从上面代码可以看到,采用Promises接口以后,程序流程变得非常清楚,十分易读。

注意,为了便于理解,上面代码的Promise对象的生成格式,做了简化。

总的来说,传统的回调函数写法使得代码混成一团,变得横向发展而不是向下发展。Promises规范就是为了解决这个问题而提出的,目标是使用正常的程序流程(同步),来处理异步操作。它先返回一个Promise对象,后面的操作以同步的方式,寄存在这个对象上面。等到异步操作有了结果,再执行前期寄放在它上面的其他操作。

Promises原本只是社区提出的一个构想,一些外部函数库率先实现了这个功能。ECMAScript 6将其写入语言标准,因此目前JavaScript语言原生支持Promise对象。

手动实现一个满足promises-aplus-tests的Promise

promisesaplus.com/ 是一个介绍promise如何实现的一个网站. 根据该网站提供的介绍信息, 我们可以尝试自己写一个promise并使用提供的promises-aplus-tests工具对所写的promise进行测试.

Step1

首先 实现promise最基本的功能: 即在promise创建以后执行执行器中的代码,在then的时候相应的函数得以执行. 需要注意的是,当执行器中有错误抛出的时候,应该捕获错误并直接执行reject

//实现功能: 当执行器中调用resolve(),则then中只执行onFulfiled方法,执行器中调用reject(),则then中只执行onRejected方法, 当两个方法都有的时候,以先执行的方法为准,后执行的方法对then不产生影响.
function Promise (executor) {   //执行器 
    let self = this;
    self.status = 'pending';   //引入状态,对两个方法都有的情况进行区分
    self.value = undefined;    //默认值
    self.reason = undefined;
    function resolve(data_value) {
        if(self.status === 'pending') {
            self.status = 'resolved';
            self.value = data_value;
        }
    }
    function reject(data_reason) {
        if(self.status === 'pending') {
            self.status = 'rejected';
            self.reason = data_reason;
        }
    }
    //如果executor是同步代码 进行try catch获取其中的异常 如果有异常 把异常传到reject
    try {
        executor(resolve, reject);   
    } catch (e) {  
        reject(e);                 //调用reject并把捕获的error作为参数传给reject
    }
}

Promise.prototype.then = function (onFulfiled, onRejected) {
    let self = this;
    if(self.status === 'resolved') {
        onFulfiled(self.value);
    }
    if(self.status === 'rejected') {
        onRejected(self.value);
    }
}
复制代码

step2

那么问题来了, 如果执行器中有异步代码, 上面的实现方法就会出问题,因为在执行

executor(resolve, reject);   
复制代码

的时候,异步代码不会立即执行, then中没有判断异步代码是否已经执行的机制. 我们的解决方案是: 在then中判定状态是否为pending,如果状态为pending(此时为构造函数new Promise()执行期间), 则把then中的执行函数onFulfiled, onRejected先存入队列(用array实现),当状态改变后执行这些方法.

function Promise (executor) {   //执行器 
    let self = this;
    self.status = 'pending';
    self.value = undefined;    //默认值
    self.reason = undefined;
    self.onResolvedCallbacks = [];   //存放then成功的回调 数组
    self.onRejectedCallbacks = [];   //存放then失败的回调 数组
    function resolve(data_value) {
        if(self.status === 'pending') {
            self.status = 'resolved';
            self.value = data_value;
            self.onResolvedCallbacks.forEach(function(fn) {   //调用resolve的时候执行保存在onRejectedCallbacks的函数
                fn();
            })
        }
    }
    function reject(data_reason) {
        if(self.status === 'pending') {
            self.status = 'rejected';
            self.reason = data_reason;
            self.onRejectedCallbacks.forEach(function(fn) {   //调用resolve的时候执行保存在onRejectedCallbacks的函数
                fn();
            })
        }
    }
    
    try {
        executor(resolve, reject);   //当executor中有异步代码时 这部分不会立即执行(但是前面的部分在new的时候还是会执行)
    } catch (e) {  
        reject(e);                 
    }
}

Promise.prototype.then = function (onFulfiled, onRejected) {
    let self = this;
    if(self.status === 'resolved') {
        onFulfiled(self.value);
    }
    if(self.status === 'rejected') {
        onRejected(self.reason);
    }
    //当调用then时可能没成功 也没失败
    if(self.status === 'pending') {             //此时没有resolve也没有reject
        self.onResolvedCallbacks.push(function(){       //用数组是为了保证在异步时有多次promise.then的情况 
            onFulfiled(self.value);
        });
        self.onRejectedCallbacks.push(function(){
            onRejected(self.reason);
        });
    }
}
复制代码

step3

到现在为止,我们仅仅完成了promise最基本的功能, 但是promise中一个重要的功能: then的链式调用尚未实现. 解决的思路类似于jquery的链式调用, 区别则是:jquery是返回this 这里则是返回一个新的promise. 我们在then中新建一个变量promise2. 以

self.status === 'resolved'
复制代码

情况为例 将之前的代码修改为:

if(self.status === 'resolved') {
        promise2 = new Promise(function(resolve, reject){  //将前一次then中的执行函数放入新的Promise的executor得到promise2作为下次then的返回值
            onFulfiled(self.value);           //注意这时返回的promise的执行器执行的是onFulfiled函数 而不是resolve或者reject. 
        }) 
    }
复制代码

但是以上代码中, promise2在创建的时候, 并没有设定resolve/reject的规则,因此只能算作半成品. 根据规定, 如果onFulfile/onRejected有返回值, 则将返回值作为resolve/reject的参数传入,这样,下一次.then就有状态,不再是无根之木. 根据返回值的不同, 又将返回值分别以普通值和promise进行分别处理. 为此我们引入了一个统一的处理方法,以resolve()为例, 对应的处理方法我们取名为resolvePromise(promise2, x, resolve, reject) 其中x为onFulfiled的返回值. resolvePromise代码如下所示:

function resolvePromise(promise2, x, resolve, reject) {
    //有可能这里返回的x是别人的promise 要尽可能允许其他人乱写 
    if(promise2 === x) {//这里应该报一个循环引用的类型错误
        return reject(new TypeError('循环引用'));
    }
    //看x是不是一个promise promise应该是一个对象
    if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {
        //可能是promise 看这个对象中是否有then 如果有 姑且作为promise 用try catch防止报错
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, function(y) {
                    //成功
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) {
                    //失败
                    reject(err);
                })
            } else {
                resolve(x)             //如果then不是函数 则把x作为返回值.
            }
        } catch (e) {
            reject(e)
        }
        
    } else {  //普通值
        return resolve(x)
    }
}
复制代码

相应的

self.status === 'resolved'
复制代码

时的代码则为:

if(self.status === 'resolved') {
        promise2 = new Promise(function(resolve, reject){  
            let x = onFulfiled(self.value);    
            resolvePromise(promise2, x, resolve, reject);
        }) 
    }
复制代码

为例保证代码的通用性,考虑到有可能其他人的promise写的不正确,可能会既调用成功又调用失败的情况, 我们应当在代码中对返回的promise进行判断: 如果两个都调用 先调用谁 另一个忽略掉. 为此引入一个变量called.

function resolvePromise(promise2, x, resolve, reject) {
    //有可能这里返回的x是别人的promise 要尽可能允许其他人乱写 
    if(promise2 === x) {//这里应该报一个循环引用的类型错误
        return reject(new TypeError('循环引用'));
    }
    //看x是不是一个promise promise应该是一个对象
    let called;  //表示是否调用过成功或者失败
    if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {
        //可能是promise 看这个对象中是否有then 如果有 姑且作为promise 用try catch防止报错
        try {
            let then = x.then;
            if (typeof then === 'function') {
                //成功
                then.call(x, function(y) {
                    if (called) return
                    called = true;
                    resolvePromise(promise2, y, resolve, reject)
                }, function(err) {
                    if (called) return
                    called = true;
                    reject(err);
                })
            } else {
                resolve(x)             //如果then不是函数 则把x作为返回值.
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e)
        }
        
    } else {  //普通值
        return resolve(x)
    }
}
复制代码

Step4

到目前为止, promise的大框架基本完成. 接下来需要解决的两个小问题是值的穿透以及onFulfiled/onRejected的异步执行问题. 值的穿透的含义就是, 当then中使用没有任何方法, onFulfiled()中的data自动作为返回值. 实现起来也很简单, 在then的定义的最开始部分做一个判断:

Promise.prototype.then = function (onFulfiled, onRejected) {
    //成功和失败默认不传, 给一个默认函数 可以实现值的穿透
    onFulfiled = typeof onFulfiled === 'function'? onFulfiled:function(value) {
        return value;
    }
    onRejected = typeof onRejected === 'function'? onRejected:function(err) {
        throw err;           //在值的穿透的情况下 应该走下一个then的onRejected而不是onFulfiled 保证逻辑的一致性
    }
    ..... 
}
复制代码

对于onFulfild/onRejected异步执行的问题, 则是在

if(self.status === 'resolved') {
    promise2 = new Promise(function(resolve, reject){
    let x = onFulfiled(self.value);                             
    resolvePromise(promise2, x, resolve, reject) 
    ...
    }
}
复制代码

代码块使用setTimeout. 这时带来的副作用就是之前在promise构造函数中的代码块

try {
        executor(resolve, reject);   
    } catch (e) {  
        reject(e);                 
    }
复制代码

不能捕获到setTimeout中的异步函数, 因此需要在setTimeout中也需要包一层try/catch:

if(self.status === 'resolved') {
        promise2 = new Promise(function(resolve, reject){  
            setTimeout(function(){                          //用setTimeOut实现异步
                try {
                    let x = onFulfiled(self.value);        //x可能是普通值 也可能是一个promise, 还可能是别人的promise                               
                    resolvePromise(promise2, x, resolve, reject)  //写一个方法统一处理 
                } catch (e) {
                    reject(e);                                        
                }
                
            }) 
        }) 
    }
复制代码

step5

最终代码如下所示:

function Promise (executor) {   
    let self = this;
    self.status = 'pending';
    self.value = undefined;    
    self.reason = undefined;
    self.onResolvedCallbacks = [];   
    self.onRejectedCallbacks = [];   
    function resolve(data_value) {
        if(self.status === 'pending') {
            self.status = 'resolved';
            self.value = data_value;
            self.onResolvedCallbacks.forEach(function(fn) {  
                fn();
            })
        }
    }
    function reject(data_reason) {
        if(self.status === 'pending') {
            self.status = 'rejected';
            self.reason = data_reason;
            self.onRejectedCallbacks.forEach(function(fn) {  
                fn();
            })
        }
    }
    try {
        executor(resolve, reject);   
    } catch (e) {  
        reject(e);                 
    }
}

function resolvePromise(promise2, x, resolve, reject) {
    //有可能这里返回的x是别人的promise 要尽可能允许其他人乱写 
    if(promise2 === x) {//这里应该报一个循环引用的类型错误
        return reject(new TypeError('循环引用'));
    }
    //看x是不是一个promise promise应该是一个对象
    let called;  //表示是否调用过成功或者失败
    if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {
        //可能是promise 看这个对象中是否有then 如果有 姑且作为promise 用try catch防止报错
        try {
            let then = x.then;
            if (typeof then === 'function') {
                //成功
                then.call(x, function(y) {
                    if (called) return        //避免别人写的promise中既走resolve又走reject的情况
                    called = true;
                    resolvePromise(promise2, y, resolve, reject)
                }, function(err) {
                    if (called) return
                    called = true;
                    reject(err);
                })
            } else {
                resolve(x)             //如果then不是函数 则把x作为返回值.
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e)
        }
        
    } else {  //普通值
        return resolve(x)
    }

}

Promise.prototype.then = function (onFulfiled, onRejected) {
    //成功和失败默认不传给一个函数
    onFulfiled = typeof onFulfiled === 'function'? onFulfiled:function(value) {
        return value;
    }
    onRejected = typeof onRejected === 'function'? onRejected:function(err) {
        throw err;
    }
    let self = this;
    let promise2;  //新增: 返回的promise
    if(self.status === 'resolved') {
        promise2 = new Promise(function(resolve, reject){  
            setTimeout(function(){                          //用setTimeOut实现异步
                try {
                    let x = onFulfiled(self.value);        //x可能是普通值 也可能是一个promise, 还可能是别人的promise                               
                    resolvePromise(promise2, x, resolve, reject)  //写一个方法统一处理 
                } catch (e) {
                    reject(e);                                        
                }
                
            }) 
        }) 
    }
    if(self.status === 'rejected') {
        promise2 = new Promise(function(resolve, reject){
            setTimeout (function() {
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e);
                }
            })
        }) 
    }
    
    if(self.status === 'pending') {            
        promise2 = new Promise (function(resolve, reject) {   
            self.onResolvedCallbacks.push(function(){   
                setTimeout(function(){
                    try {
                        let x = onFulfiled(self.value); 
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }   
                })
            });
            self.onRejectedCallbacks.push(function(){
                setTimeout(function(){
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e);
                    } 
                })   
            });
        })
    }
    return promise2;
}
复制代码

使用promises-aplus-tests对该方法进行测试, 最终测试得到通过. 如图所示:

test result

当然, 该promise相对于原生的promise还有一些不同,比如没有实现catch功能,没有静态方法等. 这些部分我们将下次进行详细讨论.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值