Promise 是什么?
Promise是ES6语法,是JS中解决异步编程的新解决方案。(旧的解决方案是单纯的调用回调函数)
从语法上来说,Promise是一个构造函数
从功能上来说,Promise对象用来封装一个异步操作,并可以获取其成功或者失败的结果值
异步编程包括但不限于以下几个:
- fs 文件操作
- 数据库操作
- Ajax
- 定时器
为什么要用Promise?
Promise支持链式调用,解决回调地狱问题
在 Promise 出现以前,我们处理一个异步网络请求,大概是这样:
// 请求 代表 一个异步网络调用。
// 请求结果 代表网络请求的响应。
请求1(function(请求结果1){
处理请求结果1
})
看起来还不错。
但是,需求变化了,我们需要根据第一个网络请求的结果,再去执行第二个网络请求,代码大概如下:
请求1(function(请求结果1){
请求2(function(请求结果2){
处理请求结果2
})
})
看起来也不复杂。
但是。。需求是永无止境的,于是乎出现了如下的代码:
请求1(function(请求结果1){
请求2(function(请求结果2){
请求3(function(请求结果3){
请求4(function(请求结果4){
请求5(function(请求结果5){
请求6(function(请求结果3){
...
})
})
})
})
})
})
这就出现了所谓的回调地狱(回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件)
回调地狱带来的负面作用有以下几点:
- 代码臃肿。
- 可读性差。
- 耦合度过高,可维护性差。
- 代码复用性差。
- 容易滋生 bug。
- 只能在回调里处理异常。
解决方案: Promise链式调用
Promise初体验
// resolve 和 reject 都是函数类型的数据
const p = new Promise ((resolve, reject) => {
let status = true;
setTimeout(() => {
if(status) {
resolve('正确信息')
} else {
reject('错误信息')
}
},2000)
})
p.then((data) => {
console.log(data);
})
Promise状态改变
Promise 对象有三个状态,并且状态一旦改变,便不能再被更改为其他状态。
这个状态实际上是Promise实例对象中的一个属性[PromiseState]
实例对象中的另一个重要的属性 PromiseResult
,存的是对象成功或者失败的结果
-
pending
,异步任务正在进行。(未决定的) -
resolved
(也可以叫fulfilled),异步任务执行成功。 -
rejected
,异步任务执行失败。
Promise状态改变只有 pending
变为 resolved
和 pending
变为 rejected
这两种
且一个 Promise 对象只能改变一次,无论成功或者失败都会有一个结果数据,成功的结果数据一般称为 value,失败的结果数据一般称为 reason。
Promise的API
1. Promise构造函数
- executor函数:执行器
- resolve函数:内部定义成功时我们执行的函数
- reject函数:内部定义失败时我们执行的函数
说明:executor
会再 Promise 内部立即同步调用,异步操作在执行器中执行。
举例说明:
const p = new Promise((resolve, reject) => {
console.log('222')
})
console.log('111')
// 打印结果为: 111 222 , 从而证明了上面的结论
2. Promise.prototype.then()
语法:
p.then(onResolved[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
指定用于得到成功 value
的成功回调和用于得到失败 reason
的失败回调,返回一个新的 promise
对象。
3. Promise.prototype.catch()
catch() 方法返回一个 Promise
,并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected)
相同。他只能用来指定失败的回调
同样支持链式调用,本质上其实是then()方法的一个单独封装
语法:
p.catch(onRejected);
p.catch(function(reason) {
// 拒绝
});
3. Promise.resolve() 方法
这个方法比较特殊,它是属于 Promise 这个函数对象的,并不属于实例对象,也就是说它是静态成员。
用法:接收一个参数,返回一个promise成功或者失败对象
语法:Promise.resolve(value);
// 如果传入的参数为非 promise 类型的对象,则返回的结果为成功的promise对象
let p1 = Promise.resolve(123)
console.log(p1)
// 如果传入的参数为 promise 对象,则参数的结果决定了 resolve 的结果,比如:
let p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('ok')
}))
console.log(p2) // 因为参数是promise对象,且返回的结果是成功的,值为ok,那么此时p2的结果也是成功的,值同样为ok
4. Promise.reject() 方法
这个方法和上面的 Promise.resolve
方法一样,是属于 Promise 这个函数对象的,不属于实例对象,同样为静态成员。
用法:快速返回一个带有拒绝原因的promise对象
语法:Promise.reject(reason)
let p1 = Promise.reject(123)
let p2 = Promise.reject(new Promise((resolve, reject) => {
resolve('成功')
}))
console.log(p1)
console.log(p2)
// 无论你传入参数是不是promise对象,都会返回一个被拒绝的promise对象,对调试和选择性错误捕捉很有帮助
Promise.all()
这个方法也是属于 Promise 函数对象的,不属于实例对象。
Promise.all()
方法接收一个promise的 iterable
类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。
语法:Promise.all(iterable)
iterable: 一般为包含 n 个 promise 的数组
总结:返回一个新的 promise ,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败
let p1 = new Promise((resolve, reject) => {
resolve('p1')
})
let p2 = new Promise((resolve, reject) => {
resolve('p2')
})
let p3 = new Promise((resolve, reject) => {
resolve('p3')
})
const result = Promise.all([p1, p2, p3])
console.log(result) // 返回值为三个promise对象成功的结果组成的数组
// 如果有一个失败,返回值为失败,失败的结构值就为第一个失败的那个结果值
Promise.race()
这个方法也是属于 Promise 函数对象的,不属于实例对象。
语法:Promise.race(iterable);
iterable: 包含 n 个 promise 的数组
总结:返回一个新的 promise,第一个完成的promise的结果状态就是最终的结果状态
就相当于在传入的promise对象在赛跑,谁先改变状态,谁就决定race方法的返回结果,无论是成功或者失败。
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
const result = Promise.race([p1, p2]).then((value) => {
console.log(value);
// 都成功,但是显然p2更快,所以result的结果为two
});
Promise中的几个关键问题
1. 如何改变promise对象的状态
let p = new Promise((resolve, reject) => {
// 1.resolve 函数
resolve('ok') // pending => resolved
// 2. reject 函数
reject('error') // pending => rejected
// 3. 抛出错误
throw '出问题了'
// 这三种方法都可以改变promise 的状态
})
2. 能否执行多个回调
promise指定多个成功或失败的回调函数,都会调用吗?
当promise改变为对应状态时都会调用。
let p = new Promise((resolve, reject) => {
resolve('ok')
})
// 指定回调 - 1
p.then(value => {
console.log(value)
})
// 指定回调 - 2
p.then(value => {
alert(value)
})
// 很显然只要时promise的状态改变了,上面的两个回调函数都会执行
3. 改变状态与执行回调顺序问题
改变promise状态和执行回调函数谁先谁后?
const p = new Promise((resolve, reject) => {
// 1. 当执行器中是一个同步任务的时候,那么会先改变状态再执行下面的then回调函数
resolve('ok')
// 2. 当执行器中时一个异步任务的时候,那么会先执行下面的then回调函数再改变状态
setTimeout(() => {
resolve('ok')
}, 1000)
})
p.then((value) => {
console.log(value)
}, (reason) => {
console.log(reason)
})
- 对于执行器中时一个异步任务的时候,我们先执行调用回调函数,但是要等到异步任务中resolve的状态改变之后,才会去执行回调函数里面的代码,然后对成功或失败的结果做处理
4. 中断promise链的方法
const p = new Promise((resolve, reject) => {
resolve(123)
})
p.then(value => {
console.log(111)
// 中断promise链的唯一方法是返回一个pending状态的promise对象
// 因为如果返回的是pending,那么then方法返回的也是一个pending状态的promise对象
// 那么后续的then方法都不能执行,因为状态没有改变
return new Promise(() => {})
// 这时候就只会打印111
}).then(value => {
console.log(222)
}).then(value => {
console.log(333)
})