ES6新特性——Promise对象的使用
前言
说到ES6中提出的新特性,
Promise
对象应该算是其中十分重要的成员之一了,Promise
对象的存在使得我们在处理异步操作的时候写法更加简洁,且结合async
和await
关键字使用的话会让代码更加清晰和优雅,是建议前端开发人员必须要掌握的知识点之一。本篇文章将对Promise
对象的使用和相关的特性进行讲解,希望对大家有所帮助。
一、什么是Promise?
介绍Promise之前,我们不妨先来看一下我们以前在JS中是如何处理异步操作的呢?
常见的做法是使用回调函数,但回调函数虽然能够解决我们实现异步操作队列化的需求。但当出现一个方法中嵌套着多个回调函数的时候,此时代码的结构就会变得十分复杂和臃肿,也就是出现回调地狱的问题。
而Promise
对象的产生则能够帮助我们较为优雅地解决多个回调函数嵌套的问题,让我们的JS代码结构更加清晰,维护起来更加方便。
二、Promise的简单使用
从语法上来说: Promise 是一个构造函数,我们需要给Promise
对象提供一个函数,函数可以接收到resolve
和reject
两个参数。分别对应成功和失败的处理结果,创建Promise对象后,我们可以使用then
来获取Promise对象中执行。
我们可以来看一下下面这个案例,页面中存在有一个按钮,点击后根据随机产生的数值来判断是否抽奖成功。我们在代码中进行了如下判断:
(1)当随机数小于50时,执行resolve
函数,并将数值传入
(2)当随机数大于50时,执行reject
函数,并将数值传入
当 p 执行then方法时,需要我们传入2个函数,分别对应着Promise中的函数执行结果,如果Promise对象中执行了resolve,那么就会走第一个函数的方法,否则就会走第二个函数的方法。
<body>
<!-- Promise是ES6推出的重要规范之一,主要是用于帮助我们简化异步操作的书写的 -->
<button onclick="Sweepstakes()">点击抽奖</button>
<script>
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
}
function Sweepstakes() {
const p = new Promise((resolve, reject) => {
const result = rand(1, 100);
if (result <= 50) {
resolve(result);
} else {
reject(result);
}
});
//执行then函数时,可以接收到2个func类型的参数
p.then(value => {
console.log(`恭喜抽到特等奖,抽奖号码为${value}`)
}, reason => {
console.log(`很遗憾,没有抽到奖,抽奖号码为${reason}`)
})
}
</script>
</body>
三、了解一下Promise的属性和 then
方法
我们可以在控制台上打印一下我们创建的Promise对象,可以发现Promise对象上主要有着PromiseState
和PromiseResult
两个属性。
<script>
function test1(){
const p = new Promise((resolve,reject)=>{
resolve('aaa');
})
console.log(p);
}
test1();
</script>
下面我们来介绍一下各个属性的作用:
PromiseState:
PromiseState 表示Promise对象中函数的执行结果,主要有以下三个状态
(1)pending :表示初始状态,一旦执行resolve或者reject函数后,Promise对象就会由pending状态更新为其他状态。
(2)fulfilled或者resolved:当Promise对象中执行resolve方法时,Promise对象即由pending
转换为fulfilled
状态。
(3)rejected:当Promise对象中执行reject方法时,Promise对象即由pending
转换rejected
状态。
需要注意的是,一个 promise 对象只能改变一次状态, 无论变为成功还是失败, 都会有一个结果数据, 成功的结果数据一般称为 value, 失败的结果数据一般称为 reason。
PromiseResult
PromiseResult 表示Promise对象函数中传递给resolve
或者reject
方法的参数,我们一般会在then
方法中通过value或者reason形参来接收到这个参数。
then方法的使用
我们从上面的介绍中可以知道,可以通过Promise对象.then(value=>{} , reason=>{})
这种方式来获取Promise的处理结果。
第一个参数对应着promise执行成功的回调函数,第二个参数对应着promise执行失败后的回调函数。
除了入参之外,我们还需要注意一点,promise对象的then方法总是会在promise中的函数执行完之后再执行的,即使promise函数中执行的是异步函数。
<script>
function test1(){
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('aaa');
resolve();
},3000)
})
p.then(value=>{
console.log('bbb')
})
}
test1();
</script>
四、Promise对象的API
(一)catch
除了在then中指定失败的回调函数之外,我们还可以通过Promise对象.then().catch()
来进行失败函数的执行:
<script>
const p = new Promise((resolve,reject)=>{
reject('aaa');
})
p.then(value=>{
console.log(value);
}).catch(reason=>{
console.error(reason);
})
</script>
需要注意的是,如果既在then中指定了失败的回调函数,又在catch中指定了失败的回调函数,那么此时只会执行then方法中定义的回调函数。
(二)Promise.resolve()
Promise中提供了一个resolve方法来快速创建Promise对象,resolve方法中可以接受参数,如果传入的参数为 非Promise类型的对象, 则返回的结果为成功promise对象。如果传入的参数为 Promise 对象, 则参数的结果决定了 resolve 的结果。
<script>
const p1 = Promise.resolve('aaa');
const p2 = Promise.resolve(new Promise((resolve,reject)=>{
reject();
}));
console.log(p1);
console.log(p2);
</script>
(三)Promise.reject()
reject方法和Promise.resolve()
方法相似,区别在于无论reject方法中传递的是什么参数,PromiseState的状态都是rejected。
(四)Promise.all()
all方法中要求我们传入包含 n 个 promise 的数组,结果返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就直接失败。
<script>
const p1 = Promise.resolve('aaa');
const p2 = Promise.resolve('bbb');
const p3 = Promise.resolve('ccc');
const result1 = Promise.all([p1,p2,p3]);
const p4 = Promise.resolve('aaa');
// 注意,x下面是reject
const p5 = Promise.reject('bbb');
const p6 = Promise.reject('ccc');
const result2 = Promise.all([p4,p5,p6]);
console.log(result1);
console.log(result2);
</script>
需要注意的是,如果当遇到第一个执行失败的promise时,接下来的promise就不会再继续调用了,所以这里图片中第二个执行all()
函数的结果中PromiseResult
是bbb。
(五)Promise.race()
race方法是一个很有意思的方法,race的翻译是赛跑,它在这里的功能也和赛跑有些类似,传入多个Promise对象,第一个完成的 promise 的结果状态就会是最终的结果状态。
<script>
const p1 = Promise.resolve(new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('aaa');
},1000);
}));
const p2 = Promise.resolve('bbb');
const p3 = Promise.resolve('ccc');
const result = Promise.race([p1,p2,p3]);
console.log(result);
</script>
五、关于Promise对象的注意事项
(一)如何改变 promise 的状态?
(1) resolve(value): 如果当前是 pending 就会变为 resolved
(2) reject(reason): 如果当前是 pending 就会变为 rejected
(3) 抛出异常: 如果当前是 pending 就会变为 rejected
<script>
let p = new Promise((resolve, reject) => {
//1. resolve 函数
// resolve('ok'); // pending => fulfilled (resolved)
//2. reject 函数
// reject("error");// pending => rejected
//3. 抛出错误
// throw '出问题了';
});
console.log(p);
</script>
(二)一个 promise 指定多个成功/失败回调函数, 都会调用吗?
答案:改变 promise 状态和指定回调函数谁先谁后
<script>
const p = Promise.resolve('aaa');
// 第一次执行
p.then(console.log);
//第二次执行
p.then(console.log);
</script>
(三)改变 promise 状态和指定回调函数执行谁先谁后?
需要注意,这里不是指promise中的resolve/reject
函数和then中调用的具体函数执行的先后,而是单单指Promise对象更改状态和确定回调函数(并没有执行)是哪个函数发生的先后顺序。
(1) 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
(2) 如何先改状态再指定回调?
- 在执行器中直接调用 resolve()/reject()
- 延迟更长时间才调用 then()
(3) 什么时候才能得到数据? - 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
- 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
(四)promise.then()返回的新 promise 的结果状态由什么决定?
(1)简单表达:
由 then()指定的回调函数执行的结果决定
(其实这个问题就和Promise对象的PromiseState状态由什么决定一样,没啥特别值得说的)
(2) 详细表达:
① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
② 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
③ 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
(五)promise 串连多个操作任务
如果promise 的then()方法中会返回一个新的 promise对象, 我们可以使用 then()的链式调用来处理多个不同模块下的Promise对象。
<script>
function test() {
const p = Promise.resolve('aaa');
p.then(value => {
console.log(value);
return new Promise((resolve, reject) => {
resolve('bbb');
})
}).then(value => {
console.log(value);
})
}
test();
</script>
需要注意的是,实际上我们经常会利用promise这个特性来分别处理异步请求,解决回调地狱的问题
(六)promise 的异常传透
当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,这样如果前面任何操作出了异常, 都会传到最后失败的回调中处理。这样就有一个好处,如果对异常的处理方式相同,我们不再需要每次都在then函数中写相同的代码,只需要在最后进行统一的catch
捕获就行。
<script>
const p = new Promise((resolve,reject)=>{
resolve();
});
p.then(value=>{
console.log('aaa');
}).then(value=>{
console.log('bbb');
throw 'aaa';
}).then(value=>{
console.log('ccc')
}).catch(err=>{
console.error(err);
})
</script>
(七)中断promise调用链
当使用 promise 的 then 链式调用时, 假如说我们想要中断promise的调用链,我们可以在回调函数中返回一个 pendding 状态的 promise 对象,不再调用后面的回调函数。
<script>
const p = new Promise((resolve,reject)=>{
resolve();
})
p.then(value=>{
console.log('aaa');
}).then(value=>{
console.log('bbb');
// 单纯的返回Promise对象,并不能阻止后面then的运行
return new Promise(()=>{});
}).then(value=>{
console.log('ccc');
})
</script>
六、async和await关键字的使用
async
和await
关键字是ES7提出的新规范之一,利用这两者可以让我们的异步操作变得更加简洁
(一)async关键字
async
函数的返回值为 promise 对象,promise 对象的结果由 async 函数执行的返回值决定
- 当返回非Promise的数据类型,PromiseState作为成功来处理
- 当返回Promise数据类型,PromiseState根据Promise返回结果决定。
- 当抛出异常时,PromiseState作为失败来处理
<script>
async function test(){
// 1、返回非Promise的数据类型,PromiseState作为成功来处理
// return '111';
// return false;
// 2、返回Promise数据类型,PromiseState根据Promise返回结果决定。
// return new Promise((resolve,reject)=>{
// // reject();
// resolve();
// })
// 3、抛出异常
throw '错误';
}
const result = test();
console.log(result);
</script>
(二)await关键字
await 右侧的表达式一般为 promise 对象, 但也可以是其它的值, 如果表达式是 promise 对象, await 返回的是 promise 成功的值; 如果表达式是其它值, 直接将此值作为 await 的返回值
注意
- await 必须写在 async 函数中, 但 async 函数中可以没有 await
- 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理
<script>
// await关键字必须在有async标识的函数中使用
async function main() {
let p = new Promise((resolve, reject) => {
//resolve('OK');
reject('Error');
});
try {
const result = await p;
console.log(result);
} catch (err){
console.log(err);
}
}
main();
</script>
(三)async和await联合使用案例
在下面的案例中,我们对Ajax进行了简单的二次封装,然后使用await关键字让异步请求同步执行。
<script>
let testNum = 0;
function sendAJAX(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", url);
xhr.send();
//处理结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
//判断成功
if (xhr.status >= 200 && xhr.status < 300) {
//成功的结果
testNum = 5;
resolve(testNum);
} else {
reject(xhr.status);
}
}
}
});
}
async function test1() {
try {
const res1 = await sendAJAX('https://api.apiopen.top/getJoke');
const res2 = await sendAJAX('https://api.apiopen.top/getJoke');
const res3 = await sendAJAX('https://api.apiopen.top/getJoke');
console.log(testNum);
} catch (err) {
console.err(err);
}
}
test1();
</script>