前端异步一直是个热门话题,作为新一代异步编程解决方案的Promise
,相比传统异步编程,不仅解决了恐怖的回调地狱,在写法上也是相当方便简洁。如此牛*的功能让人不禁地想一探究竟,在各大网络公司面试中,手写Promise也是常见的笔试题了,就让我们一起来简单探讨一下Promise的实现吧!
引子
什么是Promise?
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
Promise简单用法
// 定义一个Promise对象
let promise = new Promise((resolve,reject) => {
true&&resolve(true) || reject(false)
});
promise.then(res=>{
console.log(res)
}).catch(err=>{
throw err
})
如此一个低级简单的Promise就完成了。
正文
思考一下,需要实现哪些功能
- 定义一个Promise
- 在Promise内部,需要一个构造函数(constructor),构造函数有两个参数:
resolve
和reject
,构造函数内部需要一个状态变量state
(pending、resolved、rejected),一个异步成功的回参res
- 既然是Promise那必须得有
then
方法,有两个参数分别是onFulfilled
和onRejected
- 有了then,自然有
catch
、finally
等一系列。。。。。。
声明一个Promise
既然是手写,那么我们直接将原来的Promise对象覆盖,这里采用ES6的class写法。
声明构造函数
- Promise存在三个状态(state)pending、fulfilled、rejected
- pending(等待态)为初始态,并可以转化为fulfilled(成功态)和rejected(失败态)
- 不管成功还是失败,最终得到的res或者reason都无法改变
class Promise {
// 构造函数
constructor(executor){
// 状态控制state,默认值值为pending、成功resolved、失败rejected
this.state = 'pending';
// 成功返回值
this.res = '';
// 失败返回原因
this.reason = '';
// resolve方法
let resolve = (res) => {
// 如果状态state为pending,将状态变为resolved,并将返回值赋给res
this.state === 'pending' && (this.state = 'resolved') && (this.res = res);
};
let reject = (reason) => {
// 如果状态state为pending,将状态变为rejected,并返回错误原因
this.state === 'pending' && (this.state = 'rejected') && (this.reason = reason);
};
// 如果executor报错的话,直接执行reject
try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
}
实现then方法
then(onFulfilled,onRejected){
// state为resolved,执行onFulfilled
this.state === 'resolved' && (()=>{
onFulfilled(this.res)
})();
// state为rejected,执行onRejected
this.state === 'rejected' && (()=>{
onRjected(this.reason)
})();
}
牛刀小试
此时,一个最基本最简单最低级的Promise就完成了,让我们先测试一下结果,随机一个数,如果大于0.5则成功,如果小于则失败。
let promise = new Promise((resolve,reject)=>{
let random = Math.random();
if(random>0.5){
resolve(random)
} else{
reject(random)
}
});
promise.then(res=>{
console.log('成功:' + res)
},err=>{
console.log('失败:' + err)
})
刷新控制台:
看起来没什么毛病,不过如果你认为这就结束了?
还是这该死的异步
有人问,Promise不就是解决异步的,怎么又说到异步,这里要说的异步就是指定时器了,比如setTimeout
,为什么这样说,这就涉及到js的事件循环机制(eventLoop)了,这里暂且不提。
先看一个例子:
let promise = new Promise((resolve,reject)=>{
let random = Math.random();
if(random>0.5){
setTimeout(() => {
resolve(random)
}, 1000);
} else{
setTimeout(() => {
reject(random)
}, 1000);
}
});
promise.then(res=>{
console.log('成功:' + res)
},err=>{
console.log('失败:' + err)
})
此时,不管怎么刷新,控制台就是不打印结果,不管正确还是错误,这是为什么呢?
首先我们定义了一个Promise对象的实例,在实例中定义了一个定时器,一秒后返回,当运行到定时器之后,定时器挂起,继续执行下面的promise.then,而此时因为定时器的缘故,不管是resolve还是reject都没有运行,也就是说state的状态任然没有改变,所以在then方法中,没有对应的项,如果再次执行then方法,同理依然无法运行,那么问题来了,如何解决因为定时器导致的状态未变化和多次执行then方法呢?
改造构造函数
首先为了多次执行then方法,我们定义两个空数组,分别存放成功或者失败方法,然后在then方法中,如果状态依然是pending,则将成功或者失败的函数存入对应的数组,当定时器时间过了之后,在resolve或者reject中循环对应数组并执行函数。
constructor(executor){
// 原先代码不变
// ......
// 添加数组
// 成功列表
this.resolvedList = [];
// 失败列表
this.rejectedList = [];
// 改造一下resolve和reject
let resolve = (res) => {
this.state === 'pending' && (this.state = 'resolved') && (this.res = res);
this.state === 'resolved' && this.resolvedList.forEach(fn => {
fn();
})
};
// 失败
let reject = (reason) => {
this.state === 'pending' && (this.state = 'rejected') && (this.reason = reason);
this.state === 'rejected' && this.rejectedList.forEach(fn => {
fn();
})
};
}
改造then方法
由于定时器的原因,造成先执行then,那么在then中将对应函数存起:
then(onFulfilled,onRejected){
this.state === 'pending' && (() =>{
this.resolvedList.push(()=>{
console.log('resolvedList.push')
onFulfilled(this.res);
});
this.rejectedList.push(()=>{
onRejected(this.reason)
});
return
})()
// state为resolved,执行onFulfilled
this.state === 'resolved' && (()=>{
onFulfilled(this.res)
})();
// state为rejected,执行onRejected
this.state === 'rejected' && (()=>{
onRjected(this.reason)
})();
}
调用then方法
promise.then(
res => {
console.log("成功1:" + res);
},
err => {
console.log("失败1:" + err);
}
);
promise.then(
res => {
console.log("成功2:" + res);
},
err => {
console.log("失败2:" + err);
}
);
刷新控制台:
此时控制台已经可以正常打印。
then方法的链式调用
为了解决回调地狱,在Promise中,我们是这样的写法:new Promise().then().then()
,道理其实很简单,第一个then方法返回的值也是一个Promise对象,那么就能继续使用then方法。
当我们在第一个then中return了一个参数(参数未知,需判断)。这个return出来的新的promise就是onFulfilled()或onRejected()的值:
- 返回值是普通值如:数值、字符串等,直接将其作为
callBackPromise
成功的结果 - 如果是一个Promise,则取其结果作为
callBackPromise
成功的结果
一个判断返回值的函数callBackPromise
- res 不能是null
- 声明了then,如果取then报错,则走reject()
function callBackFunction(callBackPromise,res, resolve, reject){
// 判断callBackPromise === res ,如果等于会造成死循环
if(callBackPromise === res){
return reject(new TypeError('注意死循环了!'));
}
// 防止重复调用
let reCall;
if(res !== null && (typeof res === 'Object' || typeof res === 'function')){
try {
// 链式,调用上一次结果的then
let then = res.then;
typeof then === 'function' && then.call(res, nextRes => {
if(reCall){
return
}
reCall = true;
callBackPromise(callBackPromise,nextRes,resolve,reject)
},err => {
if(reCall)
return;
reject(err);
});
resolve(res)
}catch(e){
if(reCall)
return;
reject(e)
}
}else{
reject(res)
// resolve(res)
}
}
继续改造then方法
then(onFulfilled,onRejected){
// 注意此处用到的var,如果用let会造成callBackFunction报错,原因是let没有变量提升
var callBackPromise = new Promise((resolve,reject) => {
this.state === 'pending' && (() =>{
this.resolvedList.push(()=>{
let res = onFulfilled(this.res);
callBackFunction(callBackPromise,res,resolve,reject)
});
this.rejectedList.push(()=>{
let res = onRejected(this.reason)
callBackFunction(callBackPromise,res,resolve,reject)
});
return
})()
this.state === 'resolved' && (()=>{
let res = onFulfilled(this.res);
callBackFunction(callBackPromise,res,resolve,reject)
})();
this.state === 'rejected' && (()=>{
let res = onRejected(this.reason);
callBackFunction(callBackPromise,res,resolve,reject)
})();
})
return callBackPromise;
}
测试一下,如果打印为空多刷新几次,因为没有实现catch方法:
promise.then(res => {
console.log('第一次回调',res)
return '第一次返回'
})
.then(res2=>{
console.log('第二次回调',res2)
return '第二次回调'
})
.then(res3=>{
console.log('第三次回调',res3)
})
可以看到此时已经可以进行链式操作。
后记
到此,一个基本的链式调用已经完成,然而Promise的博大精深远不及此,诸如onFulfilled,onRejected的可选性,catch、finally、all等方法的实现都还没有完成,需要不断完善,如果上述代码中有错误的还请各位大佬指出,轻喷。