什么是Promise
是一种异步编程的解决方案。可以理解为一种包装异步操作的方案。就是使得我们的异步操作以一种更优雅的形式进行呈现。使得我们的异步代码更清晰,更明了,更易于维护。也就是说Promise是用来封装我们的异步操作。
异步嵌套(回调地狱)
用一个有点儿夸张的例子,描述一下描述一下Promise的作用。
setTimeout(function(){
console.log("a");
setTimeout(function(){
console.log("b");
setTimeout(function(){
console.log("c");
},1000)
},1000)
},1000);
三个异步操作的嵌套。这样的代码当然是可以正常运行的。但是它的可读性,可维护性实在是太差了。这时候我们就可以使用Promise来进行包装。
new Promise((resolve,reject)=>{//包装第一个异步操作
setTimeout(function(){
resolve();
},1000)
}).then(()=>{//第一个异步操作成功要执行的代码,分离出来了
console.log("a");
return new Promise((resolve,reject)=>{//创建一个Promise对象,包装第二个异步操作
setTimeout(function(){
resolve();
},1000)
})
}).then(()=>{//第二个异步操作成功的代码放在这里
console.log("b");
return new Promise((resolve,reject)=>{//创建一个Promise对象,包装第三个异步操作
setTimeout(function(){
resolve();
},1000)
})
}).then(()=>{//第三个异步操作成功的代码放在这里
console.log("c");
})
Promise包装异步操作的过程
- 第一步,创建一个Promise对象,他需要传一个参数,这个参数是一个函数并且是固定的:
(resolve,reject)=>()
,并且resolve和reject也是一个函数。 - 第二步,将异步操作包装进去,但是将其实际处理的代码分离出来。这个可能不是很好描述。拿数据请求来举例,当请求成功的时候要执行相应操作,在请求失败的时候又要执行相应操作。把这些操作都分离出来。怎么分离呢?通过then方法和catch方法。
这是用Promise包装异步操作的过程描述图。他会存在三个状态,以网络请求举例。
- 第一个阶段,pending状态,等待阶段。等待请求返回请求结果。
- 第二个阶段,fulfilled状态,满足阶段。就是请求成功了,这个时候我们会调用resolve函数,调用之后,就会执行.then()中的内容。.then方法的参数也是一个函数
()=>{}
,这个函数的参数和resolve函数一致。一般成功之后,我们需要处理data数据就是下面这样一个过程:
new promise((resolve,reject)=>{
$.ajax("/url",function(data){
resolve(data);//成功调用它并且将data传入。
reject(err);//失败调用它并将err出入
})
}).then(a=>{})//resolve执行完后,立即调用它,并将resolve中的data赋值给a
.catch(b=>{})//reject执行完后,立即调用它, 并将reject中的err赋值给b
- 第三阶段,reject状态,拒绝状态。就是请求失败了,这时候我们会调用reject函数,调用之后,我们会回调catch方法。它的参数是一个函数
()=>{}
Promise的另一种形式
还是以网络请求为例,请求成功和失败的代码就是上文那样的:
new promise((resolve,reject)=>{
$.ajax("/url",function(data){
resolve(data);
reject(err);
})
}).then(data=>{})
.catch(err=>{})
其实我们可以全部写在then方法中,而不使用catch方法。这时候then方法需要传入两个函数。
new promise((resolve,reject)=>{
$.ajax("/url",function(data){
resolve(data);
reject(err);
})
}).then(data=>{},err=>{})
Promise的链式调用
第一种形式,处理异步操作的嵌套(回调地狱)
就是我们前面处理的打印a,b,c的操作,就是Promise的链式调用。
第二种形式,分解then中的操作
描述可能有点抽象。那我们从需求出发,我进行请求,请求成功,接下来我要操作请求来的数据,进行三次处理。我不想将三次处理混在一起。也就是我想分为三个then方法。那这个怎么实现呢?
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("a");
reject("err")
})
}).then((res)=>{//第一次操作
res=res+"b";
return new Promise(resolve=>{//精髓在这儿
resolve(res)
})
}).then((res)=>{//第二次操作
res=res+"c";
return new Promise(resolve=>{
resolve(res)
})
}).then((res)=>{//第三次操作
res=res+"d";
console.log(res);
})
比如说我这,我简单模拟了一下,我的需求是把"a"拼接成'abcd'。我把每次拼接都拆分成一次操作。这里是怎么实现then方法的连用呢?
如果要实现then方法的连用,那么每次then方法执行的返回值应该是一个Promise对象。因为then方法是Promise的方法只能Promise对象才能调用。并且返回的这个Promise对象有点特殊,他要进入下个then方法的话,必然是要执行resolve方法。对吧。其实这里reject没有存在的意义。(本身new Promise((resolve,reject)=>{})
中的reject就是一个可选参数!!)那么这里我们直接省略reject参数。就形成了我们上文的结构
前面形式的语法糖写法
new Promise(resolve=>{resolve(data)}).then((data)=>{})
这种写法等于
Promise.resolve(data).then((data)=>{})
那上面的代码可以改写为:
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("a");
reject("err")
})
}).then((res)=>{
res=res+"b";
return Promise.resolve(res)
}).then((res)=>{
res=res+"c";
return Promise.resolve(res)
}).then((res)=>{
res=res+"d";
console.log(res);
})
还有一种更简洁的写法:直接return 其实内部做的事儿应该是一样的。只是一种语法糖。
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("a");
reject("err")
})
}).then((res)=>{
res=res+"b";
return res
}).then((res)=>{
res=res+"c";
return res
}).then((res)=>{
res=res+"d";
console.log(res);
})
这三段代码是一模一样的。可以在console控制台运行查看结果。
抛出异常的简写
另外其实抛出错误也一种简洁写法。
new Promise((resolve,reject)=>{
reject("err");
})
这种形式是不是一定抛出异常呢?他也有种简写。
Promise.reject("err")
好了,关于Promise的知识就涉及到这儿, 以后我会继续补充的。加油学习0.0
Promise的all用法
还是从需求出发理解每个用法的作用。
比如我现在得进行两次网络请求,我得等到两次网络请求都请求到再来处理请求结果该怎么实现呢?
Promise提供了一种all方法。
Promise.all(iteator)
iterator就是之前我在ES6文章中提到的可迭代对象。这里可以看成数组,数组的元素都是用Promise封装的异步操作
Promise.all([
new Promise((resolve,reject)=>{
setTimeout(() => {
resolve("a");
}, 1000);
}),new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('b');
}, 5000);
})]).then(res=>console.log(res))
也就是说当两个异步操作都执行resolve方法的时候,也就是都处于fulfilled状态的时候,才会执行then方法。并且这里的then方法的res是一个数组,数组的元素是由每个resolve函数的实参组成的。所以这里应该是['a','b']