Promise 什么是Promise Promise对象的特点 Promise对象的优缺点 .then .catch .finally .resolve() .reject() .all() ...
ES6 Promise
一丶什么是Promise
在浏览器中输出一下代码
var myPromise = new Promise(function(resolve, reject){
setTimeout(function(){
resolve("hello!");
}, 500);
});
console.log(myPromise);
看图可知:
-
Promise
是一个构造函数; -
__proto__
属性中保存了一个地址值,指向了实例化得到的myPromise
对象的原型对象; - 原型对象上的有3个实例方法
catch、finally、then
; - constructor属性中存储着一个地址指向了它的构造函数
Promise
; - 打开constructor,可以看到构造函数上的 静态方法 :
all、race、reject、resolve
- Promise的构造函数接收一个参数是函数,并且两个参数:
resolve
,reject
,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。
二丶Promise对象的特点
-
对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
- pending: 初始状态,挂起状态,异步任务正在执行,但还没执行完。
- fulfilled: 意味着操作成功。
- rejected: 意味着操作失败。
-
一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
new Promise(function(resolve, reject) { resolve('ok'); throw new Error('error'); }) .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) }); //Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了,所以会输出 ok
三丶Promise对象的优缺点
1.优点:
1.1. 解决回调地狱(Callback Hell)问题
之前没有Promise时,我们要让异步任务顺序执行,常规做法只能 callback 层层嵌套,但嵌套层数过多的话就会有 callback hell 问题。
1.2. 代码会变得扁平且更可读
如果使用 Promises 的话,前边的函数如果return了一个 Promises对象,就又可以接着.then 。我们可以将 then 的调用不停地串连起来,代码会变得扁平且更可读。
1.3. 更好地进行错误捕获
如果使用 promises 的话,通过 reject 方法把 Promise 的状态置为 rejected,这样我们就可以在 .catch 中执行“失败”情况的回调。
2.缺点:
2.1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
2.2. 如果不设置回调函数,promise内部抛出的错误,不会反应到外部。
2.3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
四丶创建一个Promise对象
var myPromise = new Promise(
function(resolve,reject){
//异步操作
if("异步任务的成功与否"){
resolve("意味着操作成功完成,把状态从pending改成fulfilled");
}else{
reject("意味着发生错误,把状态从pending改成rejected");
}
}
)
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
,它们是两个函数。
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态变为fulfilled
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise
对象传出的值作为参数。
可以看以下示例:
function runAsync(){
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('异步任务执行完成');
if(Math.random()<0.5){
resolve("异步任务执行成功");
}else{
reject("异步任务执行失败")
}
}, 1000);
});
}
runAsync().then(val=>{console.log(val)},val=>{console.log(val)})
注意:
用new创建一个Promise对象,并没有调用它,传进去的函数就已经执行了。所以我们使用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数
如果new Promise()被包裹在一个function(){}函数内,必须用return,抛出到函数外部,才能让后续任务知道,当前函数是new Promise()对象。
如果调用resolve
函数和reject
函数时带有参数,那么它们的参数会被传递给回调函数。reject
函数的参数通常是Error
对象的实例,表示抛出的错误;resolve
函数的参数除了正常的值以外,还可能是另一个 Promise 实例。
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
上面代码中,p1
和p2
都是 Promise 的实例,但是p2
的resolve
方法将p1
作为参数,即一个异步操作的结果是返回另一个异步操作。
注意,这时p1
的状态就会传递给p2
,也就是说,p1
的状态决定了p2
的状态。如果p1
的状态是pending
,那么p2
的回调函数就会等待p1
的状态改变;如果p1
的状态已经是resolved
或者rejected
,那么p2
的回调函数将会立刻执行。
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
上面代码中,p1
是一个 Promise,3 秒之后变为rejected
。p2
的状态在 1 秒之后改变,resolve
方法返回的是p1
。由于p2
返回的是另一个 Promise,导致p2
自己的状态无效了,由p1
的状态决定p2
的状态。所以,后面的then
语句都变成针对后者(p1
)。又过了 2 秒,p1
变为rejected
,导致触发catch
方法指定的回调函数。
注意,调用resolve
或reject
并不会终结 Promise 的参数函数的执行。
此段参考摘抄自ECMAScript 6 入门,查看原文 => ECMAScript 6 入门
五丶Promise.prototype.then
Promise.prototype.then()
:它最多有两个参数,一个参数时,这个参数是操作成功时的回调函数。两个参数时,第一个是操作成功时的回调函数,第二个是操作失败时的回调函数。
参数:当一个Promise对象的状态变为rejected时,被调用的一个方法。 该方法拥有一个参数,拒绝的原因。
返回值:
Promise.prototype.then方法参数返回值的详细介绍
采用链式的then
,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise
对象(即有异步操作),这时后一个回调函数,就会等待该Promise
对象的状态发生变化,才会被调用。
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function (comments) {
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
});
上面代码中,第一个then
方法指定的回调函数,返回的是另一个Promise
对象。这时,第二个then
方法指定的回调函数,就会等待这个新的Promise
对象状态发生变化。如果变为resolved
,就调用第一个回调函数,如果状态变为rejected
,就调用第二个回调函数。
六丶Promise.prototype.catch
Promise.prototype.catch()
:是操作失败时触发的回调函数。于.then
的第二个参数同理。
注意:
一般来说,不要在
then()
方法里面定义 rejected状态的回调函数(即then
的第二个参数),最好的选择是使用catch
方法。因为
catch
写法可以捕获前面then
方法执行中的错误,也更接近同步的写法(try/catch
)。
参数:当一个Promise对象的状态变为rejected时,被调用的一个方法。 该方法拥有一个参数,拒绝的原因。
返回值:一个新Promise
对象
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch
语句捕获。
function runAsync1(){
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('异步任务1执行完成');
if(Math.random()<0.5){
resolve();
}else{
reject();
}
}, Math.random()*2*1000+1000);//1-3随机数模拟异步任务不确定性
});
}
function runAsync2(){
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('异步任务2执行完成');
if(Math.random()<0.5){
resolve();
}else{
reject();3
}
}, Math.random()*2*1000+1000);//1-3随机数模拟异步任务不确定性
});
}
function runAsync3(){
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('异步任务3执行完成');
if(Math.random()<0.5){
resolve();
}else{
reject();
}
}, Math.random()*2*1000+1000);//1-3随机数模拟异步任务不确定性
});
}
runAsync1().then(runAsync2).then(runAsync3)
.then(()=>{ console.log("执行完成比赛结束") })
.catch(()=>{ console.log("执行失败比赛结束") })
以上的3个异步任务都有可能在中途执行失败,不管哪个失败,都会一直向后传递,直到被捕获为止。如果第一个失败了的话,就会直接调用catch,抛出异常。
七丶Promise.prototype.finally
finally()
方法在ES2018引入
用于指定不管 Promise 对象最后状态如何,都会执行的操作,它的的回调函数不接受任何参数,不依赖于Promise执行的结果。
参数:Promise
结束后调用的Function。
返回值:一个设置了 finally
回调函数的Promise对象。
new Promise(function(resolve,reject){
})
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
八丶Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就起到这个作用。
Promise.resolve()
方法的参数分成四种情况。
1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve
将不做任何修改、原封不动地返回这个实例。
2)参数是一个thenable
对象
thenable
对象指的是具有then
方法的对象,Promise.resolve()
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then()
方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function (value) {
console.log(value); // 42
});
上面代码中,thenable
对象的then()
方法执行后,对象p1
的状态就变为resolved
,从而立即执行最后那个then()
方法指定的回调函数,输出42。
3)参数不是具有then()
方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then()
方法的对象,则Promise.resolve()
方法返回一个新的 Promise 对象,状态为resolved
。
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s)
});
// Hello
上面代码生成一个新的 Promise 对象的实例p
。由于字符串Hello
不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved
,所以回调函数会立即执行。Promise.resolve()
方法的参数,会同时传给回调函数。
4)不带有任何参数
Promise.resolve()
方法允许调用时不带参数,直接返回一个resolved
状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()
方法
const p = Promise.resolve();
p.then(function () {
// ...
});
需要注意的是,立即resolve()
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
上面代码中,setTimeout(fn, 0)
在下一轮“事件循环”开始时执行,Promise.resolve()
在本轮“事件循环”结束时执行,console.log('one')
则是立即执行,因此最先输出。
九丶Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
Promise.reject('出错了')
.catch(e => {
console.log(e)
})
// 出错了
上面代码生成一个 Promise 对象的实例p
,状态为rejected
,回调函数会立即执行。
Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数。
十丶Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
Promise.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
的回调函数。
注意
如果作为参数的 Promise 实例,自己定义了
catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
上面代码中,p1
会resolved
,p2
首先会rejected
,但是p2
有自己的catch
方法,该方法返回的是一个新的 Promise 实例,p2
指向的实际上是这个实例。该实例执行完catch
方法后,也会变成resolved
,导致Promise.all()
方法参数里面的两个实例都会resolved
,因此会调用then
方法指定的回调函数,而不会调用catch
方法指定的回调函数。
如果p2
没有自己的catch
方法,就会调用Promise.all()
的catch
方法。
再看以下代码:
function zhang(){
return new Promise(
function(resolve){
var b="**张三的接力棒**"
console.log(`张三拿着[${b}]起跑`);
setTimeout(function(){
resolve(b);
},2000)
}
)
}
function li(){
return new Promise(
function(resolve){
var b="**李四的接力棒**"
console.log(`李四拿着[${b}]起跑`);
setTimeout(function(){
resolve(b);
},3000)
}
)
}
function wang(){
return new Promise(
function(resolve){
var b="**王五的接力棒**"
console.log(`王五拿着[${b}]起跑`);
setTimeout(function(){
resolve(b);
},1000)
}
)
}
Promise.all ( [ zhang( ) , li( ) , wang( ) ] )
.then(
function(arr){
console.log(arr[0],"zhang( )"),
console.log(arr[1],"li( )"),
console.log(arr[2],"wang( )")
}
);
看输出结果.then((arr)=>{})
中的回调函数中的参数arr所得到的数组中元素的排序和哪个异步任务的先执行完毕没有关系,只和异步任务在传入Promise.all()
数组中的排序有关。
十一丶Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
Promise.race()
方法的参数与Promise.all()
方法一样,如果不是 Promise 实例,就会先调用Promise.resolve()`方法,将参数转为 Promise 实例,再进一步处理。
Promise.all()
后边的.then()
方法只能所有的异步任务都执行成功后才会调用
Promise.race()
后边的.then
方法只要有一个异步任务执行成功就会调用
十二丶Promise.allSettled()
Promise.allSettled()方法由 ES2020引入。
Promise.allSettled()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束。
该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled
,不会变成rejected
。状态变成fulfilled
后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()
的 Promise 实例。
有时候,我们不关心异步操作的结果,只关心这些操作有没有结束,就可以使用Promise.allSettled()
十三丶Promise.any()
Promise.any()
方法ES2021 引入。
该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results); // [-1, Infinity]
});
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是Promise.any()
不会因为某个 Promise 变成rejected
状态而结束。
注意
用new创建一个Promise对象,并没有调用它,传进去的函数就已经执行了。所以我们使用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数。
如果new Promise()被包裹在一个function(){}函数内,必须用return,抛出到函数外部,才能让后续任务知道,当前函数是new Promise()对象。
Promise本身是异步的,但是它可以把你的代码转成同步的。可以把它看成让异步任务按序执行的一种机制。
如果多个异步函数顺序执行,则每个异步函数都应该包在new Promise()对象中,才能连续用多个.then()连接多个异步任务。
前后两个new Promise()之间如何传递数据: (放在门(resolve(数据))里)resolve(数据)只能接收一个参数值,就意味着只能传出一个值,如果想传多个值,只能将多个数据放在一个数组或对象中传出。
一般来说,不要在
then()
方法里面定义 rejected状态的回调函数(即then
的第二个参数),最好的选择是使用catch
方法。因为
catch
写法可以捕获前面then
方法执行中的错误,也更接近同步的写法(try/catch
)。Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
Promise.all(),如果作为参数的 Promise 实例,自己定义了
catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。
参考网站:
下一篇: 关于promise的实现原理的思考