1、作用
- 解决回调地狱问题
2、Promise的参数
- Promise是一个构造函数,通过new关键字实例化对象
- 接收一个函数作为参数
- 在接收的 作为参数的函数 中,又接收两个参数:resolve 和 reject
- resolve 和 reject 又是函数作为参数
3、Promise的状态 :state
- pending状态:准备、待决解、进行中
- fulfilled状态:已完成、成功
- rejected状态:已拒绝、失败
4、状态的改变
-
初始化的时候,当前Promise的状态:pending
-
调用作为参数的函数来改变:
- 调用resolve(); 将当前Promise的状态改变成:fulfilled
- 调用reject(); 将当前Promise的状态改变成:rejected
-
Promise执行体中出现运行错误,也会改变状态:
- 出现运行错误,当前Promise的状态改变成:rejected
-
主动抛出错误时,也会改变状态:
throw new Error('主动抛出一个error');
- 当前Promise的状态改变成:rejected
-
注:Promise的状态改变是一次性的,也就是状态的改变只有一个结果,不会切换。要么是fulfilled,要么是rejected
-
在Promise执行体中先调用 resolve(); 再调用 reject(); Promise的状态也只会改变一次,不会先后切换。
5、Promise的结果:result 属性
-
已知:Promise接收一个参数(函数类型),该函数又接收两个参数:resolve和reject,这两者也是函数类型。
-
既然是函数,那么就意味着可以传参。
resolve('成功的传参'); reject('失败的传参');
-
从第4点可知,调用resolve,会改变Promise的状态。并且调用resolve会改变Promise的结果,而这个结果就是resolve函数中传进的参数。
-
当调用 resolve(‘成功的传参’); Promise的 state: fulfiiled,会将resolve的参数赋值给Promise的result,result: ‘成功的传参’
-
同理:reject();
-
结论:Promise的结果改变是通过调用resolve()和reject()来传递参数 或者其他异常情况使得Promise的状态改变,传递错误信息 来实现的。
6、Promise的方法
- 方法的查看:因为Promise是一个构造函数,可以在
__proto__
上查看它拥有的方法。
6.1、then()方法
-
参数:then方法接收两个参数,这两个参数都是函数类型
-
第一个参数:成功回调函数
.then( ()=>{ console.log('成功时回调'); } )
-
第二个参数:失败回调函数
.then( ()=>{ console.log('成功时回调'); }, // 要加逗号把两个参数分隔开 ()=>{ console.log('失败时回调'); } )
-
参数函数的 触发时机:
- 成功回调函数,顾名思义,当Promise调用resolve(); state就会变成fulfilled,当状态变成fulfilled,就会触发.then()里面的成功回调函数。也就是第一个参数(函数类型)会被调用。
- 失败回调函数,同理,当Promise执行体执行出错或调用reject(); state就会变成rejected,当状态变成rejected,就会触发.then()里面的失败回调函数。也就是第二个参数(函数类型)会被调用。
- 如果Promise的state 保持 pending,那么是不会触发.then()里面的方法的。
- 必须有状态改变才会触发
- 改变:pending->fulfilled/rejected
- 不改变:pending->pending
6.1.1、then()方法 参数详解
-
从上文可知,then的参数数量:2个,类型:函数
-
触发时机:Promise的状态变成:fulfilled / rejected
-
既然是函数,那么就可以往里面传递参数。
- 参数来源:触发时机的源头
- 细细分析,从6.1的触发时机可知,Promise的状态改变,都是从调用resolve/reject 开始,调用了resolve,state就变成:fulfilled,此时Promise的result就接收到resolve中的参数。参考:第5点。
- 此时Promise的result就作为参数传递给了.then();
- state:fulfilled,接收到的参数是来自于:resolve()
- state:rejected,接收到的参数是来自于:rejected() 或者 执行出错error
new Promise( (resolve, reject)=>{ $.ajax({ type: 'GET', // 请求方式 url: 'xxx.xxx', // 请求url data:{ }, // 带的请求参数 // 请求成功回调 success: res=>{ // 如果ajax请求成功,就会得到数据:res // 此时调用resolve将Promise的state改变成:fulfilled // resolve携带【实参】改变当前Promise的result,再传递给.then() resolve(res); }, // 请求失败回调 error: err=>{ // 如果ajax请求失败,就会得到error信息 // 此时调用reject将Promise的state改变成:rejected // reject携带【实参】改变当前Promise的result,再传递给.then() reject(err); } }) }) .then( // fulfilled的时候,从Promise的result获取到实参res,赋给value (value)=>{ console.log('成功时回调', value); }, // rejected的时候,从Promise的result获取到实参error信息,赋给reason (reason)=>{ console.log('失败时回调', reason); } )
6.1.2、then()方法的返回值
-
then是同步的,但then里面的两个参数(函数类型)是异步的,只有等到Promise的state改变,才会触发回调
-
奇妙的地方:如果用一个变量来保存.then()的返回值,打印出来的不是一个值,而是一个新的Promise
- 也就是说,then()返回出去的是一个新的Promise (初始化state:pending)
-
由于返回出去的是一个新的Promise,那么就意味着它也拥有state、result、then…,那么就可以进行:链式操作(让代码扁平化,不陷入回调地狱)
new Promise( (resolve,reject)=>{ } ).then().then().then()...
6.1.3、修改then的返回值(一个新的Promise)的状态
- 假设:初始的Promise调用的是resolve(),那么**第一个.then()**触发的是成功回调(第一个参数)
- 在第一个.then()的成功回调中 返回出去一个值 可将当前then的返回值(一个新的Promise)的状态从pending变成fulfilled
- 而 返回出去的值 将作为实参传递给第二个.then()
new Promise( (resolve,reject)=>{ resolve('成功的传参'); }) .then( (value1)=>{ console.log('成功1时回调', value1); // 返回出去一个值,将改变下一个Promise的状态 // 让下一个then触发对应参数函数,并获取到实参 return '成功1'; }) .then( // 第二个then触发成功回调 // 获取到上一个then传递的【成功1】,赋给value2 (value2)=>{ console.log('成功2时回调', value2); return '成功2'; }) // 以此类推,无限链式操作 .then(...) .then(...) ...
- 对于执行出错触发的失败回调也可以通过 return出去一个值 改变当前then的返回值(一个新的Promise)的状态,从pending变成fulfilled,触发的是下一个then的成功回调
new Promise( (resolve,reject)=>{ reject('失败的传参'); }) .then( (value1)=>{ console.log('成功1时回调', value1); }, // 触发失败回调,接收到reason1 =【实参:'失败的传参'】 (reason1)=>{ console.log('失败1时回调', reason1); // 这里可以return 出去一个值,将当前then的状态改变成fulfilled // 触发下一个then的【成功回调】 return '第一个then失败回调传参'; } ) .then( // 第二个then触发成功回调 // 获取到上一个then传递的实参:value2 =【第一个then失败回调传参】 (value2)=>{ console.log('成功2时回调', value2); return '成功2'; }, // 不会被调用,除非有执行错误 (reason2)=>{ console.log('失败2时的回调', reason2); } ) // 以此类推,无限链式操作 .then(...) .then(...) ...
- 结论:无论在then的 成功回调 还是 失败回调 中return出去一个值,都会使当前then的返回值(一个新的Promise)的state从pending改变成fulfilled,从而触发下一个then的对应回调。只要return不断,那么就可以持续链式操作下去。
- 参数来源:触发时机的源头
6.2 、catch方法
6.2.1、执行时机
- catch在以下几种情况,会触发执行:
- 代码执行错误;
- 主动调用reject();
- 主动抛出错误
- 也就是:当Promise的state:rejected的时候,就会触发到catch();
6.2.2、catch的参数
- catch()接收一个参数(函数类型),当Promise的state:rejected的时候,会执行catch()里面的参数(函数)
- 由于参数是函数类型,那么该参数也可以传参进去。
- 结合6.2.1的执行时机,可以知道,传进去的实参,就是:
- 代码执行错误信息
- state变成rejected的原因
- 主动抛出的错误信息
7、Promise的常见写法
- 一般使用catch来捕获任何异常
- 在then中只写成功回调
new Promise( (resolve,reject)=>{
//Promise执行体
resolve('成功的传参');
reject('失败的传参');
})
.then(
(value)=>{
console.log('成功回调', value);
})
.catch(
(reason)=>{
console.log('失败回调/捕获异常', reason);
})
8、Promise解决回调地狱
8.1、回调地狱
- 后一次的请求依赖于前一次请求的结果,所以只能在前一次请求成功的回调中进行后一次请求;
- 在第一个请求成功的回调里进行第二个请求,在第二个请求成功的回调里进行第三个请求,在第三个请求成功…
// ------第一次请求------
$.ajax({
type: 'GET',
url: '1.data',
data: {
},
success: res =>{
let {
id} = res; // 用变量存储请求到结果
// ------第二次请求------
$.ajax({
type: 'GET',
url: '2.data',
data: {
id}, // 请求的参数依赖前一次请求结果
success: res =>{
let {
username} = res // 用变量存储请求到结果
// -------第三次请求------
$.ajax({
type: 'GET',
url: '3.data',
data: {
username}, // 请求的参数依赖前一次请求结果
success: res =>{
console.log(res); // ...可以停了,不想回调下去了
},
error: err =>{
// 第三次请求的失败回调
console.log(err)
}
})
},
error: err =>{
// 第二次请求的失败回调
console.log(err);
}
})
},
error: err =>{
// 第一次请求的失败回调
console.log(err);
}
})
8.2、解决回调地狱 (先不用catch)
new Promise((resolve, reject) => {
//------第一次请求------
$.ajax({
type: 'GET',
url: '1.data',
data: {
},
success: res => {
// 改变 第一个Promise的状态,pending->fulfilled
// 把请求结果传递给 第一个then
resolve(res);
},
error: err => {
// 第一次请求的失败回调
// 改变 第一个Promise的状态,pending->rejected
// 把错误信息传递给 第一个then
reject(err);
},
});
})
// 第一个then
.then(
// 第一个Promise:fulfilled回调
value => {
let {
id } = value; // 用变量存储请求到结果
// ------第二次请求------
// return 的新Promise会被下一个then接收到
return new Promise((resolve, reject) => {
$.ajax({
type: 'GET',
url: '2.data',
data: {
id }, // 请求的参数依赖前一次请求结果
success: res => {
// 将第二次请求成功的结果返回出去给第二个then接收
// 并改变当前Promise的state:pending->fulfilled
resolve(res);
},
error: err => {
reject(err);
},
});
});
},
// 第一个Promise:rejected回调
reason => {
console.log(reason);
}
)
// 第二个then
.then(
// 第二个Promise:fulfilled回调
value => {
let {
username } = value; // 用变量存储请求到结果
// ------第三次请求------
return new Promise((resolve, reject) => {
$.ajax({
type: 'GET',
url: '3.data',
data: {
username }, // 请求的参数依赖前一次请求结果
success: res => {
consle.log(res); // ...可以停了,不想回调下去了
},
error: err => {
console.log(err);
},
});
});
},
// 第二个Promise:rejected回调
reason => {
console.log(reason);
}
);
8.3、代码优化
- 可以看到,用Promise的写法与普通回调的写法相比,代码更加结构清晰,扁平化,易读,嵌套的层数比较少。
- 但是也可以看到代码中存在大量重复性代码,那么就可以进行重复部分的封装,让代码更加简洁明了。
// 封装的函数返回一个Promise实例,在后续的使用可以直接使用.then来获取请求得到的数据了
function ajaxReq(type, url, data={
}){
return new Promise( (resolve, reject)=>{
$.ajax({
type:type,
url: url,
data:data,
success: res =>{
resolve(res);
},
error: err =>{
reject(err);
}
})
})
}
// 因为ajaxReq返回的是一个Promise实例,那么直接用.then来获取数据
// ------第一次请求------
ajaxReq('GET', '1.data')
// 第一次处理
.then(
(value)=>{
let {
id} = value;
// ------第二次请求------
return ajaxReq('GET', '2.data', {
id})
})
// 第二次处理
.then(
(value) =>{
let {
username} = value;
// ------第三次请求------
return ajaxReq('GET', '3.data',{
username})
})
// 第三次处理
.then(
(value) =>{
console.log(value); // ...可以停了,不想回调下去了
})