Promise 详解

本篇文章详细介绍了 Promise

忽略标题 @…@

本文章详细讲解Promise

回调地狱 再看

在之前讲解生成器函数的时候我们就见识过了回调地狱 我们这里在复习一下

回调地狱就是不停的嵌套回调函数 语法混乱 不易维护

function pow(num,callback){
		callback(num*num);
}

pow(1,function(v){
	console.log(v);
	pow(2,function(v){
		console.log(v);
		pow(3,function(v){
		console.log(v);
		});
	});
});

Promise 基本使用

Promise 也是 异步编程的一种解决方案 语法上是一个构造函数 用来封装异步操作 并获取成功和失败的结果

简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Promiese是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理

Promise 是一个对象 在实例化时需要传递一个函数作为参数 在这个回调里面又有两个参数 分别是 resolve和reject 那么通过实例化后的Promise对象的then方法来调用触发 resolve 模块 catch方法触发 reject 模块

resolve 代表执行成功
reject 代表执行失败


// 成功时

const p = new Promise((resolved,reject)=>{
	 resolved(1);
});

p.then(value=>{
  console.log(value); // 1
},reason=>{
  console.log(reason);
});


// 失败时1

const p = new Promise((resolved,reject)=>{
   reject(1);
});

p.then(value=>{
  console.log(value); 
},reason=>{
  console.log(reason); // 1
});


// 失败时2

const p = new Promise((resolved,reject)=>{
reject(1);
});

p.then(value=>{
  console.log(value); 
});

p.catch(reason=>{
console.log(reason);  // 1
});

Promise API

  • Promise.prototype.then()
  • Promise.prototype.catch()
  • Promise.prototype.finally()
  • Promise.resolve()
  • Promise.reject()
  • Promise.all()
  • Promise.race()

Promise.prototype.then()

then() 方法返回一个 Promise (en-US)。它最多需要有两个参数:Promise 的成功和失败情况的回调函数

const p = new Promise((resolved,reject)=>{
    // resolved("成功");
	reject('失败');
});

p.then((value)=>{
   // 成功时执行此回调
   console.log(value);
},(reason)=>{
  // 失败时执行此回调
 console.log(reason);
});

Promise.prototype.catch()

catch() 方法返回一个Promise (en-US),并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected) 相同。 (事实上, calling obj.catch(onRejected) 内部calls obj.then(undefined, onRejected))

const p = new Promise((resolved,reject)=>{
    // resolved("成功");
	reject('失败');
});

p.then(undefined,(reason)=>{
  // 失败时执行此回调
 console.log(reason);
});

p.catch((reason)=>{
  // 失败时执行此回调
 console.log(reason);
});

Promise.prototype.finally()

finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
这避免了同样的语句需要在then()和catch()中各写一次的情况

类似 try catch 语句的 finally 语句

try{

}catch(error){

}finally{
	console.log("我总是能执行");
}

Promise 的 finally

必须要等Promise成功或者失败时才可以执行

像如下代码 finally 是不会执行的

const p = new Promise((resolved,reject)=>{
});

p.then((value)=>{

},(reason)=>{

});

p.finally(()=>{
console.log("我总是能执行");
});

如下代码会执行 finally

const p = new Promise((resolved,reject)=>{
resolved("成功!finally会执行");
});

p.then((value)=>{

},(reason)=>{

});

p.finally(()=>{
 console.log("我总是能执行");
});

