1.为什么要使用promise?
(1) 解决回调地狱(Callback Hell)
回调地狱:回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函数执行的条件。
缺点:会产生回调的嵌套,代码组织形式也是逐渐向右,这种代码是难以阅读的。然后就是异常处理还要分别去处理,这样也会加大处理难度。
(2) 指定回调方式更灵活
纯回调:回调函数必须事先指定好,也就是要先指定回调函数,然后再启动异步任务,而且必须这样。
promise:回调函数可以在启动异步任务后指定,也可以在启动异步任务之前。甚至可以在异步任务完成后指定,依然能够得到结果数据。
2.promise实践
(1) promsie初体验
<div id="btn">点我抽奖</div>
<script>
const random = () => {
return Math.floor(Math.random() * 100 + 1); //0-100随机数
}
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
const p = new Promise((resolve, reject) => {
setTimeout(() => {
let num = random();
if (num < 50) { // 50%中奖率
resolve(num);
} else {
reject(num);
}
}, 1000)
});
p.then(res => {
alert('中奖了' + res);
}, reason => {
alert('感谢参加此次活动' + reason);
})
});
</script>
(2) promise封装Ajax请求
<div id="btn">点我发送Ajax</div>
<script>
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts'); //接口源自网络
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
}
});
p.then(res => {
console.log(res);
}, reason => {
console.log(reason);
})
});
</script>
(3) 封装一个函数读取文件内容
const mineReadFile = path => {
return new Promise((resolve, reject) => {
require('fs').readFile(path, (err, data) => {
if (err) reject(err);
resolve(data);
})
})
}
//使用
mineReadFile('./text/index.txt').then(res => {
console.log(res.toString());
}, reason => {
console.log(reason);
})
(4) 封装一个函数发送 GET ajax请求
const sendAJAX = url => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
}
})
}
sendAJAX('https://jsonplaceholder.typicode.com/posts').then(res => {
console.log(res);
}, reason => {
console.log(reason);
})
3.promise状态
3种状态,开始为pending(未完成),执行resolve(),变成fulfilled/resolved(已成功);执行reject(),变成rejected,已失败。
注意:一个promise对象只能从pending变为fulfilled/resolved或从pending变为rejected,且只改变一次,无论成功或失败都有一个结果数据,且结果数据只能被resolve()或reject()修改。
4.promise基本流程
5.promise的api
1.promise构造函数:Promise(excutor){}
(1)executor函数:执行器(resolve,reject)=>{}
(2)resolve函数:内部定义成功时调用的函数
(3)reject函数:内部定义失败时调用的函数
注意:excutor会在Promise内部立即同步调用
2.Promise.prototype.then()方法:(onResolved,onRejected)=>{}
(1)onResolved函数:成功的回调函数 res=>{}
(2)onRejected函数:失败的回调函数 reason=>{}
说明:指定用于得到成功的res的成功回调和用于得到失败的reason的失败回调,返回一个新的promise对象
3.Promise.prototype.catch()方法:(onRejected)=>{}
(1)onRejected函数:失败的回调函数 reason=>{}
说明:本质是then()的特例,专门处理失败状态;then()可处理成功和失败状态,专门用来处理成功状态。
4.Promise.resolve()
new Promise(resolve => resolve());
Promise.resolve(); //两者等价,后者相当于Promise成功状态的简写形式
说明:对于Promise.resolve()的参数,若为非promise类型的对象,则返回成功状态下的promise对象。若传入参数为promise对象,则返回的promise对象状态由参数(传入的promise对象的结果)决定。
5.Promise.reject()
new Promise(reject => reject());
Promise.reject(); //两者等价,后者相当于Promise失败状态的简写形式
说明:它的参数,不管什么类型,都会原封不动的向后传递,作为后续方法的参数。且返回值为一个失败的promise对象。
6.Promise.all()
const p1 = new Promise(resolve =>{
resolve('p1:success');
})
const p2 = Promise.resolve('p2:success');
const p3 = Promise.resolve('p3:success');
const result = Promise.all([p1,p2,p3]);
console.log(result);
const p1 = new Promise((resolve,reject) =>{
reject('p1:failed');
})
const p2 = Promise.resolve('p2:success');
const p3 = Promise.resolve('p3:success');
const result = Promise.all([p1,p2,p3]);
console.log(result);
说明:传入多个promise实例,包装成一个新的promise对象返回;只有传入的promise都成功才成功,且返回成功结果组成的数组,只要有一个失败,就失败,返回失败的promise对象的结果。
7.Promise.race()
const p1 = new Promise((resolve,reject) =>{
setTimeout(function(){
reject('p1:failed');
})
})
const p2 = Promise.resolve('p2:success');
const p3 = Promise.resolve('p3:success');
const result = Promise.race([p1,p2,p3]);
console.log(result);
说明:Promise.race()状态取决于最先完成的promise状态,最先完成的成功了,就成功;否则,就失败。
8.Promise.allSettled()
const p1 = new Promise((resolve,reject) =>{
setTimeout(function(){
reject('p1:failed');
})
})
const p2 = Promise.resolve('p2:success');
const p3 = Promise.resolve('p3:success');
const result = Promise.allSettled([p1,p2,p3]);
console.log(result);
说明:Promise.allSettled()的状态与传入的promise状态无关,永远都是成功的,它会忠实的记录下各个promise的表现。
6.promise面试相关问题
1.如何改变promise的状态?
const p1 = new Promise((resolve,reject) =>{
resolve('成功了'); //1.pending ——>fulfilled/resolved
reject('失败了'); //2.pending ——>rejected
throw('出错了'); //3.pending ——>rejected
})
2.一个promise指定多个成功/失败回调函数,都会调用吗?
当promise改变为对应状态时都会调用。
const p1 = new Promise((resolve, reject) => {
resolve('成功了');
})
p1.then(res => {
console.log('then1调用了');
})
p1.then(res => {
console.log('then2调用了');
})
//then1调用了 then2调用了
3.改变promise状态和指定回调函数,执行谁先谁后?
都有可能,如下:
(1)先改变状态,再指定回调
const p1 = new Promise((resolve, reject) => {
resolve('成功了'); //在执行器中直接定义了resolve()/reject()
})
p1.then(res => {
console.log(res);
})
const p1 = new Promise((resolve, reject) => {
resolve('成功了');
})
setTimeout(function () { //延长更长时间才调用then
p1.then(res => {
console.log(res);
})
},1000)
(2)先指定回调,再改变状态(常用)
const p1 = new Promise((resolve, reject) => {
setTimeout(function(){
resolve('成功了');
},1000)
})
p1.then(res => {
console.log(res);
})
4.promise.then()返回新的promise的结果状态由什么决定?
(1) 抛出异常:新promise状态为rejected
const p1 = new Promise((resolve, reject) => {
resolve('成功了');
})
const result = p1.then(res => {
throw '出错了';
})
console.log(result);
(2)非promise的任意值:新promise状态为fulfilled
const p1 = new Promise((resolve, reject) => {
resolve('成功了');
})
const result = p1.then(res => {
return 123
})
console.log(result);
(3) promise:新promise状态由返回的promise状态决定
const p1 = new Promise((resolve, reject) => {
resolve('成功了');
})
const result = p1.then(res => {
return new Promise((resolve,rejected)=>{
resolve('success');
})
})
console.log(result); //fulfilled
5.promise如何串联多个操作任务?
通过then()进行链式调用
const p1 = new Promise((resolve, reject) => {
resolve('1');
})
const result = p1.then(res => {
return new Promise((resolve, reject) => {
resolve('2');
})
}).then(res=>{
console.log(res); //2
}).then(res=>{
console.log(res); //undefined
})
console.log(result);
undefined原因:它的上一层then()没有传递参数;
fulfilled原因:undefined属于非promise对象的任意值,故成功状态。
6.异常穿透?
当使用promise的then()链式调用时,可以在最后指定失败的回调,当前面的任何操作出现异常,都会传到最后失败的回调中处理。
const p1 = new Promise((resolve, reject) => {
resolve();
})
p1.then(res => {
return new Promise((resolve, reject) => {
resolve();
})
}).then(res => {
throw "中间出错了"
}).then(res => {
console.log(2);
}).catch(reason => {
console.log(reason); //中间出错了
})
7.中断promise链?
当使用promise的then的链式调用时,在中间中断,不在调用后面的回调函数。
方法:返回一个pending状态的promise对象。
const p1 = new Promise((resolve, reject) => {
resolve();
})
p1.then(res => {
return new Promise((resolve, reject) => {
resolve('1');
})
}).then(res => {
console.log(res);
return new Promise(()=>{});
}).then(res => {
console.log(2);
}).then(res => {
console.log(3);
})
//结果为1,因为被pending状态的promise对象中断了
7.async与await
1.async函数
(1)函数的返回值为promise对象;
(2)promise对象的结果由async函数执行的返回值决定。
说明:(2)与 4.promise.then()返回新的promise的结果状态由什么决定? 一摸一样。如·:
async function fn(){
throw "error";
}
let result = fn();
console.log(result);
2.await表达式
(1)await右侧的表达式一般为promise对象,但是也可以是其它值;
(2)如果表达式是promise对象,await返回的是promise成功的值;
(3)如果表达式是其他值,直接将此值作为await的返回值。
说明:await必须写在async函数中,但async函数中可以没有await;
如果await的promise失败了,就会抛出异常,需要通过try...catch捕获处理;
async function fn(){
let p = new Promise((resolve,reject)=>{
resolve('success');
})
let result = await p; //await右侧的表达式为promise对象
console.log(result);
}
fn(); //success
async function fn(){
let result = await 123; //await右侧的表达式是其他值
console.log(result);
}
fn(); //123
async function fn() {
let p = new Promise((resolve, reject) => {
reject('failed');
})
try {
let result = await p;
} catch (e) {
console.log(e);
}
}
fn(); //failed
3.实践
Ajax请求
<div id="btn">点我发送Ajax</div>
<script>
const sendAJAX = url => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
}
})
}
const btn = document.getElementById('btn');
btn.addEventListener('click', async function () {
let result = await sendAJAX('https://jsonplaceholder.typicode.com/posts');
console.log(result);
})
</script>
总结:
1. 是比promise更高级处理回调地狱的方案,无需书写回调函数。
2. async/await 可以使用的 try/catch 做错误处理,比 Promise 的错误捕获更简洁。
3. 代码阅读性友好。
面试题:
手写promise加载图片
<img src="" alt="" id="img">
<script>
const loadImgAsync = url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
resolve(img);
}
img.onerror = () => {
reject(`Could not load image at${url}`);
}
img.src = url;
})
}
const imgdom = document.getElementById('img');
loadImgAsync('https://s3.bmp.ovh/imgs/2022/05/01/b54d18ea20b08e8b.png').then(img => {
setTimeout(() => {
imgdom.src = img.src;
}, 1000)
}).catch(err => {
console.log(err)
})
</script>
文章大部分内容来自:
尚硅谷Web前端Promise教程从入门到精通_哔哩哔哩_bilibili
建议去观看,老师讲的很好!!!