说一说对Promise的理解
# Promise因何而出现
关键词
回调地狱
在没有promise等解决异步的方案之前,处理异步流程一般都是采用 回调函数
。如果回调中再次进行异步操作,那么一旦嵌套很深,就会出现回调地狱的情况,这对于日后的代码维护很不利。
例如:
请求1(function(请求结果1){
请求2(function(请求结果2){
请求3(function(请求结果3){
请求4(function(请求结果4){
请求5(function(请求结果5){
请求6(function(请求结果3){
...
})
})
})
})
})
})
2
3
4
5
6
7
8
9
10
11
12
13
# 回调函数处理异步的劣势
代码臃肿。
可读性差。
耦合度过高,可维护性差。
代码复用性差。
容易滋生 bug。
只能在回调里处理异常。
# Promise是什么
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
简单的说,从语义上来看,Promise意为承诺、答应,对应到编程中,可以理解为:将一个异步操作交给Promise,然后Promise就会承诺返回给你两个状态,‘完成‘ 或者是 ’失败(异常)‘。
# Promise的三种状态
等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)
Promise 必须为三种状态之一,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态。
Pending 变为 Fulfilled 会得到一个私有 value,Pending 变为 Rejected会得到一个私有 reason,当Promise达到了Fulfilled或Rejected时,执行的异步代码会接收到这个value或reason
# 怎么使用Promise
Promise状态只能在内部进行操作,内部操作在Promise执行器函数执行。Promise必须接受一个函数作为参数,我们称该函数为执行器函数,执行器函数又包含resolve和reject两个参数,它们是两个函数。
resolve : 将Promise对象的状态从 Pending(进行中) 变为 Fulfilled(已成功)
reject : 将Promise对象的状态从 Pending(进行中) 变为 Rejected(已失败),并抛出错误。
# 代码示例
var p = new Promise((resolve, reject) => {
// 模拟一个异步操作并且不确定结果是否正常
console.log('Promise 执行了')
setTimeout(() => {
var r = Math.random()*10
if(r > 5) {
resolve('成功')
} else {
reject('异常')
}
}, 1000)
})
p.then(res => {
console.log(res)
}).catch(err => {
console.error(err)
}).finally(() => {
console.log('无论结果如何,我都会执行')
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
执行两次的结果:
可以看到,通过Promise来执行异步操作,我们可以使用链式的语法,分别在then方法和catch方法中获取到执行成功态或者是执行失败态返回给我们的异步操作结果。即使需要再次进行异步操作也只需要继续使用链式语法,代码的可读性和维护性与回调函数相比有了极大的提高。
并且我们可以在外层通过catch方法显式的捕获异常,对于异常处理更加的清晰。
通过上面的示例还可以看出:Promise一旦创建就会执行
# Promise的几个方法
# 1. Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
简单的来说,就是Promise.all()所有的参数最终都是成功态,Promise.all()返回的实例才会是成功态,并且将所有参数的结果放在一个数组中返回,只要其中有一个参数是失败态,那么Promise.all()返回的实例就会是失败态,并且结果就是那个第一次变为失败态的参数的返回结果。
# 2. Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
race,比赛的意思,这个方法的用法是,参数中率先由执行态变为成功态或者失败态的,决定了Promise.race()方法最终返回的示例的状态。
# 3. Promise.allSettled() [ES2020]
有时候,我们希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。但是,现有的 Promise 方法很难实现这个要求。
Promise.all()方法只适合所有异步操作都成功的情况,如果有一个操作失败,就无法满足要求。
为了解决这个问题,ES2020 引入了Promise.allSettled()方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。
Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更。
该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
2
3
4
5
6
7
8
9
10
11
12
13
# 4. Promise.any() [ES2021]
ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
# 5. Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
2
3
参数是一个 Promise 实例 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
参数是一个thenable对象 thenable对象指的是具有then方法的对象。 Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。
参数不是具有then()方法的对象,或根本就不是对象 如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。
不带有任何参数 Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。 所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。
# 6. Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
2
3
4
5
6
7
8
# 参考文章链接
阮一峰ES6教程 (opens new window)
当面试官问Promise的时候他想知道什么 (opens new window)
面试精选之Promise (opens new window)