Promise.resolve()

Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是thenable(即带有"then" 方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成

const p = Promise.resolve(1);

p.then(v=>{
	 console.log(v);	 // 1
});

带有"then" 方法

const p = Promise.resolve(1).then(v=>{});

p.then(v=>{
    console.log(v);	 // undefined
});

romise.reject()

Promise.reject()方法返回一个带有拒绝原因的Promise对象

const p = Promise.reject("太穷了!");

p.catch(reason=>{
	console.log(reason); // 太穷了!
});

Promise.all()

Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息

all 方法就是可以批量执行多个Promise 如果批量执行的Promise有一个失败 则整体结果为失败 如果成功 那么就可以获取到所有Promise成功的结果

const p = Promise.all([new Promise((resolved,reject)=>{
	resolved("1");
}) , new Promise((resolved,reject)=>{
	resolved("2");
}), new Promise((resolved,reject)=>{
	resolved("3");
}) ]);

p.then(values=>{
	console.log(values);   // ["1", "2", "3"]
},(reason)=>{
	console.log("reason:"+reason);
});

Promise.race()

race 方法就是可以批量执行多个Promise 只取得最先执行成功的Promise

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝

const p = Promise.race([new Promise((resolved,reject)=>{
	setTimeout(function(){
		resolved("1");
	},0);
}) , new Promise((resolved,reject)=>{
	resolved("2");
}), new Promise((resolved,reject)=>{
	resolved("3");
}) ]);

p.then(value=>{
	console.log(value); // 2
},(reason)=>{
	console.log("reason:"+reason);
});




const p = Promise.race([new Promise((resolved,reject)=>{
	setTimeout(function(){
		resolved("1");
	},0);
}) , new Promise((resolved,reject)=>{
	resolved("2");
}), new Promise((resolved,reject)=>{
	reject("3");
}) ]);

p.then(value=>{
	console.log(value); // 2
},(reason)=>{
	console.log("reason:"+reason);
});




const p = Promise.race([new Promise((resolved,reject)=>{
	setTimeout(function(){
		resolved("1");
	},0);
}) , new Promise((resolved,reject)=>{
	reject("2");
}), new Promise((resolved,reject)=>{
	resolved("3");
}) ]);

p.then(value=>{
	console.log(value); // reason:2
},(reason)=>{
	console.log("reason:"+reason);
});

Promise 相关问题

Promise的执行流程

Promise 链式调用

我们的 then 方法其实返回的还是一个Promise对象 所以我们可以继续调用Promise的方法

 const p = new Promise((resolved,reject)=>{
	   setTimeout(()=>{
		reject("失败!");
	   },1000);
 });

 p.then(v=>{
	  console.log(v);
	
  }).catch(reason=>{
	   console.log(reason);
});


const p = new Promise((resolved,reject)=>{
   setTimeout(()=>{
	resolved("成功!");
   },1000);
});

p.then(v=>{
  console.log(v); // 成功!
}).then(v=>{
  console.log(v); // undefined
}).then(v=>{
  console.log(v); // undefined
});

Promise的执行时机

Promise 默认在实例化后就会自动执行

 const p = new Promise((resolved,reject)=>{
	 	console.log("执行了......");
 }); // 执行了

如果我们并不希望Promise一开始就执行的话 我们可以用一个函数将Promise封装起来

 function Request(num){
	 return new Promise((resolved,reject)=>{
		  if(num !== 0){
			resolved("成功!");
		  }else{
			reject("失败!");
		  }
	 });
 }


 Request(1)
 .then(value=>{
	 console.log(value); // 成功!
 });

Promise的状态了解
对象的状态不受外界影响。Promise对象代表一个异步操作,

有三种状态:Pending(进行中),Fulfilled(已成功)和Rejected(已失败)。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。着也是Promise这个名字的由来,它的英语的意思就是承诺,表示其他手段无法改变

一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Fulfiled和从Pending变为Rejected.只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。这时就称为Resolved(已定型).如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果

这里的成功和失败其实我们之前一直都在用 这里主要是理解一下 Promise 默认的状态是Pending 我们可以输出Promise 查看

console.log(new Promise((resolved,reject)=>{})); // Promise {<pending>}
console.log(new Promise((resolved,reject)=>{resolved(1);})); // Promise {<fulfilled>: 1}
console.log(new Promise((resolved,reject)=>{reject(1);})); // Promise {<rejected>: 1}
console.log(new Promise((resolved,reject)=>{ resolved(1); reject(2); })); // Promise {<fulfilled>: 1}

链式调用返回值和状态问题

  • return 1
  • throw 1
  • throw new Error()
  • return Promise.resolved()
  • return Promise.reject()
  • return new Promise((resolved,reject)=>{})

我们的 Promise 是可以有多种类型的返回值的 对应不同的返回值 有着不同的处理

在起始的Promise中如果不修改对应的状态 then 方法或者 catch方法是不会执行的 而且我们返回了一个数字 但是我们的 p 还是一个 Promise 对象 我们这里的返回值是在执行器函数内部返回的 并不影响Promise实例的返回值

 const p = new Promise((resolved,reject)=>{
	 return 123;
 });

 p.then(value=>{
	console.log(value);
 }).then(value=>{
	console.log(value);
 });

一旦修改了 状态为 成功 后面的 then 都会执行 而且每一个then返回的都是一个Promise对象 但是除了第一个then能够输出内容其他的都是undefined

这是因为 我们的then返回的是一个新的Promise对象 而且 这个状态也没修改为成功
所以这个value是undefined

 const p = new Promise((resolved,reject)=>{
	 resolved("success");
 });

 p.then(value=>{
	console.log(value);
 }).then(value=>{
	console.log(value);
 }).then(value=>{
	console.log(value);
 });

如果我们设置了第一个Promise的状态为 reject 那么可以通过 catch 来捕获 第一个catch捕获后下面的catch就不在捕获第一个Promise的失败信息 其他的catch就可以捕获其调用catch函数的Promise对象的失败信息 如果我们只在最后写了一个catch那么最终的捕获会放到最终的这个catch回调中 即使上面经过很多层的then

我们也叫这种情况为 异常穿透

 const p = new Promise((resolved,reject)=>{
	reject("error!");
 });

 p.then(value=>{
	console.log(value);
 }).then(value=>{
	console.log(value);
 }).catch(reason=>{
	 console.log(reason,1);
 }).then(value=>{
	console.log(value);
 }).catch(reason=>{
	 console.log(reason);
 });

如果我们直接 return 一个基本值 那么就会直接作为当前返回的Promise对象的then方法的成功回调里的value属性参数的值
也就是说 使用 return 返回内容的话 默认的会将Promise的状态修改为 成功状态

如下代码就会对应输出所返回的内容

 const p = new Promise((resolved,reject)=>{
	resolved("errorasad!");
 });

 p.then(value=>{
	console.log(value);
	return 123;
 }).then(value=>{
	console.log(value);
	return 456;
 }).then(value=>{
	console.log(value);
 })

我们可以输出一下看状态

 const p = new Promise((resolved,reject)=>{
	resolved("success!");
 });

 console.log(p.then(value=>{
	console.log(value);
	return 123;  // [[PromiseState]]: "fulfilled" [[PromiseResult]]: 123
 }));

前面的知识了解完毕之后 剩下的就方便了

throw new Error 或者 throw ‘Error’ 都将修改Promise状态为失败

 const p = new Promise((resolved,reject)=>{
	resolved("success!");
 });

 p.then(value=>{
	console.log(value);
	throw new Error("Error");
 }).catch(reason=>{
	 console.log(reason);
 });
 
 
  const p = new Promise((resolved,reject)=>{
	resolved("success!");
 });

 p.then(value=>{
	console.log(value);
	throw "Error";
 }).catch(reason=>{
	 console.log(reason);
 });
 
 
 
 const p = new Promise((resolved,reject)=>{
	resolved("success!");
 });

 /*
  * [[PromiseState]]: "rejected"
  *	[[PromiseResult]]: "Error"
  */	
 console.log(p.then(value=>{
	console.log(value);
	throw "Error";
 }));


```js

返回 Promise.reslove() 直接返回一个成功状态的Promise并携带其成功的数据


const p = new Promise((resolved,reject)=>{
resolved(1);
});

p.then(value=>{
console.log(value);
return Promise.resolve(2);
}).then(value=>{
console.log(value);
return Promise.resolve(3);
}).then(value=>{
console.log(value);
});


返回 Promise.reject() 直接返回一个失败状态的Promise并携带其失败的数据

不能再在回  Promise.reject()  的 then 中 利用第二个回调处理捕获到失败信息 必须是新的Promise对象的catch方法才可以

```js
 const p = new Promise((resolved,reject)=>{
	resolved(1);
 });

 p.then(v=>{
	 console.log(v);
	 return Promise.reject("error01");
 }).then(undefined,reason=>{
	 console.log(reason);
 });

返回一个新的 Promise 对象


 const p = new Promise((resolved,reject)=>{
	resolved("success");
 });

 p.then(value=>{
	console.log(value);
	 return new Promise((resolved,reject)=>{
		resolved("success2");
	 });
 }).then(value=>{
	console.log(value);
 });

利用Promise执行有顺序的异步请求任务

 // 1. 请求用户购买的订单信息
 // 2. 请求用户的订单中的商品信息

function RequestData(userID){
 return new Promise((resolved,reject)=>{
	  setTimeout(()=>{
		  if(userID === 1){
			  console.log("拿到用户ID:"+userID);
			  console.log("开始根据用户ID请求订单数据......");
			  console.log("订单ID获取成功");
			  resolved(2);
			}
	  },1000);
 });
}

RequestData(1)
.then(orderId=>{
 return new Promise((resolved,reject)=>{
	setTimeout(()=>{
		  if(orderId === 2){
			  console.log("拿到订单D:"+orderId);
			  console.log("开始根据订单ID请求商品数据......");
			  console.log("商品ID获取成功");
			  resolved(3);
			}
	  },1000);
 });
})
.then(goodsId=>{
           console.log("拿到商品ID:"+goodsId);
 });

上面我们发现我们也是避开了回调函数的混乱写法 使用promise看起来更加类似同步代码的写法

但是 我们上面有个问题就是 我已经封装好了一个 RequestData 函数用来返回Promise 那么我们在下面的then方法中应该是继续返回当前这个封装好的函数 不需要在重新返回一个Promise了 换成我们封装好的函数 方便我们以后修改 所以需要对函数进行一次改造

这次改造我们将主要的业务放到封装好的函数中去 then方法只负责传递数据并执行异步操作

// 1. 请求用户购买的订单信息
// 2. 请求用户的订单中的商品信息

function RequestData(requestResult,setp){
 return new Promise((resolved,reject)=>{
	 if(setp == 1){
		setTimeout(()=>{
		  if(requestResult === 1){
			  console.log("拿到用户ID:"+requestResult);
			  console.log("开始根据用户ID请求订单数据......");
			  console.log("订单ID获取成功");
			  resolved(2);
			}
	  },1000);
	 }else if(setp == 2){
		setTimeout(()=>{
		  if(requestResult === 2){
			  console.log("拿到订单ID:"+requestResult);
			  console.log("开始根据订单ID请求商品数据......");
			  console.log("商品ID获取成功");
			  resolved(3);
			}
	   },1000);
	 }
 });
}

RequestData(1,1)
.then(orderId=>{
 return RequestData(orderId,2);
}).then(goodsId=>{
console.log("拿到商品ID:"+goodsId);
});

但是还有一点不够完美 现在我们是有两个顺序任务 假设以后有多个任务呢? 比如5 6 个 我们是不是得每一次都要手动传入一个对应的步骤 还要记住这个步骤 其实我们可以省略手动传入步骤这个操作 如果你希望手动传入那么这里就可以略过了~

所以我们接着改造一下

如下代码 利用了闭包的特性来实现自动累加步骤 我们主需要关注内部业务的判断就可以了 在外部 我们调用时只获取需要的数据即可

 // 1. 请求用户购买的订单信息
 // 2. 请求用户的订单中的商品信息
 
 function RequestData(){
	let setp = 0;	
	return function(requestResult){
		setp++;
		return new Promise((resolved,reject)=>{
		 if(setp == 1){
			setTimeout(()=>{
			  if(requestResult === 1){
				  console.log("拿到用户ID:"+requestResult);
				  console.log("开始根据用户ID请求订单数据......");
				  console.log("订单ID获取成功");
				  resolved(2);
				}
		  },1000);
		 }else if(setp == 2){
			setTimeout(()=>{
			  if(requestResult === 2){
				  console.log("拿到订单ID:"+requestResult);
				  console.log("开始根据订单ID请求商品数据......");
				  console.log("商品ID获取成功");
				  resolved(3);
				}
		   },1000);
		 }else if(setp === 3){
			setTimeout(()=>{
			  if(requestResult === 3){
				  console.log("拿到商品ID:"+requestResult);
				  console.log("开始根据商品ID查询商品的分类......");
				  console.log("商品的分获取成功");
				  resolved(4);

				  // 清空驻留在内存的变量
				  setp = null ;
				}
		   },1000);
		 }
	 });
	}
	
 }

 let request = RequestData();
 request(1).then(orderId=>{
	 return request(orderId);
 }).then(goodsId=>{
	return request(goodsId);
 }).then(category=>{
	console.log("拿到商品分类信息:"+category);
 });

Promise的优缺点

  • 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
  • Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

JS的队列模型

  • 宏队列
    • 事件回调
    • 定时器回调
    • ajax回调
  • 微队列
    • Promise 回调

异步操作中微任务队列优先执行于宏任务队列

如下是一到练习题 如果不看答案能作对 对于promise的执行时机掌握的是还不错啦


 
setTimeout(()=>{
		console.log(5);
},0);
const p = new Promise((resolved,reject)=>{
		setTimeout(()=>{
			console.log(2);
		},0);
		resolved(4);
		console.log(1);
}).then(value=>{
   console.log(value);
});

console.log(3);

答案

 // 最先执行的肯定是同步代码  在本例子中同步代码有 23 行 和 28行 所以 肯定先输出这两个 就是1 3
// 接着是异步代码 15 行  20行  25行 都是 JS中微任务队列优先于宏任务队列执行 所以 接着输出 4 Promise
// 的异步队列是微任务队列
// 定时器是宏任务队列  然后 输出 5 2    因为5更靠前
// 最后结果是  1 3 4 5 2

当然最后的例子也可以写三个方法分别返回对应promise

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值