我正在参加「掘金·启航计划」
前言
Promise 系列更新第二篇文章啦!
在上一篇文章:开篇 – 你对 Promise 的基本特性了解多少 ,我们大致了解 Promise ,学习了基本特性和构造函数的相关知识
这一节,我们学习 Promise 中的 3 个对象方法 和 6 个类方法
Promise 中的方法是我们要掌握的重点,熟练的使用这些方法才能真正地在实际开发中对异步请求无所畏惧。
Promise 对象方法
我们先来看看 Promise 都有哪些对象方法,打印 Promise.prototype
console.log(Object.getOwnPropertyDescriptors(Promise.prototype));
constructor 是 Promise 的构造器、Symbol 又是一个值为 ‘Promise’ 字符串
所以这里我们只说明 Promise then()
、catch()
、finally()
的三个对象方法
1. promise.then()
promise 的 then
方法返回一个 Promise 对象,并接收两个回调函数
即使返回一个普通的值, 这个值也会被作为一个新的 Promise 的 resolve 值,所以 pormise 的返回值还是 promise
也正是因为返回值仍是 promise,所以可以进行链式调用
a) 普通的值
new Promise((resolve, reject) => {
resolve(123)
}).then(res => {
return res // 相当于 return new Promise(resolve => { resolve(123) })
}).then(res => {
console.log(res); // 123
// 上面 return 123,所以后面可链式调用 then 获取 123
// 若未 return 值,则 res 为 undefined
})
b) promise
开篇我们提到,一个请求需要的参数可能会 依赖于另一个请求的结果,所以有时我们需要 两次请求 才能获取到数据,这时 then
之后可以仍是一个 promise
new Promise((resolve, reject) => {
resolve(111)
}).then(res => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (res === 111) {
resolve(222)
}
}, 2000)
})
}).then(res => {
console.log(res); // 222
})
c) thenable
如果返回的对象实现了 thenable,那么 promise 会采取它的最终状态
const obj = {
then: (resolve, reject) => {
resolve(10086);
}
}
new Promise((resolve, reject) => {
resolve(obj);
}).then(res => {
console.log(res);
})
2. promise.catch()
.catch()
捕获;见名知义,只有面对坏人我们才称为 捕获。
promise.catch()
是 ES6 标准中的语法糖,是 promise.then(undefined, onRejected)
的简写形式, 在 promise 拒绝时,捕获其 拒绝 的原因,并在函数中做错误处理。
new Promise((resolve, reject) => {
reject(500)
}).then(undefined, err => {
console.log(err); // 500
return 111
}).then(res => {
console.log(res); // 111
})
new Promise((resolve, reject) => {
reject(500)
}).catch(err => { // <- catch
console.log(err); // 500
return 111
}).then(res => {
console.log(res); // 111
})
new Promise((resolve, reject) => {
// 在 executor 中抛出异常时,也会执行 reject 回调函数
throw new Error(500)
}).catch(err => {
console.log(err); // Error: 500...
})
3. promise.finally()
promise 完成时,无论状态是 fulfilled 还是 rejected,都会执行的回调函数。
.finally( ) 用得不多,通常用来取消一些 loading 组件,释放资源
new Promise((resolve, reject) => {
resolve(200)
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
}).finally(() => { // finally(),回调函数没有值的传递
hideLoading()
})
Promise 类方法
1. Promise.resolve()
const p1 = Promise.resolve(111);
const p2 = new Promise((resolve, reject) => {
resolve(111)
});
// p1 相当于 p2
2. Promise.reject()
const p1 = Promise.reject(222);
const p2 = new Promise((resolve, reject) => {
reject(222)
});
// p1 相当于 p2
3. Promise.all()
接收一组 promise ,当所有 promise 的状态都为 fulfilled 时,返回结果;
如果其中一个 promise 的状态变成 rejected ,则执行 onRejected
这个我理解的就是往碗里打一盒鸡蛋,只要有一个坏蛋,之前打了不管多少都不要了,就抛出异常,指出这个坏蛋
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行成功1')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('执行失败2')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('执行失败3')
}, 3000)
})
const p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('执行成功4')
}, 4000)
})
Promise.all([p1, p4]).then(res => {
console.log(res); // [ '执行成功1', '执行成功4' ]
}).catch(err => {
console.log(err);
})
Promise.all([p1, p3, p2, p4]).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // 执行失败2
})
4. Promise.allSettled()
接收一组 promise ,等待返回所有 promise 执行结果,无论失败或成功
失败的结果也将在 then 中返回,而不是在 catch
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('执行失败1')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('执行失败2')
}, 2000)
})
Promise.allSettled([p1, p2]).then(res => {
console.log(res); // 2s后返回
// [ {status: 'rejected', reason: '执行失败1' }, {status: 'rejected', reason: '执行失败2'} ]
})
5. Promise.race()
在这里肯定就是 竞争
的意思,就和高考一样,不管你是不是成绩好的学生,只要你是第一个走出考场的,记者肯定会去采访你。
接收一组 promise ,只要有一个 promise 执行完成,无论拒绝或敲定,就会返回 promise 的结果
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("执行失败1");
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("执行成功2");
}, 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("执行成功3");
}, 3000);
});
Promise.any([p2, p3, p1]).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // 执行失败1
})
Promise.any([p3, p2]).then(res => {
console.log(res); // 执行成功2
}).catch(err => {})
6. Promise.any()
any 这个词听起来就很霸道,我不仅要最快的,而且我还要最好的
接收一组 promise ,只要有一个 promise 最先执行完成 且 状态为 fulfilled 执行成功,promise 结果返回
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("执行失败1");
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("执行失败2");
}, 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("执行成功3");
}, 3000);
});
const p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("执行成功4");
}, 4000);
});
Promise.any([p1, p2, p3, p4]).then(res => {
console.log(res); // 执行成功3
}).catch(err => {})
Promise.any([p1, p2]).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // AggregateError: All promise were rejected
console.log(err.errors); // ['执行失败1', '执行失败2']
})
当所有 promise 的返回 都为 reject 时,执行 onRejected
,返回一个由 AggregateError 实例 提供的错误信息 AggregateError: All promise were rejected
我们可以利用 AggregateError 的 .errors()
对象方法得到具体的错误内容
7. 小总结
Promise.all() 和 Promise.allSettled():在 promise 执行成功时,两者都会返回 数组 结果;执行失败时,Promise.all()
只返回最先失败的结果 ,Promise.allSettled()
仍会返回所有结果,但执行时间更长,因为要等待所有 Promise 状态变更为 settled 时,才会返回数据。
Promise.race() 和 Promise.any():.race()
和 .any()
的共同点就是只要一个,只会返回 最先完成的一个结果,.any()
比较苛刻,只要最快成功的那个
? 通常在实际项目中不会出现,但是比较有意思的点是:
使用 .race()
和 .any()
时,如果两个异步请求 p1 和 p2 返回时间一样 那么会拿到谁的值呢?
想一想…
你可能会猜,先来后到呗,谁先放在数组中就会拿到谁的值 比如 Promise.any([p2, p1])
,则返回 p2 的值
但实际上不是这样的,答案是谁 先在代码中定义,就先返回谁的值,简写代码 ?:
const p2 = new Promise((resolve, reject) => {....}); // 1s 后执行 resolve('我是 p2')
const p1 = new Promise((resolve, reject) => {....}); // 同样是 1s 后执行 resolve('我是 p1')
Promise.race([p1, p2]).then(res => { console.log(res) }); // 我是 p2
结语
结合上篇,我们基本上就学完了 Promise 的所有内容,在实际开发中合理地运用好这些方法就能轻松解决异步代码带来的问题,也希望这篇文章能帮助到大家加强 Pormise 的理解。
后续还准备整理一些 手写 Promise 和 Promise 面试练习题相关的内容,如果有感兴趣的小伙伴可以先插个眼。