Promise是什么?
理解
1. 抽象表达
Promise
是一门新的技术(ES6规范)-
Promise
是JS中进行异步编程的新解决方案备注:旧的方案是单纯使用回调函数
2. 具体表达
- 从语法上来说:
Promise
是一个构造函数 - 从功能上来说:
Promise
对象用来封装一个异步操作并可以获取其成功或失败的结果值。
Promise对象的状态改变
Promise
的状态指的是实例对象中的一个属性PromiseState
,它的值有三个:
-
pending
:未决定的 -
resolve/fulfilled
:成功 -
rejected
:失败
Promise
的状态改变只有下面的三种情况:
- 由
pending
变为resolved
- 由
pending
变为rejected
- 抛出异常:如果当前为
pending
就会变为rejected
说明:只有这3种,且一个Promise
对象只能改变一次,无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般称为value
,失败的结果数据一般称为reason
。
Promise对象结果值属性
Promise实例对象中还有一个属性PromiseResult
,保存着对象成功/失败
的结果。要想改变它的值,只有通过给resolve()
或者reject()
方法传参,成功时,将对应数据传入resolve()
方法,失败时,将对应的数据传入reject()
方法,之后可以在then(value => {}, reason => {})
方法中取出,value
对应着resolve()
接收的参数,reason
对应着reject()
接收的参数。
Promise的基本使用
需求:点击按钮,1秒后显示是否中奖(30%概率中奖)。若中奖,弹出:恭喜恭喜,奖品为10万RMB玛莎拉蒂优惠券;若未中奖弹出:再接再厉。
html页面,放个标题和按钮即可:
<h2>Promise 初体验</h2>
<button>点击抽奖</button>
不使用Promise
的js代码:
<script>
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}
/** 需求:
* 点击按钮, 2s 后显示是否中奖(30%概率中奖)
* 若中奖,弹出:恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券
* 若未中奖弹出: 再接再厉
*/
// 旧的方法
// 获取元素对象
const btn = document.querySelector('button');
// 绑定单击事件
btn.addEventListener('click', function () {
// 定时器
setTimeout(() => {
// 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了(简易的算法)
// 获取从 1 - 100 的一个随机数
let n = rand(1, 100);
// 判断
if (n <= 30) {
alert('恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券');
} else {
alert('再接再厉');
}
}, 1000);
})
</script>
使用Promise
,js代码:
<script>
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}
// 获取元素对象
const btn = document.querySelector('button');
// 绑定单击事件
btn.addEventListener('click', function () {
// Promise 形式实现
// resovle 解决 是函数类型的数据 异步任务成功时调用
// reject 拒绝 是函数类型的数据 异步任务失败时调用
let p = new Promise((resolve, reject) => {
setTimeout(() => {
// 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了
// 获取从 1 - 100 的一个随机数
let n = rand(1, 100);
// 判断
if (n <= 30) {
resolve(); // 将 promise 对象的状态设置为 成功
} else {
reject(); // 将 promise 对象的状态设置为 失败
}
}, 1000);
});
// 调用 then 方法
// then方法的第一个回调函数,promise对象状态为成功时调用,第二个回调函数,promise对象状态为失败时调用
p.then(() => {
alert('恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券');
}, () => {
alert('再接再厉');
})
})
</script>
现在需求变更,在弹出提示时,同时告知本次抽到的数字。
不使用Promise
的js代码:
<script>
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}
// 旧的方法
// 获取元素对象
const btn = document.querySelector('button');
// 绑定单击事件
btn.addEventListener('click', function () {
// 定时器
setTimeout(() => {
// 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了
// 获取从 1 - 100 的一个随机数
let n = rand(1, 100);
// 判断
if (n <= 30) {
alert('恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券,您的中奖数字为:' + n);
} else {
alert('再接再厉,您抽中的数字为:' + n);
}
}, 1000);
})
</script>
使用Promise
的js代码:
<script>
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}
// 获取元素对象
const btn = document.querySelector('button');
// 绑定单击事件
btn.addEventListener('click', function () {
// 如果现在需要在弹出提示时,同时告知本次的号码,该怎么办?
// 只需要将成功或失败的值分别传入 resolve() 和 reject() 方法,然后再then()方法的两个回调函数中接收即可。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
// 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了
// 获取从 1 - 100 的一个随机数
let n = rand(1, 100);
// 判断
if (n <= 30) {
resolve(n); // 将 promise 对象的状态设置为 成功
} else {
reject(n); // 将 promise 对象的状态设置为 失败
}
}, 1000);
});
// 调用 then 方法
// then方法的第一个回调函数,promise对象状态为成功时调用,第二个回调函数,promise对象状态为失败时调用
// value 值
// reason 理由
p.then((value) => {
alert('恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券,您的中奖数字为:' + value);
}, (reason) => {
alert('再接再厉, 您的号码为:' + reason);
})
})
</script>
Promise的基本流程
为什么要使用Promise?
1. 指定回调函数的方式更加灵活
- 旧的:必须在启动异步任务前指定
- promise:启动异步任务=> 返回promise对象=>给promise对象绑定回调函数(甚至可以在异步任务结束后指定一个或多个)
2. 支持链式调用,可以解决回调地狱问题
2.1 什么是回调地狱?
回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件
例如,下面的代码是Node.js
的fs
模块读取三个文件,并将读取到的结果进行拼接并且输出到控制台中。
fs.readFile('./resource/1.txt', (err, data1) => {
if (err) throw err;
fs.readFile('./resource/content.txt', (err, data2) => {
if (err) throw err;
fs.readFile('./resource/2.txt', (err, data3) => {
if (err) throw err;
console.log(data1 + data2 + data3);
})
})
})
这个代码片段,fs.readFile()
在嵌套调用,这里就形成了回调地狱。
2.2 回调地狱的缺点
- 不便于阅读
- 不便于异常处理
2.3 解决方案?
使用promise
链式调用,
终极解决方案:async/await
如何使用Promise?
API
Promise构造函数
Promise
的构造函数:Promise(executor){}
-
executor
函数:执行器(resolve, reject) => {}
-
resolve
函数:内部定义成功时我们调用的函数 -
reject
函数:内部定义失败时我们调用的函数
说明:executor
会在Promise
内部立即同步调用,异步操作在执行器中执行。
Promise.prototype.then方法
Promise.prototype.then
方法:(onResolved, onRejected) => {}
-
onResolved
函数:成功的回调函数,通常这么写:value => {}
-
onRejected
函数:失败的回调函数,通常这么写reason => {}
说明:指定用于得到成功value
的成功回调和用于得到reason
的失败回调,返回一个新的Promise
对象。
Promise.prototype.catch方法
Promise.prototype.catch
方法:(onRejected) => {}
onRejected
函数:失败的回调函数,通常这么写reason => {}
catch()
只能指定失败的回调,不能指定成功的回调。其实也是使用then()
实现的.
说明:catch()
是 then()
的语法糖, 相当于: then(undefined, onRejected)
。
Promise.resolve方法
Promise.resolve
方法:(value) => {}
value
:成功的数据或Promise
对象
说明:返回一个成功/失败的Promise
对象
catch()
方法和resolve()
方法的使用:
<script>
// 如果传入的参数为 非Promise类型的对象,返回的结果为 成功的Promise对象
// 如果传入的参数为 Promise对象,则参数的结果决定了resolve的结果
let p1 = Promise.resolve(555);
console.log(p1); // Promise {<fulfilled>: 555}
let p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p2); // Promise {<fulfilled>: "OK"}
// 传入失败的Promise
let p3 = Promise.resolve(new Promise((resolve, reject) => {
reject('Error');
}));
console.log(p3); // Promise {<rejected>: "Error"}
// 失败的同时还报错了,原因是没有对错误进行处理,这里使用catch()进行处理就能解决报错
p3.catch(reason => {
console.log(reason); // Error
})
</script>
Promise.reject方法
Promise.reject
方法:(reason) => {}
reason
:失败的原因
说明:返回一个失败的Promise
对象
reject()
方法的示例:
<script>
// 总结,Promise.reject()方法,无论你传入什么得到的都是失败的Promise对象,并且失败的结果就是你传入的参数。
let p = Promise.reject(555);
console.log(p);
let p2 = Promise.reject('hello');
console.log(p2);
let p3 = Promise.reject(new Promise((resolve, reject) => {
resolve('OK');
}))
console.log(p3);
</script>
Promise.all方法
Promise.all
方法:(promises) => {}
promises
参数:包含n
个Promise
对象的数组
说明:返回一个新的Promise
对象,只有所有的Promise
都成功才成功,只要有一个失败了就直接失败,失败的结果就是失败的Promise
对象的结果。
all()
方法的示例:
<script>
let p1 = new Promise((resolve, reject) => {
resolve('OK');
});
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
let result1 = Promise.all([p1, p2, p3]);
console.log(result1); // 返回一个成功的Promise对象,对象的结果是一个数组,数组中存有三个成功的promise对象的结果
// 如果数组中 p2 是一个失败的Promise,那么all()方法返回的是一个失败的Promise对象,并且失败的结果是数组中失败的Promise对象的结果。
p2 = Promise.reject('Error');
let result2 = Promise.all([p1, p2, p3]);
console.log(result2);
</script>
Promise.allSettled方法
Promise.allSettled
方法:(promises) => {}
promises
参数:包含n
个Promise
对象的数组
说明:该方法返回的结果总是状态为成功的Promise
对象,这个Promise
对象的结果值是包含promises
参数中每一个promise
对象的状态以及结果值的对象数组。
注意和all()
方法的区别。
allSettled()
方法示例:
<script>
// 声明两个Promise对象
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('商品数据 - 1');
// reject('ERROR')
}, 1000);
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('商品数据 - 2');
reject('出错了');
}, 1000);
})
// 调用allSettled() 方法 该方法返回的结果总是成功的Promise对象,
// 它的结果值是包含每一个promise对象的状态以及结果值的对象。
let result = Promise.allSettled([p1, p2]);
console.log(result);
let res = Promise.all([p1, p2]); // 全部成功,才会返回成功的Promise对象
console.log(res);
</script>
Promise.race方法
Promise.race
方法:(promises) => {}
promises
参数:包含n
个Promise
对象的数组
说明:返回一个新的Promise
对象,第一个完成的Promise
对象的结果状态就是最终的结果状态
race()
方法的示例:
<script>
// 同步代码
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
// 调用
let result1 = Promise.race([p1, p2, p3]);
// 代码从上往下执行,所以p1最先执行,所以result的状态和结果应该是p1的状态和结果。
console.log(result1);
// 异步代码
let p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 2000);
})
let p5 = Promise.reject('Error');
let p6 = Promise.resolve('Oh Yeah');
let result2 = Promise.race([p4, p5, p6]);
// p4为异步代码,p5 会先于p4、p6执行,所以p5的状态和结果即为result2的状态和结果。
console.log(result2);
</script>
Promise的几个关键问题
如何改变Promise的状态?
改变Promisede
状态有三种方法:
-
resolve(value)
:如果当前是pending
就会改变为resolved(fulfilled)
-
reject(reason)
:如果当前是pending
就会变为rejected
- 抛出异常:如果当前是
pending
就会变为rejected
示例:
<script>
let p = new Promise((resolve, reject) => {
// 1. resolve函数
// resolve('OK'); // pending => fulfilled(resolved)
// 2. reject 函数
// reject('Error'); // pending => rejected
// 3. 抛出错误
// throw '出问题了'; // pending => rejected
})
console.log(p);
</script>
一个Promise指定多个成功/失败的回调函数,都会调用吗?
指定回调用的就是then()
,所以这个题目的意思是如果使用then()
方法为一个promise
对象指定多个回调,那么这些回调是否都会执行?
答案是:当Promise
改变为对应状态时都会调用。
示例:
<script>
let p = new Promise((resolve, reject) => {
resolve('OK');
})
// 指定回调 - 1
p.then(value => {
console.log(value);
})
// 指定回调 - 2
p.then(value => {
alert(value);
})
// 有弹框,控制台有输出,说明这两个回调都执行了。
</script>
改变Promise状态和指定回调函数谁先执行谁后执行?
- 都有可能,正常情况下是先指定回调再改变状态,但也可以先改变状态再指定回调
- 如何先改状态再指定回调?
- 在执行器中直接调用
resolve()/reject()
-
then()
方法延迟更长时间才被调用
- 在执行器中直接调用
- 什么时候才能得到数据?
- 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
- 如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据
示例:
<script>
let p = new Promise((resolve, reject) => {
// setTimeout(() => { // 异步任务
resolve('OK'); // 同步任务
// }, 1000)
})
p.then(value => {
console.log(value);
}, reason => {
})
// 1. 当Promise构造函数中的executor执行器函数内部是同步任务时,先改变状态,在执行then()方法指定回调(注意是指定回调,不是执行回调)
// 2. 当Promise构造函数中的executor执行器函数内部是异步任务时,那么是先执行then()方法指定回调函数,再改变状态。
</script>
promise.then()返回新promise的结果状态由什么决定?
- 简单表达:由
then()
指定的回调函数执行的结果决定 - 详细表达:
- 如果抛出异常, 新
promise
变为rejected
,reason
为抛出的异常 - 如果返回的是非
promise
的任意值,新promise
变为resolved
,value
为返回的值 - 如果返回的是另一个新
promise
,此promise
的结果就会成为新promise
的结果
- 如果抛出异常, 新
示例:
<script>
let p = new Promise((resolve, reject) => {
resolve('OK');
// reject('error')
})
// 执行 then 方法
let result = p.then(value => { // p 为成功的回调
// console.log(value); // result的状态为fulfilled,结果为undefined,因为没有返回值
// 1. 抛出错误
// throw '出了问题'; // result 的状态为rejected,结果为'出了问题'
// 2. 返回结果为非 Promise 类型的对象
// return 555; // result的状态为fulfilled,结果为555
// 3. 返回结果是Promise对象
return new Promise((resolve, reject) => {
// resolve('success'); // result的状态为fulfilled,结果为success
// reject('error'); // result 的状态为rejected,结果为error
throw '出了问题'; // result 的状态为rejected,结果为'出了问题
})
}, reason => { // p 为失败的回调
// console.log(reason); // result的状态为fulfilled,结果为undefined,因为没有返回值。 这里本身会输出error
// 1. 抛出错误
// throw '出了问题'; // result 的状态为rejected,结果为'出了问题'
// 2. 返回结果为非 Promise 类型的对象
// return 555; // result的状态为fulfilled,结果为555
// 3. 返回结果是Promise对象
return new Promise((resolve, reject) => {
// resolve('success'); // result的状态为fulfilled,结果为success
// reject('error'); // result 的状态为rejected,结果为error
// throw '出了问题'; // result 的状态为rejected,结果为'出了问题'
})
})
console.log(result);
</script>
promise如何串连多个操作任务?
-
promise
的then()
返回一个新的promise
,可以继续调用then()
方法,形成链式调用 - 通过
then
的链式调用串连多个同步或异步任务
示例:
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
p.then(value => {
return new Promise((resolve, reject) => {
resolve('success');
})
}).then(value => {
console.log(value) // success
}).then(value => {
console.log(value) // undefined
})
</script>
promise异常穿透?
- 当使用
promise
的then
链式调用时,可以在最后指定失败的回调 - 前面任何操作除了异常,都会传到最后失败的回调中处理
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
// reject('Err')
}, 1000);
})
p.then(value => {
// console.log(111);
throw '出问题了';
}).then(value => { // 也可以在这里处理上面出现的错误
console.log(222)
}).then(value => {
console.log(333)
}).catch(reason => { // 这里使用catch,也可以使用then
console.warn(reason);
})
// 在调用链的最后指定失败的回调,就可以处理任务当中出现的错误,这个现象称为异常穿透。
</script>
如何中断promise链?
当使用promise
的then
链式调用时,想要在中间中断,不再调用后面的回调函数,有且只有一个办法:在回调函数中返回一个pending
状态的promise
对象。
<script>
// 如果想要中断这个then调用链,有且只有一个方式,在需要中断的回调函数中,返回一个pending状态的promise对象
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
p.then(value => {
console.log(111);
// 假如后面不想让它调用了,有且仅有一个方式:返回一个pending状态的promise对象
return new Promise(() => {});
}).then(value => {
console.log(222)
}).then(value => {
console.log(333)
}).catch(reason => {
console.warn(reason);
})
</script>