Promise概念
- 背景:异步任务的代码,不能保证按照顺序执行,如果我们非要代码顺序执行,就需要嵌套回调函数,像这种回调函数中嵌套回调函数的情况就叫做回调地狱;回调地狱会造成我们的代码可读性非常差,后期不好维护;而Promise的链式编程可以保证代码的执行顺序,更容易阅读和理解。
- 概念:Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理
异步/同步的概念
同步: 在执行某段代码时,在没有得到返回结果之前,其他代码暂时是无法执行的,但是一旦执行完成拿到返回值,即可执行其他代码。也就是说,在此段代码执行完未返回结果之前,会阻塞之后的代码执行,这样的情况称为同步。
异步: 当某一代码执行异步过程调用发出后,这段代码不会立刻得到返回结果。而是在异步调用发出之后,一般通过回调函数处理这个调用之后拿到结果。异步调用发出后,不会影响阻塞后面的代码执行,这样的情况称为异步。
Promise的状态、过程和特点
- 三状态:
`pending`: 进行中,Promise创建时的状态
`fulfilled`: 已成功,在Promise执行器中调用resolve后的状态
`rejected`: 已失败,在Promise执行器中调用reject后的状态
-
两过程
pending-->fulfilled:resolved(已完成)
pending-->rejected :rejected(已拒绝)
-
两特点:
Promise对象的状态一经改变就不会再变,任何时候都可以获取结果:只要发生了状态的改变,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对
Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。Promise对象的状态不受外界影响:Promise对象代表一个异步操作,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
Promise用法
1.Promise的创建
let promise = new Promise((resolve,reject)=>{
if(/* 异步操作成功 */) resolve(/*成功后向外传递的值*/);
else reject(/*失败后向外给的提示信息*/);
})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,resolve函数的作用是,将Promise对象的状态从“进行中”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“进行中”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
一般情况下,我们会用new Promise()来创建Promise对象。除此之外,也可以使用promise.resolve和 promise.reject这两个方法来创建。
2.常用方法
2.1 .then()
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数。
let promise = new Promise((resolve,reject)=>{
resolve(100);
});
promise.then(data=>{
console.log('成功,获取到值:'+data);//成功,获取到值:100
},error=>{
})
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
let promise = new Promise((resolve,reject)=>{
resolve(100);//这里使用resolve抛出数值,则下一个then如有第一个回调参数,则执行第一个回调函数的代码块
});
promise.then(data=>{
console.log('成功,获取到值:'+data);//成功,获取到值:100
new Promise((resolve,reject)=>{
reject(200);//这里使用reject抛出数值,则下一个then有第二个回调参数,则执行第二个回调函数的代码块
})
},error=>{
console.log('失败:'+error);
}).then(data=>{
console.log('成功,获取到值:'+data);
},error=>{
console.log('失败:'+error);//失败: 200
});
2.2 .catch()
catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
let promise = new Promise((resolve,reject)=>{
reject(100);
});
promise.catch(error=>{
console.log('发生错误:' + error);
})
catch方法还有一个作用,就是在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中
2.3 .finally()
.finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
2.4 .all()
.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
let p1 = new Promise((resolve,reject)=>{
resolve('我是p1');
});
let p2 = new Promise((resolve,reject)=>{
reject('我是p2');
});
let p3 = new Promise((resolve,reject)=>{
resolve('我是p3');
});
let p = Promise.all([p1, p2, p3]);
上面代码中,all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
上面代码中p的状态就是第二种情况,此时第二个Promise实例的值被返回,如下图
我们再看一个例子:
let p1 = new Promise((resolve, reject) => {
resolve('我是p1');
})
.then(result => result)
.catch(e => e);
let p2 = new Promise((resolve, reject) => {
throw new Error('我是p2,我开始报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
//结果为:[我是p1,Error:我是p2,我开始报错了]
上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。
2.5 .any()
ES2021 引入了any()。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回
let p = Promise.all([p1, p2, p3]);
上面代码中p状态的结果与使用all()方法相反但又有点不同,p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成rejected,p的状态才会变成rejected但不是一个数组。
(2)只要p1、p2、p3之中有一个被fulfilled,p的状态就变成fulfilled,此时第一个被resolve的实例的返回值,会传递给p的回调函数。
2.6 .race()
.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
let p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
.race()方法的参数与.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。
2.7 .allSettled()
有时候,我们希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作,这个时候就需要使用allSettled()方法啦。
let p = Promise.allSettled([p1, p2,p3])
p.then(data=>{
console.log(data);
});
上面代码中p的状态只可能变成fulfilled。它的回调函数接收到的参数是一个数组。该数组的每个成员都是一个对象,对应传入.allSettled()的数组里面的 Promise 对象,如下图。
成员对象的status属性的值只可能是字符串fulfilled或字符串rejected,用来区分异步操作是成功还是失败。如果是成功(fulfilled),对象会有value属性,如果是失败(rejected),会有reason属性,对应两种状态时前面异步操作的返回值。
2.8 .resolve()
有时需要将现有对象转为 Promise 对象,.resolve()方法就起到这个作用。
Promise.resolve()等价于下面的写法。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
.resolve()方法的参数分成四种情况。
- 参数是一个 Promise 实例
如果参数是 Promise 实例,那么
Promise.resolve将不做任何修改、原封不动地返回这个实例。 - 参数是一个
thenable对象thenable对象指的是具有then方法的对象,比如下面代码中的这个对象。
let thenable = {
a:1,
then: function (resolve,reject) {
resolve('我是thenable对象');
}
};
let p = Promise.resolve(thenable);
p.then(data=>{
console.log(data);//我是thenable对象
});
resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。上面代码中,thenable对象的then()方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then()方法指定的回调函数,输出'我是thenable对象'。
- 参数不是具有
then()方法的对象,或根本就不是对象
resolve()方法返回一个新的 Promise 对象,状态为resolved:
let obj = {
a:1,
};
let p = Promise.resolve(obj);
p.then(data=>{
console.log(data);//{a:1}
});
上面代码生成一个新的 Promise 对象的实例p。由于对象没有then方法,返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve()方法的参数,会同时传给回调函数,输出:{a:1}
- 不带有任何参数
调用时不带参数,则直接返回一个resolved状态的 Promise 对象。
2.9 .reject()
reject方法和resolve不同,reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
Promise练习
Promise.resolve(100).then(result => {
console.log(`成功:${result}`);//100
return result / 10;
},
reason => {
console.log(`失败:${reason}`);
return Promise.resolve(200);
}).then(result => {
console.log(`成功:${result}`);//10
return Promise.reject(300);
}, reason => {
console.log(`失败:${reason}`);
return reason / 20;
}).then(result => {
console.log(`成功:${result}`);
return 0;
}, reason => {
console.log(`失败:${reason}`);//300
return 1;
}).then(result => {
console.log(`成功:${result}`);//成功1
}, reason => {
console.log(`失败:${reason}`);
});