一、Promise介绍与基本使用
1.1 何为Promise
-
Promise 是一门新的技术(ES6规划)
-
Promise 是JS中进行异步编程的新的解决方案,旧方案是纯回调函数;
异步编程:
- fs 文件操作
- 数据库操作
- AJAX
- 定时器
-
从语法上来说: Promise是一个构造函数
-
从功能上来说: promise对象用来封装一个异步操作并可以获取其成功/失败的结果值
1.2 为何使用Promise
-
指定回调函数的方法更灵活
- 旧的:必须在启动异步任务前指定
- promise:启动异步任务=>返回 promie对象=>给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
-
支持链式调用、可以解决回调地狱问题
- 回调地狱:回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件
1.3 Promise小案例
假设有一个抽奖程序,30%的中奖率,点击按钮抽奖,1s后出结果。JS代码如下:
<body>
<button>点我抽奖</button>
<script>
const rand = function(n,m){
return n + Math.floor((m-n+1)*Math.random())
}
const btn = document.getElementsByTagName('button')[0];
btn.onclick = function(){
setTimeout(()=>{
let n = rand(1,100);
console.log(n)
if(n<=30){
console.log("恭喜!")
}else{
console.log("非酋")
}
},1000)
}
</script>
</body>
下面用Promise来实现上述代码:
resolve 解决
reject 拒绝 两者都是函数类型的数据
btn方法体:
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
let n = rand(1,100);
console.log(n)
if(n<=30){
resolve(); //将Promise的状态设置为成功
}else{
reject(); //将Promise的状态设置为失败
}
},1000)
})
//调用then方法,用于指定回调,参数为两函数,第一个是成功时的回调,第二个是失败时的回调
p.then(()=>{
console.log("恭喜!")
},()=>{
console.log("非酋")
})
resolve和reject函数可以传递结果值,假设n即为结果,那么可以这样写:
if(n<=30){
resolve(n); //将Promise的状态设置为成功
}else{
reject(n); //将Promise的状态设置为失败
}
回调函数中就可以利用结果值了
注意,value和reason都是回调形参;
p.then((value)=>{
console.log("恭喜!你的中奖号码为:"+ value);
},(reason)=>{
console.log("非酋,你的号码为:"+ reason);
})
1.4 Promise封装AJAX请求
定义一个sendAJAX函数,用于发送GET AJAX请求,参数为url,返回Promise对象。
<body>
<button>点我发送请求</button>
<div id="result"></div>
<script>
function sendAJAX(url){
return new Promise((resolve,reject)=>{
const xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.send();
xhr.onreadystatechange = function (){
if(xhr.readyState ===4){
if(xhr.status>=200 && xhr.status<300){
resolve(xhr.response);
}else {
reject(xhr.status)
}
}
}
})
}
const btn = document.getElementsByTagName('button')[0];
const result = document.getElementById('result');
btn.onclick = function(){
sendAJAX('http://127.0.0.1:8000/server-json').then(value => {result.innerHTML = value.toString()},reason => {alert(reason)})
}
</script>
</body>
二、Promise API
2.1 Promise 对象的状态
实例对象中的一个属性 :PromiseState,值有:
- pending 未决定的
- resolved / fulfilled 成功
- rejected 失败
promise 的状态改变:
- pending变为resolved
- pending变为rejected
只有上述两种,且每个Promise对象只能改变一次,无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般称为value,失败的结果数据一般称为reason
2.2 Promise对象的值
实例对象中的另一个属性 :PromiseResult,保存着对象成功或失败的结果。
2.3 Promise的工作流程
2.4 API
构造函数
const promise = new Promise((resolve,reject)=>{...})
Promise.prototype.then方法
- p.then(onResolved,onRejected)
- onResolved函数:成功的回调函数 (value)=>{…}
- onRejected函数:失败的回调函数 (reason)=>{…}
- 如果resolve()或reject()中传入是一个Promise对象,回调的将会是resolve()方法或reject()方法返回值的值,并不是直接回调resolve()和reject()的返回值
promise.then(value=>{...},reason=>{...})
Promise.prototype.catch 方法*
-
p.(onRejected)
-
只能传失败的回调函数
promise.catch(reason=>{...})
Promise.resolve() 方法
- 注意,这个方法不属于实例对象,而是属于Promise函数,另外要与新建Promise时涉及的resolve区别开来,那个resolve只是约定俗成的形参,而这个是实打实的函数,实际上,实例对象中的形参resolve调用的就是Promise.resolve();
- 如果传入的形参为非Promise类型的对象,则返回结果为成功的promise对象,即状态为resolved,值为传入对象;
- 如果传入的形参为Promise对象,则参数的结果决定了resolve()的结果,相对于就返回了这个Promise对象参数
Promise.reject() 方法
- 同上
- 无论传入什么形参,都会得到一个失败的Promise对象
- 注意理解上面那句话,如果传入的是一个成功的Promise对象,那么返回值依然是一个失败的Promise对象,只不过返回的对象的值是那个成功的Promise对象
//传入非Promise数据类型
let p1 = Promise.resolve('hello');
let p2 = Promise.reject('hi?')
//传入结果为失败的Promise对象
let p3 = Promise.resolve(new Promise((a,b)=>{
if(1>2){
a('hello')
}else {
b('hi')
}
}))
//传入结果为成功的Promise对象
let p4 = Promise.reject(new Promise((a,b)=>{
if(1){
a('hello')
}else {
b('hi')
}
}))
console.log(p1);
console.log(p2);
console.log(p3);
console.log(p4);
输出结果为:(报错是因为失败的promise没有进行处理)
通过下面代码可以更进一步的了解resolve()、reject()和then()的联系和区别:
<script>
new Promise((a,b)=>{
let n = Math.random();
console.log(n);
if(n>0.5){
a(new Promise((a,b)=>{
b('hello');
}))
}else{
b(new Promise((a,b)=>{
a('hi?');
}))
}
}).then(value => {
console.log(value);
},reason => {
console.log(reason)
})
</script>
结果:
回调只回调对象的值,在形参为Promise对象的情况下,resolve()本质上就是返回形参对象,reject()本质上返回的是一个失败的Promise对象,该对象的值为形参Promise对象对象。
所以说在本例中:
- resolve()传入了一个失败的promise对象,值为hello,那么返回值就是一个失败的Promise对象,值为hello,所以回调为hello;
- reject()传入了一成功的promise对象,值为hi?,根据reject函数的特性,返回值为一个失败的Promise对象,值为形参对象,即一个成功的、值为hi?的Promise对象,那么回调只回调值,即一个成功的值为hi?的Promise对象
Promise.all() 方法**
- 形参为包含n个Promise对象的数组
- 返回一个新的Promise对象,只有所有的Promise对象都为成功才成功
- 如果所有数组promise对象都成功,那么返回一个成功的Promise对象,这个对象的值为一个数组,这个数组存储着形参promise数组的值
- 如果形参数组中有失败的promise对象,那么返回值为失败的Promise对象,它的值为传入形参数组中,首个失败对象的值;
<script>
let p1 = Promise.resolve('hello');
let p2 = Promise.resolve('world');
let p3 = Promise.resolve('!!!');
const result1 = Promise.all([p1,p2,p3]);
let p4 = Promise.resolve('hello');
let p5 = Promise.reject('world');
let p6 = Promise.reject('!!!');
const result2 = Promise.all([p4,p5,p6]);
console.log(result1);
console.log(result2);
</script>
输出结果为;
Promise.race() 方法
-
形参为包含n个Promise对象的数组
-
返回一个新的Promise对象,第一个完成的Promise的结果状态即为最终的结果状态
三、Promise 关键问题
3.1 如何修改对象的状态
- resolve(value):如果当前是pending,就会变成fulfilled;
- reject(reason):如果当前是pending,就会变成rejected;
- throw抛出异常:如果当前是pending,就会变成rejected;
3.2 能否执行多个回调
当promise改变为对应状态后,都会调用
<script>
let p = new Promise((resolve,reject)=>{
if(Math.random()>0.5){
resolve('hello');
}else{
reject('hi')
}
})
p.then((value)=>{
console.log(value)
},(reason)=>{
console.log(reason)
})
p.then((value)=>{
console.log(value)
},(reason)=>{
console.log(reason)
})
p.then((value)=>{
console.log(value)
},(reason)=>{
console.log(reason)
})
</script>
3.3 改变promise状态和指定回调函数谁先谁后
-
都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调
-
如何先改状态再指定回调?
- 在执行器中直接调用resolve()/reject()
- 延迟更长时间才调用then()
-
什么时候才能得到数据?
- 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
- 如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据
3.4 then方法返回结果由什么决定
-
then()方法内部会执行回调函数,外部还可以返回一个新的Promise对象;
-
返回结果由then()指定的回调函数执行的结果决定:
-
如果回调函数都有没有返回值,那么哪种回调函数被执行了,then返回的就是哪种类型的Promise对象,不过该对象没有值(undifined)
-
如果抛出异常,新 promise变为rejected, reason为抛出的异常
-
如果返回的是非promise的任意值(无论哪个回调函数被执行),新 promise变为resolved/fulfilled, value为返回的值
-
如果返回的是另一个新promise,此promise的结果就会成为新promise的结果
-
<script>
const p = new Promise((resolve,reject)=>{
let n = Math.random();
console.log(n);
if(n>0.5){
resolve("success")
}else{
reject("fail")
}
})
//1、无返回值
let result1 = p.then(value => console.log(value),reason => console.log(reason));
console.log("1、无返回值的情况如下:")
console.log(result1)
//2、抛出异常
let result2 = p.then(value => {throw 'success-error';},reason => {throw 'fail-error'});
console.log("2、抛出异常的情况如下:")
console.log(result2)
//3、返回值为非Promise类型
let result3 = p.then(value => {return 'success-type';},reason => {return 'fail-type'});
console.log("3、返回值为非Promise类型的情况如下:")
console.log(result3)
//4、返回值为Promise类型
let result4 = p.then(value => {
return new Promise((resolve,reject)=>{
resolve("promise-success")
});
},reason => {
return new Promise((resolve,reject)=>{
reject("promise-fail")
});
});
console.log("4、返回值为Promise类型的情况如下:")
console.log(result4)
</script>
补充一点:如果Promise的结果为失败,then方法没有进行失败的回调,那么then方法的返回值就是一个失败的Promise对象,值为那个结果的失败值
<script>
const p = new Promise((resolve,reject)=>{
reject("fail");
})
let result = p.then(value => {
console.log(value)
})
console.log(result)
</script>
下面的异常穿透就利用到了这个特性;
3.5 串联多个任务
由于then方法的结果依然是一个Promise对象,因此可以再使用then方法调用下去,把任务串联起来;
<script>
new Promise((resolve,reject)=>{
resolve("success");
}).then(value => {
return new Promise((resolve,reject)=>{
resolve(value);
})
}).then(value =>console.log(value))
</script>
3.6 异常穿透
- 当使用promise的 then链式调用时,可以在最后指定失败的回调,
- 前面任何操作出了异常,都会传到最后失败的回调中处理
<script>
let p = new Promise((resolve,reject)=>{
resolve('ok')
});
p.then(value => {
console.log('1')
}).then(value => {
console.log('2')
}).then(value => {
console.log('3')
}).catch(reason => {
console.warn(reason)
})
</script>
输出结果依次为1,2,3,如果将resolve(‘ok’),改成reject(‘fail’),那么最终输出结果将是fail
3.7 如何中断Promise链
在想要中断的then链前,返回一个状态为pending的promise的对象。这样就会因为pending状态没有改变,后面的then就不会执行,达到中断效果。
<script>
let p = new Promise((resolve,reject)=>{
resolve('ok')
});
p.then(value => {
console.log('1')
return new Promise(()=>{});
}).then(value => {
console.log('2')
}).then(value => {
console.log('3')
}).catch(reason => {
console.warn(reason)
})
</script>
四、Promise 自定义封装
相对于自己重新写Promise这个函数(或叫类)
五、async与await
5.1async函数
-
标志一个函数,使之成为async函数
-
函数的返回值为promise对象
-
promise对象的结果由async函数执行的返回值决定(和resolve()高度类似)
- 如果抛出异常,返回的是一个失败的promise对象,值为抛出异常的值
<script>
async function fun1(){
return "1"
}
let result = fun1();
console.log(result)
</script>
5.2 await表达式
- await右侧的表达式一般为promise对象,但也可以是其它的值
- 如果表达式是promise对象, await返回的是promise成功的值
- 如果表达式是其它值,直接将此值作为await的返回值
注意:
- await必须写在async.,函数中,但 async.,函数中可以没有await
- 如果await的 promise失败了,就会抛出异常,需要通过 try…catch捕获处理
<script>
async function fun1(){
let p = new Promise((resolve,reject)=>{
resolve("ok")
});
let result = await p;
console.log(result)
}
fun1();
</script>
return “1”
}
let result = fun1();
console.log(result)
## 5.2 await表达式
- await右侧的表达式一般为promise对象,但也可以是其它的值
- 如果表达式是promise对象, await返回的是promise成功的值
- 如果表达式是其它值,直接将此值作为await的返回值
注意:
- await必须写在async.,函数中,但 async.,函数中可以没有await
- 如果await的 promise失败了,就会抛出异常,需要通过 try...catch捕获处理
```javascript
<script>
async function fun1(){
let p = new Promise((resolve,reject)=>{
resolve("ok")
});
let result = await p;
console.log(result)
}
fun1();
</script>