前言,前端Promise ES6一个比较重要的知识点,很多面试动不动就问到promise实现原理,手写promise, 我们就来手写promise一系列的代码
用过promise 的都知道,构造函数传一个函数,函数里面是两个函数,也就是决定下一个promise的状态的两个回调方法 ,我们先来看看原生的是怎么搞的
new Promise((resolve,reject)=>{
})
ok,我们也搞一个构造函数,构造函数里面有两个方法,一个是resolve,一个是reject,我们传入一个函数,构造函数里面直接执行这个函数,然后把构造函数里面的方法作为函数参数传递给我们,我们就可以直接调用构造函数里面的方法了
function MyPromise(callback){
callback(resolve,reject);
function resolve(data){
console.log(data);
}
function reject(){
}
}
new MyPromise((resolve,reject)=>{
resolve('data1');
})
好,继续往下,promise 不是有个状态嘛,初始化时是pending,所以我们就定义一个状态变量,有一个成功值value,还有一个失败的reason 初始化 ,还有一个then方法,这个方法我们有两种方式可以定义,一种是放在实例上,还有一种是放在原型上,放在实例上的话,就有点多此一举了 ,需要定义一个then属性,还需要定义一个success,failed的两个回调方法,所以我们考虑放到原型上
MyPromise.prototype.then = function(success,failed){
this.success = success;
this.failed = failed;
}
这样,我们的框架基本上算是搭好了,当调用resolve 方法 会走构造函数里面的resolve方法,在构造函数可以根据对象状态来执行then 方法里面的回调,因为状态是不可逆的,所以resolve里面需要做一下判断
const PENDING = 'pending';
const RESOLVED = 'fulFilled';
const REJECTED = 'rejected';
const FULFILLED = 'fulfilled';
function MyPromise(callback){
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
callback(resolve,reject);
function resolve(data){
if(this.status===PENDING){
this.success = RESOLVED;
this.value = data;
this.onSuccess(data);
}
}
function reject(reason){
if(this.status===PENDING){
this.success = REJECTED;
this.reason = reason;
this.onfailed(reason);
}
}
}
MyPromise.prototype.then = function(onSuccess,onfailed){
this.onSuccess = success;
this.onfailed = failed;
}
new MyPromise((resolve,reject)=>{
resolve('success');
}).then((data)=>{},(err)=>{
console.log(data);
})
我们运行下代码,你会发现说明都没有,应为我们在回调方法里面是直接调用,执行的上下文是window 所以在返回函数的时候需要绑定一下
我们继续执行
onSuccess没有定义,说明在调用 onSuccess 没有执行then方法,要不然原型上赋值肯定能访问得到onSuccess方法,所以这里需要异步调用resolve方法,让then方法先执行,把成功和失败的回调赋值给原型
new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('success');
},1000)
}).then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
})
ok ,基本上的功能是已经完成了,但是有个问题,有如下骚操作。。。。 这样我们的回调只能执行一次 后面的then方法把前面的onSuccess 和 onFailed方法覆盖掉了,所以只会回调的时候只会执行最后一个then里面的代码
var p = new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('success');
},1000)
})
p.then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
})
p.then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
})
p.then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
})
p.then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
})
这样我们的代码就得改,发布订阅者的逻辑是个好东西啊(iOS RAC 就是这个原理),把要执行的回调放到一个数组里面,需要执行的时候全部遍历去执行
MyPromise.prototype.then = function(onSuccess,onfailed){
/*因为原生的Promise then 里面的成功和失败可以不传,如果不传的话,我们默认给一个匿名方法 返回传入的参数
避免报错
*/
var onSuccessFn = typeof onSuccess==='function'? onSuccess:value=>value
var onfailedFn = typeof onfailed==='function'? onfailed:reason=>{throw reason}
let self = this;
if(this.status===FULFILLED){
onSuccessFn(this.value);
}
if(this.status===REJECTED){
onfailedFn(this.reason);
}
if(this.status===PENDING){
self.onFulfilled.push(onSuccessFn);
self.onRejected.push(onfailedFn);
}
}
到此,我们的第一阶段可以告一段落了,我们进入第二阶段,Promise是可以一直then 的,所以我们的then 方法一定要返回一个promise对象,promise 的下一个then走成功还是走失败取决于上一个promise then 走成功还是失败,所以我们要拿到上一个then 方法的返回值,then方法继续改造,这里可能有点绕,仔细读一下
我直接贴代码了
MyPromise.prototype.then = function(onSuccess,onfailed){
/*因为原生的Promise then 里面的成功和失败可以不传,如果不传的话,我们默认给一个匿名方法 返回传入的参数
避免报错
*/
var onSuccessFn = typeof onSuccess==='function'? onSuccess:value=>value;
var onfailedFn = typeof onfailed==='function'? onfailed:reason=>{throw reason};
let self = this;//这里的self 就是上个prosie对象
/*
下一个Promise then的回调的执行状态是根据上一个then的返回值来处理的
所以封装一个方法,统一处理
*/
function resolvePromise(promise2,x,resolve,reject){
if(x===promise2){//如果下一个promise和上一个是同一个对象,那就会无限循环
reject(new TypeError('Chaining cycle'));
}
if(x &&typeof x==='function'|| typeof x==='object'){
/*
1.为什么要尝试获取then方法,因为不获取可能会出问题,比如如下代码直接调用就报错了
const object1 = {};
Object.defineProperty(object1, 'then', {
get:function(){
throw Error('error')
}
});
2.获取到then方法之后,可以尝试去执行下它,这里的then 可能是系统的Promise的then
或者我们自定义的MyPromise的then,还有一种是我们自定义的对象自己搞个then方法那就走完它的then方法就结束了
*/
try {
let then = x.then;
if(typeof then==='function'){
then.call(x,(y)=>{
resolve(y)
},(n)=>{
reject(n)
})
}else{
resolve(x)
}
} catch (error) {
reject(error)
}
}else(
resolve(x)
)
}
let promise2 = new MyPromise((resolve,reject)=>{
if(self.status===FULFILLED){
/*
这里可能不太好理解,x = onSuccessFn() 或者 x = onfailedFn()
就是上个then的返回值 执行onSuccessFn()/onfailedFn() 就会执行当前promise的then里面的
成功或者失败方法,返回值就是我们想要的,我们根据这个返回值的类型对应做一些判断操作
这两句代码需要异步执行,同步调用的话,then执行,promise2还没有返回,这样promise2
就是未定义了 下面逻辑是同理的
*/
setTimeout(()=>{
let x = onSuccessFn(self.value);//这里的x就是上一个then的返回值
resolvePromise(promise2,x,resolve,reject);
},0)
}
if(self.status===REJECTED){
setTimeout(()=>{
let x = onfailedFn(self.reason);
resolvePromise(promise2,x,resolve,reject);
},0)
}
if(self.status===PENDING){
self.onFulfilled.push(()=>{
setTimeout(()=>{
let x = onSuccessFn(self.value);
resolvePromise(promise2,x,resolve,reject)
},0)
});
self.onRejected.push(()=>{
setTimeout(()=>{
let x = onfailedFn(self.reason);
resolvePromise(promise2,x,resolve,reject)
},0)
});
}
})
return promise2
}
功能基本上搞定了,我们来测试下代码
结果是我们想要的 你以为大功告成了吗?
然而并没有,还有一种情况,resolve里面的对象又是一个promise对象,写个代码来看看结果
还是一个是一个promise对象,这就不科学了啊,应该拿到最里面的值,所以就需要递归调用一下
好的,至此,你以为基本上大功告成了吗?
然而。
其实差不多啦 ,还有个细节需要优化就是在递归调用的时候,promise的状态只能改一次,我们添加一个变量 如果改变过我们就直接返回
我觉得此操作稍稍有点多余,因为我们在构成函数里面写的reject resolve 方法一旦状态不是pedding了就不能改了,即使你在调用也不会在走里面的方法
不过我们加个判断,减少代码的执行也是蛮好的。。。 至此手写Promise 完,下面我们来手写 promise.All promise.race, promise.allSettled
贴一下全部代码
const PENDING = 'pending';
const REJECTED = 'rejected';
const FULFILLED = 'fulfilled';
function MyPromise(callback){
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilled = [];//成功的回调
this.onRejected = []; //失败的回调
callback(resolve.bind(this),reject.bind(this));
function resolve(data){
if(this.status===PENDING){
this.status = FULFILLED;
this.value = data;
this.onFulfilled.forEach(fn => {
fn(data);
});
}
}
function reject(reason){
if(this.status===PENDING){
this.status = REJECTED;
this.reason = reason;
this.onFulfilled.forEach(fn => {
fn(reason);
});
}
}
}
MyPromise.prototype.then = function(onSuccess,onfailed){
/*因为原生的Promise then 里面的成功和失败可以不传,如果不传的话,我们默认给一个匿名方法 返回传入的参数
避免报错
*/
var onSuccessFn = typeof onSuccess==='function'? onSuccess:value=>value;
var onfailedFn = typeof onfailed==='function'? onfailed:reason=>{throw reason};
let self = this;//这里的self 就是上个prosie对象
/*
下一个Promise then的回调的执行状态是根据上一个then的返回值来处理的
所以封装一个方法,统一处理
*/
function resolvePromise(promise2,x,resolve,reject){
if(x===promise2){//如果下一个promise和上一个是同一个对象,那就会无限循环
reject(new TypeError('Chaining cycle'));
}
if(x && typeof x==='function'|| typeof x==='object'){
/*
1.为什么要尝试获取then方法,因为不获取可能会出问题,比如如下代码直接调用就报错了
const object1 = {};
Object.defineProperty(object1, 'then', {
get:function(){
throw Error('error')
}
});
2.获取到then方法之后,可以尝试去执行下它,这里的then 可能是系统的Promise的then
或者我们自定义的MyPromise的then,还有一种是我们自定义的对象自己搞个then方法
*/
let changed;
try {
let then = x.then;
if(typeof then==='function'){
then.call(x,(y)=>{
if(changed) return;
changed = true;
// 这里递归调用一下,知道y不是一个Promise对象(包含then方法),也就是其它对象 或者其它值
resolvePromise(promise2,y,resolve,reject)
},(n)=>{
if(changed) return;
changed = true;
reject(n)
})
}else{
if(changed) return;
changed = true;
resolve(x)
}
} catch (error) {
if(changed) return;
changed = true;
reject(error)
}
}else(
resolve(x)
)
}
let promise2 = new MyPromise((resolve,reject)=>{
if(self.status===FULFILLED){
/*
这里可能不太好理解,x = onSuccessFn() 或者 x = onfailedFn()
就是上个then的返回值 执行onSuccessFn()/onfailedFn() 就会执行当前promise的then里面的
成功或者失败方法,返回值就是我们想要的,我们根据这个返回值的类型对应做一些判断操作
这两句代码需要异步执行,同步调用的话,then执行,promise2还没有返回,这样promise2
就是未定义了 下面逻辑是同理的
*/
setTimeout(()=>{
let x = onSuccessFn(self.value);//这里的x就是上一个then的返回值
resolvePromise(promise2,x,resolve,reject);
},0)
}
if(self.status===REJECTED){
setTimeout(()=>{
let x = onfailedFn(self.reason);
resolvePromise(promise2,x,resolve,reject);
},0)
}
if(self.status===PENDING){
self.onFulfilled.push(()=>{
setTimeout(()=>{
let x = onSuccessFn(self.value);
resolvePromise(promise2,x,resolve,reject)
},0)
});
self.onRejected.push(()=>{
setTimeout(()=>{
let x = onfailedFn(self.reason);
resolvePromise(promise2,x,resolve,reject)
},0)
});
}
})
return promise2
}
var p = new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('success');
},0)
}).then((data)=>{
return new MyPromise((resolve,reject)=>{
resolve(
new MyPromise((resolve,reject)=>{
// resolve('success1');
reject('error1')
resolve('error1')
})
)
})
}).then((data)=>{
console.log(data);
return {a:5}
}).then((data)=>{
console.log(data);
})