第七篇:Promise的基本使用
Promise简介:
抽象:Promise是JS中进行异步编程的新方案 老的(纯回调函数)
具体:从语法上来讲 Promise是一个内置的构造函数
功能上Promise的实例对象可以封装一个异步操作并可以获得其结果
1.优势:指定回调函数的方式更加灵活
旧的:必须在启动异步任务之前指定
Promise:启动异步任务 => 返回Promise对象 => 给Promise对象绑定回调函数(甚至可以在异步任务结束后指定)
2.支持链式调用,可以解决回调地狱问题
(1):什么是回调地狱:
回调函数的嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件
(2):回调地狱的弊端:
代码不便于阅读,不便于异常的处理
(3):一个不是很优秀的解决方案:
then的链式调用
(4):终极解决方案:
async/await (底层实际上依然使用then的链式调用)
大纲:
【学Promise必须弄明白】
【Promise基本使用】
【Promise配合ajax使用】
【利用Promise封装ajax请求】
【改变实例对象的状态和指定回调的顺序】
【.then配合ajax的链式调用】
【中断Promise链】
【错误的穿透】
【最终的解决方案async...await】ES7的最新处理方法
---------------------------------------------------------------------------------------------------------------------------------
【学Promise必须弄明白】
1.了解什么是函数对象与实例对象
function Fn(){
// 函数对象
Fn.name='wang';
}
// 实例对象
let f1 = new Fn();
2.回调函数
由我们自己定义的但是不是我们自己调用的函数
同步回调函数:
例如:array.forEach(element => {
});在主线程上运行的
异步回调函数:
例如:btn.onclick = ()=>{};setTimeout(()=>{});xhr.onreadystatechange = ()=>{};
3.捕获js中任务执行时可能出现的错误以及处理方法 try...catch...
try{
//此处放可能出现错误的代码 一旦出现错误该任务将停止执行调用catch,并携带错误信息
}catch(error){
//error 为错误的原因
console.log('出错了原因是',error)
}
function isOu(){
let time = Date.now();
if(time%2 === 0){
console.log('偶数可以运行')
}else{
throw new Error('奇数执行出错了')
}
}
//捕获当上述代码运行时可能出现的错误
try{
isOu()}catch(error){
console.log(error)
}
【Promise】
语法:
//实例一个Promise对象用来操作异步函数
const p = new Promise((resolve,reject)=>{
//这里运行异步函数 例如setTimeout
setTimeout(()=>{
if(){//成功判断条件
//指定成功时携带的数据
resolve(successData)
}else{
//指定失败时携带的数据
reject(faileData)
}
},2000)
});
//指定回调函数 接收异步函数运行的结果
p.then(
//成功时的回调函数
(value)=>{
console.log('成功了',value);
},
//失败时的回调函数
(reason)=>{
console.log('失败了',reason);
}
);
(1)Promise实例对象p有三种状态
初始化状态 pending
成功状态 fulfilled
失败状态 rejected
(2)当我们新建一个Promise实例对象p时 它的状态是pending 即初始化状态
(3)Promise实例对象状态的改变方式
pending(初始化)= > fulfilled(成功)
pendinh (初始化 = > rejected(失败)
(4)Promise实例对象的方法
const p = Promise.resolve() 快速创建一个成功状态的promise对象
const p = Promise.reject() 快速创建一个失败状态的promise对象
const p1 = new Promise();
const p2 = new Promise();
const p3 = new Promise();
const p = Promise.all([p1,p2,p3]);
当 p1、p2、p3状态都成功时 p的状态才是成功
当 p1、p2、p3状态有一个失败 则p的状态为失败且那个失败就从哪里停止运行
const p = Promise.race([p1,p2,p3]);
该方法只判断第一个也就是最短时间内出现的结果的状态
(5).then方法的说明
const p = new Promise();
p.then 用来指定Promise实例对象中的状态的回调函数
p.then(
(value)=>{
console.log('成功了',value);
},
(reason)=>{
console.log('失败了',reason);
}
)
p.then可以返回 一个新的Promise对象 且状态为pending
p.catch实际上是p.then方法的一个语法糖 专门用来指定失败的回调
【Promise配合ajax使用】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise配合Ajax使用</title>
</head>
<body>
<h3>Promise配合Ajax使用</h3>
<p>--[Promise]---</p>
<button id="btn">点击</button>
</body>
</html>
<script>
const btn = document.getElementById('btn');
btn.onclick = () => {
//用户点击时实例一个Promise对象
const p = new Promise((resolve, reject) => {
//实例一个xhr对象用来前后端交互的
const xhr = new XMLHttpRequest();
//指定xhr异步回调函数用来接收数据的
xhr.onreadystatechange = () => {
if(xhr.readyState===4){
if(xhr.status>=200 && xhr.status<300){
//当异步接收数据成功时
resolve(xhr.response);
}else{
//当异步接收数据失败时
reject('我是失败的数据')
}
}
}
xhr.responseType = 'json';
xhr.open('GET', 'http://127.0.0.1:8080/test_promise')
// xhr.open('GET', 'http://127.0.0.1:8080/test_promise2')//错误的地址
xhr.send();
});
//then方法用来指定成功和失败的回调函数 目的为了接收成功失败数据
p.then(
(value) => {
console.log('成功了', value);
},
(reason) => {
console.log('失败了', reason);
}
);
}
</script>
【利用Promise封装ajax请求】
创建一个函数用来发送ajax请求 可以指定不同的 url 以及请求携带的数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise封装Ajax</title>
</head>
<body>
<h3>Promise封装Ajax</h3>
<p>--[Promise]---</p>
<button id="btn">点击</button>
</body>
</html>
<script>
const btn = document.getElementById('btn');
// 创建函数 sendAjax(url,data)
function sendAjax(url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
} else {
reject('失败了');
}
}
}
xhr.responseType = 'json';
let str = '';
for (let key in data) {
str += `${key}=${data[key]}&`;
}
xhr.open('GET', url + '?' + str.slice(0, -1));
xhr.send();
})
}
btn.onclick = () => {
const p = sendAjax('http://127.0.0.1:8080/test_promise', { name: 'Cyndi' })
p.then(
(value) => {
console.log('成功了',value);
},
(reason) => {
console.log('失败了',reason);
}
)
};
console.log('我是主线程中的代码');
</script>
【改变实例对象的状态和指定回调的顺序】
1.先改变状态再指定回调:
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(100)
},1000)
})
setTimeout(()=>{
p.then(
value=>{
},
reason=>{
}
)
},3000)
2.先指定回调再改变状态
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(100)
},1000)
})
p.then(
value=>{
},
reason=>{
}
)
【.then配合ajax的链式调用】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise的链式调用</title>
</head>
<body>
<h3>Promise的链式调用</h3>
<p>--[Promise]---</p>
<button id="btn">点击</button>
</body>
</html>
<script>
const btn = document.getElementById('btn');
// 封装一个使用Promise处理ajax请求的函数
function sendAjxax(url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if(xhr.readyState===4){
if(xhr.status>=200 && xhr.status<300){
resolve(xhr.response);
}else{
reject('错误的请求');
}
}
}
xhr.responseType = 'json';
let str = '';
for(let key in data){
str += `${key}=${data[key]}&`
}
xhr.open('GET',url+'?'+str.slice(0,-1));
xhr.send();
})
};
btn.onclick = ()=>{
sendAjxax('http://127.0.0.1:8080/test_promise1',{name:'wang'})
.then(//命名第一次请求数据的回调
(value)=>{
console.log('成功了1',value);
// 当第一次成功之后 发取第二次请求 返回一个Promise实例对象 第二次
// 请求的结果取决于当前函数的调用结果
return sendAjxax('http://127.0.0.1:8080/test_promise2',{name:'胡歌'});
},
(reason)=>{
console.log('失败了',reason);
}
)
.then(//命名第二次请求数据的回调
(value)=>{//如果成功则获取第二次请求的数据
console.log('成功了2',value);
return sendAjxax('http://127.0.0.1:8080/test_promise3',{name:'Cyndi'});
},
(reason)=>{
console.log('失败了',reason);
}
)
.then(//命名第三次请求数据的回调
(value)=>{//如果成功则获取第三次请求的数据
console.log('成功了3',value);
},
(reason)=>{
console.log('失败了',reason);
}
)
}
</script>
链式调用很好的解决了回调地狱问题 只要发一个请求即可在前一次p.then成功的回调函数中返回
一个封装的ajax请求函数(返回一个新的Promise实例对象决定外侧p.then新的Promise对象的状态)
p.then(
value => {
console.log(valkue);
return sendAjax(url, data);//返回一个Promise实例对象
},
reason => {
}
)//生成一个新得为初始化状态的Promise实例对象
.then(
value => {
console.log(valkue);
return sendAjax(url, data)
},
reason => {
}
)
【中断Promise链】
该操作目的是为了当Promise链进入到错误的状态时不会出现意外的执行(当出现请求错误的时候不停止运行,却运行了成功的回调)
原因:这个与Promise对象的自身机制是相关的
当某一次请求出错的时候就会进入到.then的错误回调中
如果错误的回调没有指定返回的结果则默认 为undefined 它是一个非promise的值 如果一个Promise实例对象的状态接收的是一个非Promise的值 则认为是成功的状态 那么下个.then就会走成功的回调 这样就不符合预期的情况 (一旦出错即停止运行)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>中断Promise链</title>
</head>
<body>
<h3>中断Promise链</h3>
<p>--[Promise]---</p>
<button id="btn">点击</button>
</body>
</html>
<script>
const btn = document.getElementById('btn');
// 封装一个使用Promise处理ajax请求的函数
function sendAjxax(url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if(xhr.readyState===4){
if(xhr.status>=200 && xhr.status<300){
resolve(xhr.response);
}else{
reject('错误的请求');
}
}
}
xhr.responseType = 'json';
let str = '';
for(let key in data){
str += `${key}=${data[key]}&`
}
xhr.open('GET',url+'?'+str.slice(0,-1));
xhr.send();
})
};
btn.onclick = ()=>{
// sendAjxax('http://127.0.0.1:8080/test_promise1',{name:'wang'})
// 测试一下第一次请求失败是否能中端后面的任务
sendAjxax('http://127.0.0.1:8080/test1_promise1',{name:'wang'})
.then(//命名第一次请求数据的回调
(value)=>{
console.log('第一次成功了1',value);
// 当第一次成功之后 发取第二次请求 返回一个Promise实例对象 第二次
// 请求的结果取决于当前函数的调用结果
return sendAjxax('http://127.0.0.1:8080/test_promise2',{name:'胡歌'});
},
(reason)=>{
console.log('第一次失败了',reason);
// 当第一次失败的时候想要停止后面的任务
// 只能返回一个Promise实例状态为pending 的对象
return new Promise(()=>{});
}
)
.then(//命名第二次请求数据的回调
(value)=>{//如果成功则获取第二次请求的数据
console.log('成功了2',value);
return sendAjxax('http://127.0.0.1:8080/test_promise3',{name:'Cyndi'});
},
(reason)=>{
console.log('第二次失败了',reason);
return new Promise(()=>{});
}
)
.then(//命名第三次请求数据的回调
(value)=>{//如果成功则获取第三次请求的数据
console.log('成功了3',value);
},
(reason)=>{
console.log('第三次失败了',reason);
return new Promise(()=>{});
}
)
}
</script>
【错误的穿透】
目的是为了简化上述中断Promise链太繁杂的处理方式
其实就是不指定错误的回调 而在最后面添加一个.catch方法用来处理当执行错误的时候执行的函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>错误的穿透</title>
</head>
<body>
<h3>错误的穿透</h3>
<p>--[Promise]---</p>
<button id="btn">点击</button>
</body>
</html>
<script>
const btn = document.getElementById('btn');
// 封装一个使用Promise处理ajax请求的函数
function sendAjxax(url, data,index) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if(xhr.readyState===4){
if(xhr.status>=200 && xhr.status<300){
resolve(xhr.response);
}else{
reject(`第${index}次失败了`);
}
}
}
xhr.responseType = 'json';
let str = '';
for(let key in data){
str += `${key}=${data[key]}&`
}
xhr.open('GET',url+'?'+str.slice(0,-1));
xhr.send();
})
};
btn.onclick = ()=>{
//第一次成功的请求
sendAjxax('http://127.0.0.1:8080/test_promise1',{name:'wang'})
// 测试一下第一次请求失败是否能中端后面的任务
// sendAjxax('http://127.0.0.1:8080/test1_promise1',{name:'wang'},1)
.then(//命名第一次请求数据的回调
(value)=>{
console.log('第一次成功了1',value);
// 当第一次成功之后 发取第二次请求 返回一个Promise实例对象 第二次
// 请求的结果取决于当前函数的调用结果
return sendAjxax('http://127.0.0.1:8080/test_promise2',{name:'胡歌'},2);
}
)
.then(//命名第二次请求数据的回调
(value)=>{//如果成功则获取第二次请求的数据
console.log('第二次成功了',value);
return sendAjxax('http://127.0.0.1:8080/test_promise3',{name:'Cyndi'},3);
}
)
.then(//命名第三次请求数据的回调
(value)=>{//如果成功则获取第三次请求的数据
console.log('第三次成功了',value);
}
)
.catch(
reason =>{
console.log('失败了',reason);
}
)
}
</script>
【最终的解决方案async...await】ES7的最新处理方法
1.async修饰的函数
函数的返回值为Promise对象
Promise实例的结果有async函数执行的返回值决定
2.await表达式
await右侧的表达式,一般为Promise实例对象,但也可以是其他的值
如果表达式是Promise对象,await后的返回值是Promise成功的值
如果表达式是其他值,直接将此值作为await的返回值
3.注意:
await必须写在async函数中,但async函数中可以没有await
如果await的Promise的值失败了,就会抛出异常,需要通过try...catch来捕获异常
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async&await的应用规则</title>
</head>
<body>
<h3>async&await的应用</h3>
<p>--[async&await]---</p>
<button id="btn">点击</button>
</body>
</html>
<script src="../jquery.min.js"></script>
<script>
const btn = document.getElementById('btn')
function sendAjxax(url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if(xhr.readyState===4){
if(xhr.status>=200 && xhr.status<300){
resolve(xhr.response);
}else{
reject('出错了');
}
}
}
xhr.responseType = 'json';
let str = '';
for(let key in data){
str += `${key}=${data[key]}&`
}
xhr.open('GET',url+'?'+str.slice(0,-1));
xhr.send();
})
};
btn.onclick = ()=>{
;(async()=>{
try{
const res1 = await sendAjxax('http://127.0.0.1:8080/test_promise1',{name:'wang'});
console.log(res1);
const res2 = await sendAjxax('http://127.0.0.1:8080/test_promise2',{name:'hu'});
console.log(res2);
const res3 = await sendAjxax('http://127.0.0.1:8080/test_promise3',{name:'Cyndi'});
console.log(res3);
}catch(error){
console.log('出错了',error);
}
})();
}
</script>