Promise是web前端工程师在面试的过程中很难绕过的一个坎。如果您目前处于对Promise一知半解,或仅仅是停留在可以使用的层面上,建议您跟着本文敲打练习一遍,相信您一定会有所收获!另外文章有点长……
一、实现Promise
以下功能:
const p1 = new Promise((resolve, reject) => {
resolve("成功");
})
p1.then(value => {
// 成功
console.log(value);
})
const p2 = new Promise((resolve, reject) => {
reject("失败");
})
p2.then(undefined, reason => {
// 失败
console.log(reason);
})
const p3 = new Promise((resolve, reject) => {
throw "异常"
})
p3.then(undefined, reason => {
// 异常
console.log(reason);
})
1、根据上面的代码,我们不难推断出其基本的结构:
/*
* 创建一个构造函数 Promise
* 该函数接收一个 executor 执行函数
* */
function Promise(executor) {
}
/*
* 为 Promise 函数增加 then 方法;
* then 方法接收两个类型为 function 的参数;
* 第一个参数onResolved为成功时调用的函数;
* 第二个参数onRejected为失败时调用的函数;
* */
Promise.prototype.then = function (onResolved,onRejected) {
}
2、Promise
对象存在三种状态:Pending(进行中)
、resolved(已成功)
、rejected(已失败)
。我们可以将其设置为三个常量:
// 进行中状态
const _PENDING="pending";
// 已成功状态
const _RESOLVED="resolved";
// 已失败状态
const _REJECTED="rejected";
3、我们在实例化Promise
时,resolve
或reject
函数将会得到执行,一旦执行其实例的状态会由pending
更改为resolved
或rejected
,然后在原型对象方法then
当中进行接收。所以我们要干以下几个事情:
- 实例中创建两个属性
status
与value
- 创建内部函数
_resolve
与_reject
用于更新status
与value
- 立即执行
executor
函数
代码如下:
function Promise(executor){
/****** 1、实例中创建两个属性`status`与`value` ******/
// 设置状态初始值为 pending
this.status = _PENDING;
// 设置初始值为 undefined
this.value = undefined;
/****** 2、创建内部函数`_resolve`与`_reject`用于更新`status`与`value` ******/
// 成功时执行
function _resolve(value) {
// 修改 promise 对象的状态为 resolve
this.status = _RESOLVED;
// 保存成功的数据
this.value = value;
}
// 失败时执行
function _reject(reason) {
// 修改 promise 对象的状态为 resolve
this.status = _REJECTED;
// 保存失败的数据
this.value = reason;
}
/****** 3、立即执行`executor`函数 ******/
try{
// 立即执行 executor
executor(_resolve.bind(this),_reject.bind(this))
}catch (err) {
_reject.call(this,err);
}
}
4、我们需要在then
函数内,根据实例的状态state
来判断要执行onResolved
成功函数,还是onRejected
失败函数。Promise
的核心then
函数初始代码如下:
Promise.prototype.then = function (onResolved,onRejected) {
switch (this.status) {
// 当状态为resolve时,执行onResolved,并传递结果
case _RESOLVED:
onResolved(this.value);
break
// 当状态为reject时,执行onRejected,并传递结果
case _REJECTED:
onRejected(this.value);
break
}
}
5、当前已完成代码如下:
// 进行中状态
const _PENDING="pending";
// 已成功状态
const _RESOLVED="resolved";
// 已失败状态
const _REJECTED="rejected";
/*
* 创建一个构造函数 Promise
* 该函数接收一个 executor 执行函数
* */
function Promise(executor){
// 设置状态初始值为 pending
this.status = _PENDING;
// 设置初始值为 undefined
this.value = undefined;
// 成功时执行
function _resolve(value) {
// 修改 promise 对象的状态为 resolve
this.status = _RESOLVED;
// 保存成功的数据
this.value = value;
}
// 失败时执行
function _reject(reason) {
// 修改 promise 对象的状态为 resolve
this.status = _REJECTED;
// 保存失败的数据
this.value = reason;
}
try{
// 立即执行 executor
executor(_resolve.bind(this),_reject.bind(this))
}catch (err) {
_reject.call(this,err);
}
}
/*
* 为 Promise 函数增加 then 方法;
* then 方法接收两个类型为 function 的参数;
* 第一个参数onResolved为成功时调用的函数;
* 第二个参数onRejected为失败时调用的函数;
* */
Promise.prototype.then = function (onResolved,onRejected) {
switch (this.status) {
// 当状态为resolve时,执行onResolved,并传递结果
case _RESOLVED:
onResolved(this.value);
break
// 当状态为reject时,执行onRejected,并传递结果
case _REJECTED:
onRejected(this.value);
break
}
}
二、Promise的状态state只允许更改一次
Promise
即承诺,一旦承诺便会给予结果,且结果是不允许更改的。也就是说状态state
一旦确定便不可更改。
const p1 = new Promise((resolve, reject) => {
resolve("成功");
reject("失败");
})
p1.then(value => {
// 不会执行
console.log(value);
},reason=>{
// 输出:失败
console.log(reason);
})
以上代码正确的输出应该是成功
,而我们自己封装的Promise
输出结果却为失败
!说明我们的state
被reject("失败")
进行了二次更改,原因很简单:我们没有对当前的状态进行判断。
所以我们要对_resolve
与_reject
进行调整,分别在其函数体内增加对当前状态的判断,如果不是初始状态pending
则不会继续更新状态及数据。
在构造函数Promise
中找到_resolve
与_reject
函数,代码调整如下:
// 成功时执行
function _resolve(value) {
// 如果为pending退出函数
if (this.status !== "pending")
return;
// 修改 promise 对象的状态为 resolve
this.status = _RESOLVED;
// 保存成功的数据
this.value = value;
}
// 失败时执行
function _reject(reason) {
// 如果为pending退出函数
if (this.status !== "pending")
return;
// 修改 promise 对象的状态为 resolve
this.status = _REJECTED;
// 保存失败的数据
this.value = reason;
}
接下来,再来执行最初的程序,输出为成功
,说明状态只允许被更新一次了!
三、then函数是异步的
因为then函数是异步的,所以在正常情况下,以下代码的输出应该为:1 2 3
。但是采用我们自己封装的Promise
,其结果却为1 3 2
。原因:我们并未对then
函数进行异步的处理。
const p = new Promise((resolve, reject) => {
console.log(1);
resolve("成功");
})
p.then(value=>{
console.log(3);
console.log()
})
console.log(2);
接下来,进入到then
函数中。我们只需要将执行回调的代码用setTimeout
进行包裹即可:
Promise.prototype.then = function (onResolved, onRejected) {
switch (this.status) {
// 当状态为resolve时,执行onResolved,并传递结果
case _RESOLVED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
onResolved(this.value);
})
break
// 当状态为reject时,执行onRejected,并传递结果
case _REJECTED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
onRejected(this.value);
})
break
}
}
再来执行,结果为1 2 3
。问题顺利解决!
四、完成Promise的连缀调用(核心)
const p = new Promise((resolve, reject) => {
resolve("成功");
})
const result = p.then();
// 输出status为 resolved 的 Promise 实例
console.log(result);
const p2 = new Promise((resolve, reject) => {
reject("失败");
})
const result2 = p2.then();
// 输出status为 resolved 的 Promise 实例
console.log(result2);
const p3 = new Promise((resolve, reject) => {
throw "异常"
})
const result3 = p3.then();
// 输出status为 rejected 的 Promise 实例
console.log(result3);
1、正常情况下,上方代码不管成功与失败,then
函数的返回结果始终应该是一个Promise
实例,且其状态均为resolved
。如果出现异常报错,则返回的状态为rejected
,如下:
但是,我们目前的then
函数是没有返回值的,所以我们只能得到一个undefined
,并且由于我们未给予then函数相对应的参数(类型为函数),还给我们飘红报错了:Uncaught TypeError: onResolved is not a function
。
所以接下来,我们要做三件事:1、验证参数是否为函数。 2、让then
函数直接返回Promise
3、更改promise
的状态:异常执行reject
,其它均执行resolve
- 验证参数是否为函数:
// 防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
- 让
then
函数返回promise
,then
函数完整代码如下:
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
// 防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 当状态为resolve时,执行onResolved,并传递结果
case _RESOLVED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
onResolved(this.value);
});
break
// 当状态为reject时,执行onRejected,并传递结果
case _REJECTED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
onRejected(this.value);
});
break
}
})
}
- 更改
Promise
的状态:异常执行reject
,其它均执行resolve
,并传值。
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
// 防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 当状态为resolve时,执行onResolved,并传递结果
case _RESOLVED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
// 增加try方法,如果出现异常,执行reject
try {
onResolved(this.value);
// 无异常执行resolve
resolve(this.value);
} catch (err) {
// 出现异常,执行reject
reject(err);
}
});
break
// 当状态为reject时,执行onRejected,并传递结果
case _REJECTED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
// 增加try方法,如果出现异常,执行reject
try {
onRejected(this.value);
// 无异常执行resolve
resolve(this.value);
} catch (err) {
// 出现异常,执行reject
reject(err);
}
});
break
}
})
}
接下来,通过咱们封装的程序,可以得到准确的数据了:
2、我们知道then
在其回调函数中返回非Promise
的数据,最终得到的result
是一个为resolved
状态的Promise
(成功的状态),倘若返回的是一个Promise
数据,那么最终得到的便是该Promise
的状态(成功或失败的状态),例如:
const p = new Promise((resolve, reject) => {
resolve("成功");
})
const result = p.then(value=>{
return "返回字符串"+value;
});
console.log("result",result);
const result2 = p.then(value=>{
return new Promise((resolve,reject)=>{
resolve("成功的Promise")
})
});
console.log("result2",result2);
const result3 = p.then(value=>{
return new Promise((resolve,reject)=>{
reject("失败的Promise")
})
});
console.log("result3",result3);
应该是这样的结果:
但是,通过我们自己封装的
Promise
得到的结果都是一样的:- 原因:没有在
then
函数中判断onResolved
与onRejected
返回类型。 - 解决:判断
onResolved
与onRejected
的返回结果是否为Promise,如果是Promise
,则将其状态与then
要返回的Promise
状态设为一致。
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
// 防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 当状态为resolve时,执行onResolved,并传递结果
case _RESOLVED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
// 增加try方法,如果出现异常,执行reject
try {
let result = onResolved(this.value);
// 判断返回结果是否为Promise类型
if(result instanceof Promise){
// result.then(v => {
// // 成功,修改返回的 Promise 状态为成功
// resolve(v);
// }, r => {
// // 失败,修改返回的 Promise 状态为失败
// reject(r);
// });
// result 是 promise,下面这行代码是上方代码的简写形式
result.then(resolve,reject);
}else{
// 非Promise类型,将结果直接传递过去
resolve(result);
}
} catch (err) {
// 出现异常,执行reject
reject(err);
}
});
break
// 当状态为reject时,执行onRejected,并传递结果
case _REJECTED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
// 增加try方法,如果出现异常,执行reject
try {
let result = onRejected(this.value);
// 判断返回结果是否为Promise类型
if(result instanceof Promise){
// result.then(v => {
// // 成功,修改返回的 Promise 状态为成功
// resolve(v);
// }, r => {
// // 失败,修改返回的 Promise 状态为失败
// reject(r);
// });
// result 是 promise,下面这行代码是上方代码的简写形式
result.then(resolve,reject);
}else{
// 非Promise类型,将结果直接传递过去
resolve(result);
}
} catch (err) {
// 出现异常,执行reject
reject(err);
}
});
break
}
})
}
结果:
效果虽然出来了,但是这样的代码确实有些臃肿。所以我们要对其进行优化:不难发现两个
case
内的代码除了回调函数不同,其它都是一样的,所以我们可以将其进行封装,并将回调函数作为参数传递。封装优化后:
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
/*
* 参数 cb 的值为 onResolved 或 onRejected 函数
* */
function _callback(cb) {
// 增加try方法,如果出现异常,执行reject
try {
let result = cb(this.value);
// 判断返回结果是否为Promise类型
if (result instanceof Promise) {
// result 是 promise,下面这行代码是上方代码的简写形式
result.then(resolve, reject);
} else {
// 非Promise类型,将结果直接传递过去
resolve(result);
}
} catch (err) {
// 出现异常,执行reject
reject(err);
}
}
// 防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 当状态为resolve时,执行onResolved,并传递结果
case _RESOLVED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
_callback.call(this, onResolved);
});
break;
// 当状态为reject时,执行onRejected,并传递结果
case _REJECTED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
_callback.call(this, onRejected);
});
break;
}
})
}
3、程序写到这一步其实还隐藏着一个bug,如果我们采用连缀的形式会有问题,代码如下:
let p1 = new Promise((resolve, reject) => {
resolve('成功')
}).then(value => {
console.log("我会输出")
}).then(value => {
console.log("我不会输出")
})
正常来讲,两个then
均会输出才对。而通过我们封装的Promise
,只会将第一个输出。
- 分析:因为我们现在的Promise是同步任务,所以当我们执行到第一个
then
的时候,当前Promise
的状态已经确定为resolved。而当执行到第二个then
的时候,此时的Promise
是通过第一个then
得到的,又因为在第一个then
当中有setTimeout
,使其变为了异步,所以会造成resolve
或reject
不会立即调用,最终导致在执行第二个then时,当前Promise
的status
为pending
。也就是说我们更改状态后,回调方法没有得到执行。如果此时我们将封装then
函数当中的setTimeout
移除掉,则会恢复正常,但将其移除掉封装也就失去了意义。 - 解决:我们已经知道原因是当
Promise
的状态发生变化时,then
函数的回调没有得到调用。所以我们需要在改变状态后调用即可。可状态更改完成之后我们又如何才可以执行回调?在这个时候我们可以在实例当中创建一个属性onCallBacks
用于存放回调函数队列,然后在执行then
函数时判断当前状态如果为pending
则说明为异步任务,只需将回调函数放置到onCallBacks
属性中。这样当异步修改完状态后,我们就可以通过onCallBacks
执行回调了。代码: - 在实例当中创建一个属性
onCallBacks
用于存放回调函数队列。
// 添加回调函数队列
this.onCallBacks = [];
- 在
then
函数中判断当前状态为pending
时,将回调函数放置到onCallBacks
数组中。
// 当状态为 pending 时,将要执行的回调函数放置到队列中,待状态更改完毕后再调用。
case _PENDING:
this.onCallBacks.push({
onResolved() {
//获取回调函数的执行结果
_callback.call(this,onResolved);
},
onRejected() {
_callback.call(this,onRejected);
}
});
break;
- 当异步修改完状态后,我们就可以通过
onCallBacks
执行回调了。
// 成功时执行
function _resolve(value) {
if (this.status !== "pending")
return;
// 修改 promise 对象的状态为 resolve
this.status = _RESOLVED;
// 保存成功的数据
this.value = value;
//检查回调数组中是否存在数据
if (this.onCallBacks.length > 0) {
// 异步执行
setTimeout(() => {
this.onCallBacks.forEach(onCb => {
onCb.onResolved.call(this);
});
});
}
}
// 失败时执行
function _reject(reason) {
if (this.status !== "pending")
return;
// 修改 promise 对象的状态为 resolve
this.status = _REJECTED;
// 保存失败的数据
this.value = reason;
//检查回调数组中是否存在数据
if (this.onCallBacks.length > 0) {
// 异步执行
setTimeout(() => {
this.onCallBacks.forEach(onCb => {
onCb.onRejected.call(this);
});
});
}
}
Promise
完整代码如下:
// 进行中状态
const _PENDING = "pending";
// 已成功状态
const _RESOLVED = "resolved";
// 已失败状态
const _REJECTED = "rejected";
/*
* 创建一个构造函数 Promise
* 该函数接收一个 executor 执行函数
* */
function Promise(executor) {
// 设置状态初始值为 pending
this.status = _PENDING;
// 设置初始值为 undefined
this.value = undefined;
// 添加回调函数队列
this.onCallBacks = [];
// 成功时执行
function _resolve(value) {
if (this.status !== "pending")
return;
// 修改 promise 对象的状态为 resolve
this.status = _RESOLVED;
// 保存成功的数据
this.value = value;
//检查回调数组中是否存在数据
if (this.onCallBacks.length > 0) {
// 异步执行
setTimeout(() => {
this.onCallBacks.forEach(onCb => {
onCb.onResolved.call(this);
});
});
}
}
// 失败时执行
function _reject(reason) {
if (this.status !== "pending")
return;
// 修改 promise 对象的状态为 resolve
this.status = _REJECTED;
// 保存失败的数据
this.value = reason;
//检查回调数组中是否存在数据
if (this.onCallBacks.length > 0) {
// 异步执行
setTimeout(() => {
this.onCallBacks.forEach(onCb => {
onCb.onRejected.call(this);
});
});
}
}
try {
// 立即执行 executor
executor(_resolve.bind(this), _reject.bind(this))
} catch (err) {
_reject.call(this, err);
}
}
/*
* 为 Promise 函数增加 then 方法;
* then 方法接收两个类型为 function 的参数;
* 第一个参数 onResolved 为成功时调用的函数;
* 第二个参数 onRejected 为失败时调用的函数;
* */
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
/*
* 参数 cb 的值为 onResolved 或 onRejected 函数
* */
function _callback(cb) {
// 增加try方法,如果出现异常,执行reject
try {
let result = cb(this.value);
// 判断返回结果是否为Promise类型
if (result instanceof Promise) {
// result 是 promise,下面这行代码是上方代码的简写形式
result.then(resolve, reject);
} else {
// 非Promise类型,将结果直接传递过去
resolve(result);
}
} catch (err) {
// 出现异常,执行reject
reject(err);
}
}
// 防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 当状态为resolve时,执行onResolved,并传递结果
case _RESOLVED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
_callback.call(this, onResolved);
});
break;
// 当状态为reject时,执行onRejected,并传递结果
case _REJECTED:
// 通过 setTimeout 让代码异步执行
setTimeout(() => {
_callback.call(this, onRejected);
});
break;
// 当状态为 pending 时,将要执行的回调函数放置到队列中,待状态更改完毕后再调用。
case _PENDING:
this.onCallBacks.push({
onResolved() {
//获取回调函数的执行结果
_callback.call(this,onResolved);
},
onRejected() {
_callback.call(this,onRejected);
}
});
break;
}
})
}
截止到目前,我们已经完成了Promise
的链式调用。
五、其它API
// catch方法的封装
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected);
}
// 函数对象 resolve 的封装
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(resolve, reject);
} else {
resolve(value);
}
});
}
// 函数对象 reject 的封装
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
//函数对象 all 的封装
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let pValues = [];
let flag = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
pValues[i] = v;
flag++;
if (flag >= promises.length) {
resolve(pValues);
}
}, r => {
reject(r);
})
}
});
}
// 函数对象 race
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for(let i=0;i<promises.length;i++){
promises[i].then(value=>{
resolve(value);
}, reason=>{
reject(reason);
})
}
});
}
—————END—————
喜欢本文的朋友们,欢迎关注公众号 张培跃,收看更多精彩内容!!