Promise对象的出现,是为了解决异步请求回调嵌套(地狱回调)的问题,它的实现原理很简单,从嵌套回调到链式回调的转换,其实就是形式的改变,最关键的因素是增加了状态判断,状态贯穿Promise整个对象;好吧,下面我们从最初的地方开始讲起:异步回调嵌套
还记得jquery时代的ajax请求吗?
$.ajax({
type:"post",
url:"api/getDatas",
async:true,
data:{status: 1},
success:function(res){
//请求成功的回调
},
error:function(err){
//请求失败的回调
}
})
其实这样写一点问题都没有,但如果在成功的回调里再次嵌套一次异步请求,那么代码就成了这样:
$.ajax({
type:"post",
url:"api/getDatas“,
async:true,
data:{status: 1},
success:function(res){
$.ajax({
type:"post",
url:"api/getDatas“,
async:true,
data:{status: 1, value: res.id},
success:function(res){
//第二次请求成功的回调
},
error:function(err){
//第二次请求失败的回调
}
})
},
error:function(err){
//第一次请求失败的回调
}
})
之所以有这样的写法,很多情况是因为第二次请求的参数需要通过第一次请求获取,比如上面的value: res.id
就是如此;
为了解决嵌套的问题,我们可以先自己尝试下自己的方法,既然不能嵌套,那我就把回调里的内容拿出来外面,分成两个方法:
function execute(success, error){
$.ajax({
type:"post",
url:"api/getDatas",
async:true,
data:{status: 1},
success:function(res){
success()//先不做类型判断
},
error:function(err){
error()
}
})
}
function execute2(success2, error2){
$.ajax({
type:"post",
url:"api/getDatas2",
async:true,
data:{status: 2},
success:function(res){
success2()//先不做类型判断
},
error:function(err){
error2()
}
})
}
let success = function(res){
execute2()
}
let error = function(err){
//...
}
execute(success, error)
这样定义好后,想再执行一次异步,只需要在success方法里执行execute2()方法(暂时忽略参数),以及多定义两个回调方法即可:
function execute(success, error){
$.ajax({
type:"post",
url:"api/getDatas",
async:true,
data:{status: 1},
success:function(res){
success()//先不做类型判断
},
error:function(err){
error()
}
})
}
function execute2(success2, error2){
$.ajax({
type:"post",
url:"api/getDatas2",
async:true,
data:{status: 2},
success:function(res){
success2()//先不做类型判断
},
error:function(err){
error2()
}
})
}
let success2 = function(res){
//2
}
let error2 = function(err){
//2
}
let success = function(res){
//1
execute2(success2, error2)
}
let error = function(err){
//1
}
execute(success, error)
代码是不是就这样分离开来了,再嵌套只需要重复上面的步骤即可,嵌套的问题自然就解决了,但是代码这样写结构比较松散,因为这种结构从形式上跟构造器+原型链的模式很相似,所以我们可以利用面向对象的方法来整理代码,写成这样:
function Promise(callback){
this.res = null
this.err = null
this.status = "0"
this.resolve = (res)=>{
this.res = res
this.status = "1"
}
this.reject = (err)=>{
this.err = err
this.status = "2"
}
callback(this.resolve, this.reject)
}
Promise.prototype.then = function(res){
if(this.status==="1"){
typeof res === 'function' && res(this.res)
}
return this
}
Promise.prototype.catch = function(err){
if(this.status==="2"){
typeof err === 'function' && err(this.err)
}
return this
}
可以看到,我们将execute换成了callback,success换成了resolve,error换成了reject,另外还增加了status来标记成功与失败的状态,成功时同步给this.res赋值,失败时同步给this.err赋值,新增链式写法的then和catch回调,下面我们来看看用法:
将execute方法给Promise来运行:
let promise = new Promise(execute)
promise.then(function(res){
console.log(res)
})
可以看到,这样就变成了链式的写法,当然你也可以用class{}
类的写法来实现;
但是这里有个问题:
如果execute是同步函数
function execute(success, error){ success(666) }
new完以后会先执行resolve回调然后再执行then方法;
但如果execute是异步函数
function execute(success, error){ setTimeout(()=>{ success(666) },1000) }
new完以后就会先执行then再到异步回调resolve
这样一来,就没办法保证异步链式写法的执行顺序了,怎么办呢?
那就在resolve里面执行then方法的回调,那then方法用来干嘛呢?用来注册将要执行的回调方法!于是就要定义多两个构造属性用来存储then与catch回调方法,同时,为了保证同步异步执行的顺序一致,都是先执行then再到resolve,利用setTimeout来执行callback:
//为了代码简洁直观,暂不作细致判断
function Promise(callback){
this.res = null
this.err = null
this.status = "0"
this.onThenBack = null
this.onCatchBack = null
this.resolve = (res)=>{
this.res = res
this.status = "1"
this.onThenBack(this.res)
}
this.reject = (err)=>{
this.err = err
this.status = "2"
this.onCatchBack(this.err)
}
setTimeout(()=>{
callback(this.resolve, this.reject)
}, 0)
}
Promise.prototype.then = (res)=>{
typeof res === 'function'?this.onThenBack = res : ""
}
Promise.prototype.catch = (err)=>{
typeof err === 'function'?this.onCatchBack = err : ""
}
到这里就基本完成了then的链式写法,试着执行下面的代码:
function execute(success, error){
setTimeout(()=>{
success(666)
},3000)
}
new Promise(execute).then((res)=>{ alert(res) })
可以测试,上面的代码是可以在3s后执行then里的回调的(实际测试要将Promise改个名字);
到这里就完成了Promise的第一步:将回调写在外面,即then的链式写法;
实际中使用Promise的时候,代码执行时间确实是有些许延迟,因为Promise源码中确实使用了setTimeout来执行代码,在前端性能优化的过程中,发现,从发起接口请求到请求拦截器之间,是会延迟100ms左右的时间,这是小编在做极致性能优化过程中追踪到的,没想到是Promise的锅
(未完待续)