ES2015正式发布(也就是ES6),其中Promise被列为正式规范。作为ES6中最重要的特性之一,它是前端异步编程的重要方案,我们有必要掌握并理解透彻。
Promise是什么
我们用console.dir(Promise)先来看看Promise是什么
可见,Promise是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。
这是MDN对它的介绍
一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。
一个 Promise 必然处于以下几种状态之一:`
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled): 意味着操作成功完成。
- 已拒绝(rejected): 意味着操作失败。
待定状态的 Promise 对象要么会通过一个值被兑现(fulfilled),要么会通过一个原因(错误)被拒绝(rejected)。当这些情况之一发生时,我们用 promise 的 then 方法排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序就会被调用,因此在完成异步操作和绑定处理方法之间不会存在竞争状态。
聊一聊Promise怎么使用
resolve
function fn1(){
const a = new Promise(function(reslove, reject) {
setTimeout(function(){
console.log('执行完毕')
reslove('我是数据')
}, 1000)
})
return a
}
fn1()
在chrome浏览器的控制台中,Promise被立即执行了,而不是我们以为的是异步执行的,并确定了它的状态,是成功的
then
fn1().then(function(data){
console.log(data)
})
then里面的函数返回了resolve中的状态,很像我们以前的写的回调函数,那我们为什么还需要它呢,正是因为,如果当回调函数越来越多的时候,它将变得难以维护和阅读,而Promise却可以将这两者分离开来,还可以链式调用,下面就为大家展示它的使用
链式调用
function fn1(){
const a = new Promise(function(resolve, reject) {
setTimeout(function(){
console.log('执行完毕')
resolve('我是数据1')
}, 1000)
})
return a
}
fn1();
function fn2(){
const b = new Promise(function(resolve, reject) {
setTimeout(function(){
console.log('执行完毕')
resolve('我是数据2')
}, 1000)
})
return b
}
fn2();
function fn3(){
const c = new Promise(function(resolve, reject) {
setTimeout(function(){
console.log('执行完毕')
resolve('我是数据3')
}, 1000)
})
return c
}
fn3();
fn1()
.then(function(data) {
console.log(data)
return fn2()
})
.then(function(data) {
console.log(data)
return fn3()
})
.then(function(data) {
console.log(data)
})
从这个例子中更加可以清晰的可以看出,promise的状态是立即返回的,而then中函数是异步执行的。大家也可以看到,代码优雅了很多。
reject
function fn4() {
const d = new Promise(function(resolve, reject) {
setTimeout(function() {
let num1 = 3
if(num1 > 5) {
resolve(num1)
}else {
reject('我失败了')
}
}, 1000)
})
return d
}
fn4();
fn4()
.then(
function(data) {
console.log(data)
},
function(reason) {
console.log(reason)
}
)
这里因为3 < 5,不满足fulfilled的条件,执行了reject,在then这个回调中直接抛出了错误
catch
var p1 = new Promise(function(resolve, reject) {
throw 'Uh-oh!';
});
p1.catch(function(e) {
console.log(e); // "Uh-oh!"
});
与我们常用见try catch类似,catch方法通常也是用来捕获错误的,区别在于使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。但是要注意的是,在异步函数中抛出的错误不能被catch捕获到,在resolve后面抛出的错误也不会被捕获。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。一般来说,不要在then()方法里面定义 Reject 状态的回调函数,总是使用catch方法。
race
Promise
.all([fn1(), fn2(), fn3()]) //还是我们前面的三个函数
.then(function(results){
console.log(results);
});
返回了第一个值,因为是第一个执行的,最快执行完毕,所以返回了它的值。race也就竞赛的意思。
all
Promise
.all([fn1(), fn2(), fn3()]) //还是我们前面的三个函数
.then(function(results){
console.log(results);
});
同时将我们三个函数执行完了,并返回了他们值的集合。all也就是全部的意思。
要注意的是 全部fulfilled才会执行then的回调或者有一个rejected,也会进入then的回调,此时第一个被rejected的实例的返回值,会被传递。
最新标准
finally: ES2018引入,用于指定不管Promise对象最后状态如何,都会执行的操作allSettled: ES2020引入,接受一组Promise实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
尝试一个简易的Promise
后续会根据标准,尝试一个简易的Promise,更加深入理解