Promise对象在现代前端开发工作日常写业务中使用非常广泛,主要用于解决回调地狱的问题。下面通过一点点的实现一个自己的Promise,加强对其的理解以及实践应用。
正常使用Promise
对象时,将要执行的方法传入构造函数中,再方法中修改状态后,通过then
方法便能在修改状态后执行对应预期的回调函数
const promise = new Promise((resolve,reject)=>{
// 模拟Ajax请求
setTimeout(()=>{
resolve([{id:1,name:"test"}]);
},2000);
});
promise.then((res)=>{
console.log(res);
},(err)=>{
console.log(err);
});
// 状态为成功的回调函数
// 两秒后输出结果
// [{id:1,name:"test"}]
复制代码
开始动手实现一个自己的Promise
之前,需要先确定设计(实现会有很多注释方便理解阅读)
Promise
是一个类(本质是语法糖,也可以通过Function
去创建,不赘述),构造函数接受一个函数,并且会传入两个实例方法作为参数Promise
中有3种状态:等待中(pending
),成功(fulfilled
),失败(rejected
),确定状态后无法更改,只有pending --> (fulfilled/rejected)
- 实例对象的
resolve
和reject
方法可用于改变状态,并且需要内部存储传入成功回调的value
或者失败回调的reason
- 实例对象的
then
方法接受两个函数做为参数(resolve,reject
),用于判断上个Promise
的状态,成功调用resolve
,失败调用reject
Promise
支持链式调用
下面这段包含了完整代码,但部分api
会单独解析
// 定义状态常量,便于引用
const FUFILLED = "fulfilled";
const REJECTED = "rejected";
const PENDING = "pending";
class CustomPromise {
// executor就是new Promise((resolve,reject)=>{})时传入的方法参数
constructor(executor) {
try {
// 直接执行,并将修改状态的实例方法
executor(this.resolve, this.reject);
} catch (err) {
// 如果在执行函数时遇到错误,则将状态改为失败
this.reject(err);
}
}
// promise状态
status = PENDING;
// 成功值
value = undefined;
// 失败原因
reason = undefined;
// 异步的成功回调缓存
successFn = [];
// 异步的失败回调缓存
failureFn = [];
// 箭头函数的目的是绑定this为当前promise实例对象
// 最终调用时并非通过promise.resolve去调用,相当于resolve.call(undefined,value)
// 这个语法糖相当于写在constructor函数中,并将this绑定
// 作用为将状态更改为成功
resolve = (value) => {
// 只有等待状态才会往下执行
if (this.status === PENDING) {
this.status = FUFILLED; // 修改状态
this.value = value; // 缓存值
while (this.successFn.length) {
// 如果是异步的方法这个数组便会有值,取出依次调用
this.successFn.shift()();
}
}
};
// 将状态更改为失败,参考resolve方法
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
while (this.failureFn.length) {
this.failureFn.shift()();
}
}
};
// 添加默认函数让参数变为可选项,实现.then().then()可不传参数
then = (
onFulfilled = (v) => v,
onRejected = (e) => { throw e; } // 箭头函数中throw语法要求需要被{}包裹
) => {
// 这个promise对象会被当做返回值,可实现then().then()的前提
let newPromise = new CustomPromise((resolve, reject) => {
// 这里的status是被调用then方法的promise对象的的status
// 也就是上一个promise
// 判断状态是否成功
if (this.status === FUFILLED) {
// 判断返回值是promise对象还是其他
// 如果是其他,直接调用resolve
// 如果是promise对象,查看promise对象返回的结果再去判断返回值,来决定调用resolve还是调用reject
// 需要添加一个宏任务(setTimeout),才能获取到newPromise,因为当前代码执行时还无法访问实例
// 所以需要同步代码异步执行
setTimeout(() => {
try {
// 获取then方法成功回调的返回值
let cbValue = onFulfilled(this.value);
// 将要返回的promise,成功回调返回值,要返回的promise的成功和失败回调传入作为参数解析
resolvePromis(newPromise, cbValue, resolve, reject);
} catch (error) {
// 如果成功回调函数和解析函数抛错则将返回一个rejected的promise
reject(error);
}
}, 0);
} else if (this.status === REJECTED) {
// 判断状态是否失败,逻辑参考成功判断
setTimeout(() => {
try {
// 和成功判断的区别是,这里调用的是失败的回调,并且传入的是失败原因
let err = onRejected(this.reason);
resolvePromis(newPromise, err, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
} else {
// 进入到else就是一直pending状态,说明调用then方法时还没有修改promise的状态
// 将成功和失败回调缓存起来,通过一个函数,将成功和失败的逻辑包装起来,状态更改后直接调用即可
this.successFn.push(() => {
setTimeout(() => {
try {
let cbValue = onFulfilled(this.value);
resolvePromis(newPromise, cbValue, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
// 等待失败同理
this.failureFn.push(() => {
setTimeout(() => {
try {
let err = onRejected(this.reason);
resolvePromis(newPromise, err, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return newPromise; // 将promise作为then函数的返回值
};
// 后面会解析
finally = (callback) => {
return this.then(
(value) => {
return CustomPromise.resolve(callback()).then(() => value);
},
(reason) => {
throw CustomPromise.resolve(callback()).then(() => {
throw reason;
});
}
);
};
// 后面会解析
catch = (callback) => {
return this.then(undefined, callback);
};
// 后面会解析
static all(array) {
const result = [];
let rIndex = 0;
return new CustomPromise((resolve, reject) => {
const push = (index, value) => {
result[index] = value;
rIndex++;
if (rIndex === array.length) resolve(result);
};
array.forEach((item, index) => {
if (item instanceof CustomPromise) {
item.then((value) => push(index, value), reject);
} else {
push(index, item);
}
});
});
}
// 后面会解析
static race(array) {
return new CustomPromise((resolve, reject) => {
array.forEach((item) => {
if (item instanceof CustomPromise) {
item.then(resolve, reject);
} else {
resolve(item);
}
});
});
}
// 后面会解析
static resolve(value) {
if (value instanceof CustomPromise) return value;
return new CustomPromise((resolve) => resolve(value));
}
// 后面会解析
static reject(value) {
if (value instanceof CustomPromise) return value;
return new CustomPromise((_, reject) => reject(value));
}
}
// 解析返回值类型并执行回调
const resolvePromis = (newPromise, value, resolve, reject) => {
// 如果成功/失败回调的返回值就是then返回的promise,将抛出错误
if (newPromise === value) {
return reject(
new TypeError("Chaining cycle detected for promise #<Promise>")
);
}
if (value instanceof CustomPromise) {
// 如果是promise,则将回调透传,让传入的promise去处理即可
value.then(resolve, reject);
} else {
// 非promise则直接将状态改为成功
resolve(value);
}
};
复制代码
核心功能已经实现的差不多,来验证下代码是否可行,将开头中模拟Ajax
的Promise
改为手写的Promise
const promise = new CustomPromise((resolve,reject)=>{
// 模拟Ajax请求
setTimeout(()=>{
resolve([{name:"jack"}]);
},2000);
});
promise.then((res)=>{
console.log(res);
},(err)=>{
console.log(err);
});
复制代码
可以看到状态已被改为成功,并且回调函数中也能能到并且输出了结果数组
试试失败的案例
const post = new CustomPromise((resolve,reject)=>{
// 模拟Ajax请求
setTimeout(()=>{
reject("failure")
},2000);
});
post.then((res)=>{
console.log(res);
},(err)=>{
console.log(err);
});
复制代码
同样的也成功的把状态改为失败,并且输出的错误原因
异步操作都成功,那么同步代码就不演示了,因为实现的代码更简单:)
接着测试下在then
方法中将then
方法返回的promise
作为resolve
返回值的情况
const p1 = new CustomPromise((resolve, reject) => {
resolve("进入成功回调");
});
const p2 = p1.then((res) => {
console.log(res); // 会输出“进入成功回调”字符串
return p2; // 将then方法的返回值返回,由于在resolvePromise中做了判断,会执行reject并传入一个错误
});
p2.then(
() => {},
(err) => console.log(err.message)
// 由于p2的状态是reject,所以失败的回调函数会执行
);
复制代码
结果也符合预期,拿到的预先定义好的错误
实现then方法参数实现可选,不传值亦可链式调用并将值往后传
目前的代码如果直接调用x.then().then()
会直接报错,因为默认把回调当成函数并用()
操作符去调用
只需要在then
方法的参数给俩默认参数即可,在成功回调拿到传入的值并再次return
,而失败回调则需要手动抛出失败原因,这里使用了默认参数和箭头函数,语法上throw
需要被{}
代码块包裹,要测试报错将默认参数的函数删掉即可
// 省略其他代码
class CustomPromise{
then = (onFulfilled = v => v,onRejected = err => { throw err })=>{
// 省略
}
}
复制代码
实现resolve,reject,finally,catch,all,race
resolve的实现
因为在调用时是使用Promise.resolve()
的形式,所以resolve是一个静态方法,不需要实例化即可通过类名去调用
实现原理也很简单,只需要判断传入的值类型,如果是Promise
类型,则将promise
原封不动的返回接口,其他值则将其封装成一个Promise
对象再返回
// 省略其他代码
class CustomPromise{
static resolve(value){
// 是promise就直接返回啥也不干
if(value instanceof CustomPromise) return value;
return new CustomPromise(resolve => resolve(value));
}
}
复制代码
调用时CustomPromise.resolve
// 传入非promise
CustomPromise.resolve(1).then((res) => {
console.log(res);
// 会输出1
});
// 传入Promise
CustomPromise.resolve(new CustomPromise((resolve) => resolve(123))).then(
(res) => {
console.log(res);
// 输出promise的成功回调的return值
}
);
复制代码
结果如下,传入非promise的输出
传入promise的输出
reject的实现
reject
的实现原理和resolve
类似,却别只是在于要将返回的Promise
的状态改为rejected
,并将reason
传入
// 省略其他代码
class CustomPromise{
static reject(reason){
if(reason instanceof CustomPromise) return reason;
// _是形参resolve的引用,用不到所以单纯是个占位符
return new CustomPromise((_, reject) => reject(reason));
}
}
复制代码
调用CustomPromise.reject
// 传入非promise
CustomPromise.reject(new Error("错了")).then(null, (reason) =>
console.log(reason)
);
// 传入Promise
CustomPromise.resolve(
new CustomPromise((_, reject) => reject(new Error("我错了")))
).then(null, (reason) => console.log(reason.message));
复制代码
输出的结果符合预期,都执行了then
方法传入的失败回调
finally的实现
该方法是实例方法,接受一个函数作为参数,不管前面的promise
状态如何,最终都会调用传入的方法,并返回一个promise
,状态就是上一个promise
的状态
- 由于方法会返回一个
Promise
,可以调用this.then
方法,在成功和失败的回调方法中,均调用finally
的传入函数 - 而由于回调函数可能返回
promise
,那么就需要等待该promise
有结果时,再走下面的逻辑,利用静态方法resolve
方法可实现
// 省略其他代码
class CustomPromise{
finally = (callback) => {
// return this.then是因为要返回一个promise,finally后还可以继续then操作
return this.then((value) => {
// 成功回调会执行回调函数,将球传给静态方法resolve,按照上面的实现,传入promise会原封不动的返回,也就实现了等待的功能
return CustomPromise.resolve(callback()).then(() => value);
},(reason)=>{
// 失败回调也会执行回调函数,其他同理,只不过将reason手动的抛出
// 由于成功的回调函数会抛出错误,所以后面的then能获取到错误,也就是finally().then()的状态还是失败
return CustomPromise.resolve(callback()).then(() => { throw reason });
})
}
}
复制代码
测试finally
方法
new CustomPromise((resolve, reject) => {
resolve();
})
.finally(() => {
console.log("成功会调用");
})
.then(
() => {
console.log("finally后的resolve会调用");
},
() => {
console.log("执行了就不对");
}
);
setTimeout(() => {
new CustomPromise((resolve, reject) => {
reject();
})
.finally(() => {
console.log("-----------------");
console.log("失败也会调用");
})
.then(
() => {
console.log("执行了就不对");
},
() => {
console.log("finally后的reject会调用");
}
);
}, 1000);
复制代码
catch方法的实现
该方法是实例方法,捕获到Promise
链中的错误,再返回一个Promise
对象
- 实现的思路其实很简单,本质上也是个调用
then
,但是忽略了resolve
回调,只传入reject
回调 - 也就是上个
promise
是失败的状态才会执行
// 省略其他代码
class CustomPromise{
catch = (callback) => {
return this.then(null,callback)
}
}
复制代码
测试catch
方法
new CustomPromise((resolve, reject) => {
resolve();
}).catch((err)=>{
console.log("这句话不会执行");
})
new CustomPromise((resolve, reject) => {
reject(new Error("故意抛出的错"));
}).catch((err)=>{
console.log(err);
})
复制代码
测试结果为只有reject
状态的promise
才会执行回调函数
all方法的实现
all
方法是类的静态方法,允许依照异步代码的顺序,得到异步代码的执行结果,方法接受一个数组,数组的内容可以是任意值,返回值是Promise
对象
- 如果
all
方法中全部promise
对象都成功,返回的promise
状态为成功 - 如果有一个
promise
对象失败,返回的promise
状态为失败
// 省略其他代码
class CustomPromise(){
static all = (arr) => {
const result = []; // 成功返回的结果集
let rIndex = 0; // 用于判断是否全部promise都为成功
return new Promise((resolve,reject) => {
const addValue = (index,value) => {
result[index] = value;
rIndex++;
// 这一步很关键,因为promise的执行时机不能确定,所以只有当调用addValue时rIndex+1后,则视为有一个结果成功
// 当rIndex的大小与入参数组的长度相当,便相当于成功
if(rIndex === arr.length) resolve(result)
}
arr.forEach((item,index) => {
// 如果某一项是promise对象,则在该promise对象成功的回调中再添加结果到结果集中
// 如果不是promise直接添加到结果集中
// 如果某个promise失败了,则调用当前要返回promise的reject
if(item instanceof CustomPromise){
item.then((value) => addValue(index,value), reject);
}else{
addValue(index,item);
}
})
})
}
}
复制代码
接下来测试下结果
// 方便测试,定义两个返回promise的函数
function testFulfilledPromise(delay) {
return new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, delay);
});
}
function testRejectedPromise(delay) {
return new CustomPromise((resolve, reject) => {
setTimeout(() => {
reject(new Error("err"));
}, delay);
});
}
CustomPromise.all([
1,
2,
testFulfilledPromise(2000),
testFulfilledPromise(4000),
4,
5,
]).then(
(res) => {
console.log("按顺序拿到的结果集");
console.log(res);
},
(err) => {
console.log(err);
}
);
复制代码
再4秒后,成功的拿到了返回结果集,4秒后执行了成功的回调
测试数组中有失败promise的情况
CustomPromise.all([
1,
2,
testFulfilledPromise(2000),
testRejectedPromise(4000),
4,
5,
]).then(
(res) => {
console.log("按顺序拿到的结果集");
console.log(res);
},
(err) => {
console.log("rejected:")
console.log(err);
}
);
复制代码
预期也是满足执行了失败回调,在4秒后执行了失败的回调
race方法
该方法与all
方法的区别是,只要有任意一个promise
返回了成功或者失败状态,便以这个promise
为返回的状态结果。race
有赛跑的意思,谁先跑到终点,不管这个人状态怎么样,他都是冠军了,就用他的状态当返回结果
- 接收参数与
all
方法相同 - 如果传入的数组中有非
promise
对象,将起包装成promise
再返回,状态为成功 - 如果是
promise
对象,在promise
状态更改后,调用要返回的promise
对象的回调(相当于数组中某个promise
更改状态后,当前要return
的promise
也跟着更改状态) - 测试函数延用
all
实例中的
// 省略其他代码
class CustomPromise(){
static race(arr) {
return new CustomPromise((resolve, reject) => {
arr.forEach((item) => {
if (item instanceof CustomPromise) {
item.then(resolve, reject);
} else {
resolve(item);
}
});
});
}
}
复制代码
测试成功Promise先于失败Promise
console.log(new Date())
CustomPromise.race([
testFulfilledPromise(2000),
testRejectedPromise(3000),
]).then(
(res) => {
console.log(new Date())
console.log("拿到更改状态最快的结果");
console.log(res);
},
(err) => {
console.log(err);
}
);
复制代码
测试结果为以两秒后返回的promise
为结果,并且状态为成功
测试失败Promise先于成功Promise
console.log(new Date())
CustomPromise.race([
testFulfilledPromise(2000),
testRejectedPromise(1000),
]).then(
(res) => {
console.log(new Date())
console.log("拿到更改状态最快的结果");
console.log(res);
},
(err) => {
console.log(new Date())
console.log("拿到更改状态最快的结果");
console.log(err);
}
);
复制代码
测试结果为以一秒后返回的promise
为结果,并且状态为失败
Promise
的主要功能都实现了一遍,通过这个简易版Promise
的实现,不是真的要写出一个Promise
,而是写完后再看ES6
提供的原生Promise
能有更深的理解:)