一、为什么要用Promise?
1. JS异步
-
和同步相对应,同步:会按照代码书写顺序依次执行,后一个任务会等待前一个任务结束,然后再执行;
-
异步: 代码的执行顺序和书写顺序不一致
-
异步函数会被放到另一个队列等待结果;其后的函数不必等待异步函数的结果就可以执行,不会被阻塞
function fn1() { console.log('fn1') } function fn2() { console.log('fn2') } fn1(); setTimeout(function () { console.log("setTimeout"); }, 2000); fn2();
-
-
异步操作场景
-
原生事件处理函数
- onclick、onmouseover、onkeydown、onkeypress、.....
-
定时器
- setTimeout(),setInterval()
-
发送网络请求
-
请求API获取数据,数据需要一些时间才能得到,什么时间有返回结果不可预测
- 不希望发送请求之后,一直等待返回结果,阻塞后面其他的代码执行
-
-
2. 在Promise出现之前 JS异步操作是什么样子?
-
Callback回调函数
-
为了拿到异步执行的返回结果,再执行后续操作,要先定义并传入一个函数作为参数,等结果返回后,再回过头调用这个函数,所以叫回调函数。
function getHotPot(callback) { setTimeout(() => { callback('火锅') }, 2000) } function printFood(food) { console.log(food) } getHotPot(function(data) { printFood(data) })
-
-
有什么缺点?
-
写法复杂,阅读体验不够好
-
回调地狱
如果要保证异步操作按顺序执行,就需要层层嵌套 function getBubbleTea(callback) { setTimeout(() => { callback('奶茶') }, 1000) } function getHotPot(callback) { setTimeout(() => { callback('火锅') }, 2000) } function getFiredChicken(callback) { setTimeout(() => { callback('炸鸡') }, 3000) } function printFood(food) { console.log(food) } // in order getHotPot(function(data) { printFood(data) getBubbleTea(function(data){ printFood(data) getFiredChicken(function (data) { printFood(data) }) }) })
-
-
Promise优缺点
-
优点
// Promise function getBubbleTea() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('奶茶') }, 1000) }) } function getHotPot() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('火锅') }, 2000) }) } function getFiredChicken() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('炸鸡') }, 2000) }) } getHotPot() .then(res => { console.log(res) return getBubbleTea() }) .then(res => { console.log(res) return getFiredChicken() }) .then(res => console.log(res))- 代码更清楚,嵌套关系转变为链式步骤
- 可读性更好
-
缺点
- 无法中途取消,一旦新建就会立即执行
-
二、Promise是什么?
-
是一个对象,由关键字 new 及其构造函数来创建的,它承诺在未来的某个时间,一定会返回一个(成功/失败的)结果
-
3种状态
- pending 还没有落定(settled),不知道最终结果
- fulfilled 成功,得到结果
- rejected 失败,程序出错
// 1. pending 未落定之前 function p() { return new Promise((resolve, reject) => { }) } console.log(p()) // Promise {<pending>} // 2. fulfilled 期约得到兑现 function res() { return new Promise((resolve, reject) => { resolve('一个说话算话的期约') }) } console.log(res()) // Promise {<fulfilled>: '一个说话算话的期约'} // 3. rejected 期约失败,未得到兑现 function rej() { return new Promise((resolve, reject) => { reject('失约了') }) } console.log(rej()) // Promise {<rejected>: '失约了'} // Uncaught (in promise) -
不可逆
-
一旦 Promise 落定,它就永远保持在这个状态,不会再更改
-
原因:
- 使用promise代表希望最终得到一个确定的结果(immutable value),可以安全放心地把这个结果传递给第三方,利用这个结果去做后续的事情;
- 存在多方调用promise结果的情况,不可逆能够保证任何一方都不能改变这个落定的结果,大家都能拿到同一个确定的结果;
- promise的执行从pending落定为fulfilled或rejected需要时间,时间也是不可逆的;
-
-
三、Promise怎么用
1. 基础语法 new Promise
function p(flag) {
return **new Promise**((resolve, reject) => {
// 参数(接收一个函数作为参数,这个函数的参数是两个函数, resolve、 reject)
setTimeout(() => {
if (flag) {
resolve('fulfilled')
} else {
reject('rejected')
}
}, 2000)
})
}
2. 参数
- 接收一个函数作为参数,这个函数的参数是两个函数(resolve、 reject)
- resolve在promise成功时调用,将状态从pending兑现为fulfilled;reject在失败时调用,将状态从pending更改为rejected
3. 常用api(then, catch, finally)
-
.then() 接收2个函数作为参数,会在promise状态落定后被调用
- 第一个函数会在resolved时被调用,能够接收到promise resolved的结果;
- 第二个函数会在rejected时被调用,接收rejected的结果;
-
.catch() 用于捕获错误
- 当promise从pending变为rejected时会被调用,相当于.then只传第二个函数作为参数
-
.finally() 在promise结束时,无论结果是fulfilled或者是rejected,都一定会进入finally的回调
- 通常做一些清理的收尾工作
function p(flag) { return **new Promise**((resolve, reject) => { setTimeout(() => { if (flag) { resolve('fulfilled') } else { reject('rejected') } }, 2000) }) } p(true) .then(res => console.log(res, '成功!')) .catch(error => console.log(error, '出错啦!')) .finally(() => console.log('成功或者失败,最终都会走到这里!')) // p(true) // .then(res => console.log(res, '成功!'), // error => console.log(error, '出错啦!') // )项目代码常见用法是在action里,请求数据前开启loading,在finally里取消loading const actions = { getWarningDetail ({ commit, dispatch }, warningId) { dispatch('ui/AddLoadingCount', null, { root: true }) return warningAPIs.getWarningDetail(warningId) .then(res => { commit(mutationTypes.GET_WARNING_DETAIL, res) }) .catch(error => { dispatch( 'ui/showToast', { text: get(error, 'response.data.errorMessage', '系统错误'), type: '' }, { root: true }, ) }) .finally(() => { dispatch('ui/SubLoadingCount', null, { root: true }) }) }, }
4. 链式调用
-
then()的返回结果会被包装成promise对象,具备then方法(thenable),因此可以继续.then(), 连续调用.then()就是链式调用;
-
前一个then的返回值作为参数传入后一个then
-
错误处理
-
链式调用时,把catch放在最后, 一旦某一个then接收到拒绝的结果,则promise 方法会跳过它后面的 then 直接进入catch
function fetchApi() { return new Promise((resolve, reject) => { // resolve('P1') reject('fetchApi error') }) } function getResult(res) { return new Promise((resolve, reject) => { resolve('P2') // reject('getResult error') }) } fetchApi() .then(res => res) .then(res => getResult(res)) .then(lastRes => console.log(lastRes, 'lastRes')) .catch(error => console.log(error))
// 如果错误已经被捕获,就不会影响后续的流程 function fetchApi() { return new Promise((resolve, reject) => { reject('fetchApi error') }) } function getResult(res) { return new Promise((resolve, reject) => { resolve('P2') }) } fetchApi() .then(res => res) .catch(e => console.log(e, 'caught')) // 处理第一个Promise的错误 .then(res => getResult(res)) .then(lastRes => console.log(lastRes, 'lastRes')) .catch(error => console.log(error, 'last'))function fetchApi() { return new Promise((resolve, reject) => { reject('fetchApi error') }) } function getResult(res) { return new Promise((resolve, reject) => { resolve('P2') }) } // fetchApi失败,不能进getResult,就需要在catch里return Promise.reject fetchApi() .then(res => res) .catch(e => { console.log(e, 'caught') return Promise.reject(e)// 立刻返回被拒绝的promise }) .then(res => getResult(res)) .then(lastRes => console.log(lastRes, 'lastRes')) .catch(error => console.log(error, 'last')) -
5. Promise.all
-
参数:一般是由多个Promise对象组成的一个数组
-
返回:Promise.all返回的是所有promise对象完成状态的结果,会是一个数组,并且返回数据的顺序和传入参数数组的顺序对应
-
Promise.all里的多个Promise对象会同时发出请求,等所有promise resolved,才会得到结果
-
Promise.all只要有一个对象rejected,就会进入catch
function p1() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('p1') // reject('p1-error') }, 3000) }) } function p2() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('p2') }, 1000) }) } function allP() { return Promise.all([p1(), p2()]) } allP() .then(res => console.log(res, 'all-res')) .catch(error => console.log(error, 'all-error'))
6. Promise.race
和Promise.all的区别在于只要有一个对象落定,就直接返回第一个落定的结果
function raceP() {
return Promise.race([p1(), p2()])
}
raceP()
.then(res => console.log(res, 'race-res'))
.catch(error => console.log(error, 'race-error'))