什么是Promise
当我们说到Promise的时候可能会有两个意思,第一个是Promise A+是一套民间规范,第二个就是ES6 Promise构造函数。这两个我们都来说一下。
Promise A+是在2015年也就是ES6之前就出现的,那时候的异步处理就比较混乱,于是社区就自行出了这么一套Promise A+规范去处理异步,统一了异步的处理模型,解决了回调地狱的问题,让不同的人之间的代码可以相互操作。Promise A+规范文档里开头就介绍了凡是带then方法的对象都是Promise:
const obj = {
then() {},
};
// 这就是一个Promise
// 或者说一个数组,数组也是对象
const arr = [];
arr.then;
// 那么这也是一个Promise
// 再或者说一个函数,函数也是对象
const fun = function () {};
fun.then;
// 那么这也是一个Promise
ES6的Promise是一个构造函数,通过这个构造函数new出来的对象就是满足Promise A+规范的对象,也有一个then方法,而且ES6对Promise还加了一些方法比如catch方法,finally方法,还有一些静态方法比如Promise.all,Promise.race,Promise.allsettled
我们这里主要讲的是ES6的Promise构造函数。
Promise的基本使用
Promise构造函数
Promise构造函数:Promise(excutor) {}
- excutor:是一个执行函数(resolve,reject) => {},该执行函数里面也有两个参数:
- resolve:内部定义成功时调用的函数,可以传一个实参给then方法的onResolved函数。
- reject:内部定义失败时调用的函数,可以传一个实参给then方法的onRejected函数。
我们在进行new这个Promise构造函数去创建实例对象的时候,会在执行函数里进行一个条件判定(因为Promise一般都是处理异步问题,根据是否满足条件去调用回调函数),如果满足条件就去调用resolve函数,在不满足条件的时候调用reject函数,至于调用这两个函数有什么作用,是去修改Promise的状态和结果的,也是指定Promise实例对象的回调函数的,下面会讲到这一点,现在知道执行函数里面做什么,又什么时候调用resolve函数和reject函数就好了。
const p = new Promise((resolve, reject) => {
if (满足) {
resolve();
} else {
reject();
}
});
有一个比较重要的点就是,excutor执行函数会在Promise内部立即同步调用,就是说在开始new的时候执行函数就已经开始执行了,这点在实际应用中很重要。
const p = new Promise((resolve, reject) => {
console.log(1);
});
console.log(2);
先打印1后打印2就说明执行函数是同步调用的。
then方法
then方法是用来调用回调函数的,我们在前面判断完是成功还是失败以后,就要在then方法里面去调用回调函数,then方法里面会有两个参数,这两个参数都是回调函数,至于调用哪一个,就要根据Promise实例对象的状态去判断了,也就是根据前面调用的是resolve函数还是reject函数了。
Promise.prototype.then(onResolved,onRejected)
- onResolved:成功的回调函数 (value)=>{},接收一个形参,就是resolve函数传的值。
- onRejected:失败的回调函数 (reason)=>{},接收一个形参,就是reject函数传的值。
如果执行函数里面调用的是resolve函数,那么then方法里面就会去调用第一个函数参数onResolved,如果执行函数里面调用的是reject函数,那么then方法里面就会去调用第二个函数参数onRjected。
而且then方法会返回一个新的Promise对象,这个后面会讲。
做个小案例感受一下:设计一个小功能,点击按钮生成随机数,如果随机数小于30就弹出一个成功。
<body>
<div>
<h2>Promise初体验</h2>
<h2>点击按钮,30%概率中奖</h2>
<button id="btn">点击抽奖</button>
</div>
</body>
<script>
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
}
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
const p = new Promise((resolve, reject) => {
let n = rand(1, 100);
if (n <= 30) {
resolve();
} else {
reject();
}
});
p.then(
() => {
alert("中奖了");
},
() => {
alert("再接再厉");
}
);
});
</script>
Promise的状态和结果
Promise实例对象有两个比较重要的属性,一个是状态PromiseState,另外一个就是结果PromiseResult。
状态
状态PromiseState有三个值:
- pending:初始化未决定的。
- resolved/fullfilled:成功的。
- rejected:失败的。
刚开始创建Promise实例对象的时候状态是还未修改的,为pending,调用resolve函数以后会将状态修改为resolved或者fullfilled表示成功,调用reject函数会将状态修改为rejected表示失败。
需要注意的是状态只能由pending修改为fullfilled或者rejected,而且是不可逆的,就是说第一次修改完状态以后是不能再被第二次修改的。
结果
Promise实例对象还有一个属性叫做结果PromiseResult,是对象成功或者失败的结果,也就是resolve函数和reject函数传递的值,也就是传给onResolved函数和onRejected函数的值。
结果Promise只能被resolve函数和reject函数修改。
catch方法
前面说了一个then方法是用来指定回调函数的,then方法里有两个函数参数,所以需要通过前面Promise实例对象的状态去判断调用哪一个,如果是成功的就调用第一个onResolved函数,如果是失败就调用第二个onRejected函数,catch方法跟then差不多,只不过只有一个函数参数,是失败的时候调用的。
Promise.prototype.catch(onRejected)
- onRejected:失败的回调函数 (reason)=>{},接收一个形参,就是reject函数传的值。
举个例子:
const p = new Promise((resolve, reject) => {
reject("我失败了");
});
p.catch((reason) => {
console.log(reason);
});
所以为了方便也为了好区分,我们会在then方法里只写成功的回调函数,在catch方法里写失败的回调函数。
p.then((value) => {
console.log(value);
}).catch((reason) => {
console.log(reason);
});
Promise的工作流程
通过Promise构造函数创建一个实例对象,在Promise内部去执行一个异步操作,前面说执行函数是同步的,这里的异步操作指的是在执行函数里去执行异步操作(一般Promise就是去处理异步问题的),如果是成功的,就调用resolve函数,将Promise实例对象状态改为成功,然后调用then方法的第一个回调函数,返回一个新的Promise实例对象。如果是失败的,就调用reject函数,将Promise实例对象状态改为失败,然后调用then方法的第二个回调函数或者catch方法的回调函数,返回一个新的Promise实例对象。
=> 成功 执行resolve() =>将Promise实例对象状态改为resolved => 调用 then 的第一个回调函数
new Promise() => 执行异步操作 => 返回一个新的Promise对象
=> 失败 执行reject() =>将Promise对象状态改为rejected => 调用 then 的第二个回调函数
Promise的静态方法 resolve方法
这里的resolve方法和前面说的new Promise((resolve,reject) => {})中的resolve函数不同,这里的是作用于构造函数Promise的静态方法,用于快速创建一个Promise实例对象。
Promise.resolve()
- 返回一个成功/失败的Promise对象。
- 接收一个参数,分为四种情况。
- 如果传入一个非Promise类型对象,则返回为一个Promise实例对象,状态为成功,结果值就是传入的参数值;
- 如果传入的是一个Promise类型的对象,会直接返回这个Promise类型的对象。
- 如果传入的是一个thenable对象,thenable对象就是一个带有then方法的对象,resolve会将这个对象直接转化为Promise对象,然后执行thenable对象的then方法。
- 如果不传入参数,会返回一个状态为成功的Promise实例对象,结果为undefined。
下面我们看几个案例:
当传入的参数是一个非Promis类型对象,比如数字,字符串,数组,普通对象
let p1 = Promise.resolve("于家宝");
let p2 = Promise.resolve(18);
let p3 = Promise.resolve([1, 2, 3]);
let p4 = Promise.resolve({ a: 1 });
console.log(p1);
console.log(p2);
console.log(p3);
console.log(p4);
可以看到返回的是一个Promise对象,状态都是成功的,然后结果值就是我们传入的参数本身。
当传入的参数为一个Promise类型对象
let p1 = new Promise((resolve, reject) => {
resolve("我成功了");
});
let p2 = Promise.resolve(p1);
console.log(p2, "传入成功的Promise对象");
let p3 = new Promise((resolve, reject) => {
reject("我失败了");
});
let p4 = Promise.resolve(p3);
console.log(p4, "传入失败的Promise对象");
let p5 = new Promise((resolve, reject) => {});
let p6 = Promise.resolve(p5);
console.log(p6, "传入pending的Promise对象");
可以看到是将这个Promise对象直接返回,不做任何变动。
至于为什么会有一个报错,是因为里面有一个失败的Promise对象p3,但是没有用then方法的第二个回调函数或者catch方法的回调函数去接收这个错误。
我们只需要使用then方法的第二个回调函数或者catch方法的回调函数去接收这个错误就不会在后台报错了。
let p3 = new Promise((resolve, reject) => {
reject("我失败了");
});
p3.then(
(value) => {},
(error) => {}
);
// 或者
p3.catch((error) => {});
这个时候不知道会不会有人提出,那p4不也是一个失败的Promise对象吗,为什么不需要去接收p4的错误,我暂时也不知道,我猜想是因为p3和p4是同一个Promise对象,所以只需要接收一个就好了,后面我也做了测试去接收p4的错误而不接收p3的,后台也没有报错,我感觉我猜对了,如果有人知道原因可以一起讨论下。
当传入的参数为thenable对象
首先先讲一下什么是thenable对象,thenable对象就是一个带有then方法的对象,比如:
let thenable = {
name: "yujiabao",
then: function(resolve, reject) {},
};
Promise.resolve()方法会将thenable对象转化为Promise实例对象,状态和结果由thenable对象的then方法决定,如果then方法里执行的是resolve函数,状态就是成功的,如果执行reject函数,状态就是失败的,结果为函数传的值。
let thenable = {
name: "yujiabao",
then: function(resolve, reject) {
resolve(this.name);
},
};
let p = Promise.resolve(thenable);
console.log(p);
当不传入参数
let p = Promise.resolve();
console.log(p);
不传入参数的时候会返回一个状态为成功的Promise对象,结果为undefined。就像是第一种情况,传入的非Promise对象,传入的是一个undefined,返回的是一个Promise对象,状态是成功的,然后结果值就是我们传入的参数本身。
Promise的静态方法 reject方法
同样这里的reject方法和new Promise((resolve,reject) => {})中的reeject函数不同。
Promise.reject()
- 无论参数是什么,都会返回一个失败的Promise对象,结果值就是参数本身。
做几个案例:
// 传入非Promise对象,比如数字,数组,字符串,对象
let p1 = Promise.reject(123)
let p2 = Promise.reject([1, 2, 3])
let p3 = Promise.reject('123')
let p4 = Promise.reject({ a: 1 })
console.log(p1, 'p1')
console.log(p2, 'p2')
console.log(p3, 'p3')
console.log(p4, 'p4')
console.log('---------------------------------------')
// 传入Promise对象
let pResolve = Promise.resolve('我是成功的Promise')
let p5 = Promise.reject(pResolve)
console.log(p5, 'p5')
let pReject = Promise.reject('我是失败的Promise')
let p6 = Promise.reject(pReject)
console.log(p6, 'p6')
// 传入thenable对象
let thenable = {
name: 'yujiabao',
then: function (resolve, reject) {
resolve(this.name)
}
}
let p7 = Promise.reject(thenable)
console.log(p7, 'p7')
看上面的案例,p1,p2,p3,p4传的都是非Promise类型对象,返回的错误状态的Promise实例对象,结果值为参数本身;p5,p6传的是不同状态的Promise实例对象,返回的都是错误状态的Promise实例对象,结果还是参数本身;p7传的值thenable对象,返回的还是错误状态的Promise实例对象,结果还是参数本身。
这一点跟resolve方法不太一样,要注意区分。
Promise的静态方法 all方法
Promise.all()方法用于将多个Promise实例对象包装成一个Promise实例对象。
接收一个参数为数组,数组里的每一项都是Promise实例对象,如果有不是Promise实例对象的会先去调用resolve方法将其快速的转换为Promise实例对象再进行处理。
Promise.all()返回的实例对象的状态由数组所有的实例对象决定,如果所有的实例对象的状态都是成功的,那么Promise.all()返回的实例对象的状态就是成功的,结果是数组所有实例对象的结果的集合。如果有一个是失败的,Promise.all()返回的实例对象的状态就是失败的,结果就是第一个失败的Promise实例对象的结果。
let p1 = 'yujiabao'
let p2 = Promise.resolve('我是成功的Promise实例对象')
let p3 = Promise.reject('我是失败的Promise实例对象')
let p = Promise.all([p1, p2, p3])
console.log(p)
let p1 = 'yujiabao'
let p2 = Promise.resolve('我是成功的Promise实例对象')
let p3 = Promise.resolve('我也是成功的Promise实例对象')
let p = Promise.all([p1, p2, p3])
console.log(p)
Promise的静态方法 race方法
Promise.race()方法同样用于将多个Promise实例对象包装成一个Promise实例对象。和Promise.all()方法不同的是,Promise.race()方法返回的实例对象的状态和结果就是第一个率先改变状态的Promise实例对象的状态和结果。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
}, 1000)
})
let p2 = Promise.resolve('success')
let p3 = Promise.resolve('oh yeah')
const p = Promise.race([p1, p2, p3])
console.log(p)
看代码这里第一个完成状态改变的是p2,所以Promise.race()方法返回的Promise实例对象的状态和结果就是p2的状态和结果.
这个方法可以用于如果指定时间内没有获得结果就报错。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
])
p.then(console.log).catch(console.error)
Promise的静态方法 allSettled方法
Promise.allSettled()方法亦是同样用于将多个Promise实例对象包装成一个Promise实例对象。也是接收一个数组参数,每一项都是一个Promise实例对象,如果不是就先调用Promise.resolve()方法转换一下,和all方法和race方法不同的是,allSettled方法会等数组所有的实例对象都改变状态以后才会返回一个新的Promise实例对象,而且该实例对象只能是成功的也就是状态只能是fulfilled,结果是一个数组,数组的每一项都是一个对象,有一个属性叫做state,对应的是参数数组的每一个实例对象的状态,如果状态是成功的就为fulfilled,然后就会有另外一个属性value存放结果,如果是失败的就为rejected,就有另外一个属性reason存放结果。
let p1 = 1;
let p2 = Promise.resolve(2);
let p3 = Promise.reject(3);
let p = Promise.allSettled([p1, p2, p3]);
console.log(p);