Promise
一. Promise是什么?
Promise 是
异步编程
的一种解决方案,比传统的解决方案回调函数
, 更合理和更强大。ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象 。
指定回调函数方式更灵活易懂。(指指定成功的回调函数和失败的回调函数)
解决异步 回调地狱 的问题。
1. 回调地狱(嵌套金字塔)
当一个回调函数嵌套一个回调函数的时候
就会出现一个嵌套结构
当嵌套的多了就会出现回调地狱的情况
比如我们发送三个 ajax 请求
- 第一个正常发送
- 第二个请求需要第一个请求的结果中的某一个值作为参数
- 第三个请求需要第二个请求的结果中的某一个值作为参数
ajax({ url: '我是第一个请求', success (res) { // 现在发送第二个请求 ajax({ url: '我是第二个请求', data: { a: res.a, b: res.b }, success (res2) { // 进行第三个请求 ajax({ url: '我是第三个请求', data: { a: res2.a, b: res2.b }, success (res3) { console.log(res3) } }) } }) } })
回调地狱,其实就是回调函数嵌套过多导致的
- 当代码成为这个结构以后,已经没有维护的可能了
二. Promise使用
// promise 承诺
// promise 构造函数 创建Promise对象
// 接收参数 executor(执行器函数)
var promise = new Promise((resolve, reject) => {
// 如果成功, 调用resolve()方法传递参数, 最后执行then()方法的第一个参数
resolve();
// 如果失败, 调用reject()方法传递参数, 最后执行then()方法的第二个参数,或者catch()方法
setTimeout(() => {
reject();
}, 1000);
});
console.log(promise);
promise.then(
() => {
console.log("成功");
},
() => {
console.log("失败");
}
);
// then指定成功的回调参数, catch指定失败的回调参数
promise
.then(() => {
console.log("成功");
})
.catch(() => {
console.log("失败");
});
- 语法:
new Promise(function (resolve, reject) {
// resolve 表示成功的回调
// reject 表示失败的回调
}).then(function (res) {
// 成功的函数
}).catch(function (err) {
// 失败的函数
})
.then()
和.catch
方法
.then()
和 .catch()
方法都会返回一个新的 Promise 对象。无论是处理成功的结果还是捕获错误,这两个方法的返回行为都是确保 Promise 链式调用可以继续进行。
.then()
和.catch()
方法retrun
返回的值都会被离本方法最近的另一个.then()
方法接收到
详细说明:
.then()
方法:- 当调用
.then()
方法时,它会接受两个参数:第一个是处理成功情况的回调函数,第二个是处理失败情况的回调函数(可选)。 - 在调用
.then()
时,如果成功回调返回一个值,新的 Promise 就会解析该值。如果返回的是一个 Promise,新的 Promise 会”跟随”这个 Promise 的状态(即当它解决或者拒绝时,新的 Promise 也会相应地解决或拒绝)。
- 当调用
.catch()
方法:.catch()
是一个特殊类型的.then()
,只接受处理失败情况的回调函数。- 如果前面的 Promise 链中有任何拒绝的情况(即调用
reject()
),这个拒绝会被.catch()
捕获,并执行相应的回调。 - 同样,
.catch()
也会返回一个新的 Promise,允许继续链式调用。
示例:
以下是一个简单的示例,演示如何使用 .then()
和 .catch()
的返回值:
let promise = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
// 这里用一个条件来决定 resolve 或 reject
const success = true;
if (success) {
resolve("操作成功");
} else {
reject("操作失败");
}
}, 1000);
});
// 使用 then 和 catch
promise
.then((result) => {
console.log(result); // 输出: 操作成功
return"进一步处理"; // 返回一个新值
})
.then((newResult) => {
console.log(newResult); // 输出: 进一步处理
})
.catch((error) => {
console.error(error); // 处理错误
});
在这个例子中,promise
开始时是一个 Promise 对象,经过一秒后根据条件解析或拒绝。你可以看到:
- 第一个
.then()
返回供后续处理的值”进一步处理”。 - 如果 Promise 发生错误,错误会被
.catch()
捕获并处理。
总体来说,.then()
和 .catch()
的返回特性允许开发者方便地链式处理异步操作的结果及错误,形成灵活的控制流。
finally()方法
应用案例: 隐藏loading框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>finally方法</title>
</head>
<body>
<script>
// 无论promise执行成功还是失败,都会执行finally方法
var promise = new Promise((resolve, reject) => {
resolve("成功");
});
// promise
// .then((res) => {
// console.log(res);
// return "then方法执行"; // 返回一个新的promise
// })
// .catch((err) => {
// console.log(err);
// return "catch方法执行"; // 返回一个新的promise
// })
// .finally(() => {
// // 总会执行
// console.log("finally方法执行");
// });
/*
假如我们在finally方法中后再调用then方法,输出是:
成功
finally方法执行
then方法执行
*/
promise
.then((res) => {
console.log(res);
return "then方法执行"; // 返回一个新的promise
})
.catch((err) => {
console.log(err);
return "catch方法执行"; // 返回一个新的promise
})
.finally(() => {
// 总会执行
console.log("finally方法执行");
})
.then((res) => {
console.log(res); // 也会执行,相当于从第一个then方法返回的promise接收到之后再调用then方法
});
</script>
</body>
</html>
三. Promise对象的状态
Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态
。
异步操作未完成 (pending)
异步操作成功 (fulfilled)
异步操作失败 (rejected)
这三种的状态的变化途径只有两种。
从"未完成"到"成功"
从"未完成"到"失败"
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是”承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。
因此,Promise 的最终结果只有两种
。
异步操作成功,Promise 实例传回一个值(value),状态变为 fulfilled。
异步操作失败,Promise 实例抛出一个错误(error),状态变为 rejected。
四. Promise对象方法
Promise 是一个对象,也是一个构造函数。
1.Promise.resolve
将现有对象转为 Promise 对象
Promise.resolve('kerwin')
// 等价于
new Promise(resolve => resolve('kerwin'))
单页面缓存的使用案例
- 存入loaclStorage
- 存入window的全局变量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport"content="width=device-width, initial-scale=1.0"/>
<title>Promise封装ajax</title>
</head>
<body>
<button>清空缓存</button>
<script>
function ajax(url, method ="GET", body = null) {
var cache = ajax.cache || (ajax.cache = { data: null }); // 缓存数据挂在ajax函数上
if (cache.data) {
console.log("使用缓存数据");
return Promise.resolve(cache.data);
}
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open(method, url, true);
if (method ==="POST") {
xhr.setRequestHeader(
"Content-Type",
"application/json;charset=UTF-8"
);
}
xhr.send(body);
xhr.onload = function () {
if (/^2[0-9]{2}$/.test(xhr.status)) {
// 请求成功
console.log("不走缓存");
const responseData = JSON.parse(xhr.responseText);
ajax.cache.data = responseData; // 缓存数据
resolve(responseData);
} else {
reject(
new Error(
`请求失败,状态码: ${xhr.status}, 响应: ${xhr.responseText}`
)
);
}
};
xhr.onerror = function () {
reject(new Error("网络错误"));
};
});
}
// 第一次请求
ajax("https://jsonplaceholder.typicode.com/todos/122")
.then((res) => console.log("第一次请求结果:", res))
.catch((err) => console.log("错误:", err));
setTimeout(() => {
// 第二次请求
ajax("https://jsonplaceholder.typicode.com/todos/122")
.then((res) => console.log("第二次请求结果:", res))
.catch((err) => console.log("错误:", err));
}, 2000);
// 清空缓存的方法
function clearCache() {
ajax.cache = { data: null }; // 重置缓存
console.log("缓存已清空");
}
// 可以在需要的地方调用 clearCache() 函数来清除缓存
var clearBtn = document.querySelector("button");
clearBtn.onclick = clearCache;
</script>
</body>
</html>
2.Promise.reject
Promise.reject(reason)
方法也会返回一个新的
Promise 实例,该实例的状态为rejected
。
const p = Promise.reject('error');
// 等同于
const p = new Promise((resolve, reject) => reject('error'))
var p = Promise.reject("error");
console.log(p);
p.catch((res) => console.log(res));
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(null);
// reject("error");
}, 1000);
});
promise
.then((res) => {
if (res) {
console.log(res);
} else {
// console.log("数据为空");
// return Promise.reject("数据为空");
throw new Error("数据为空"); // 抛出错误 同样也可以
}
})
.catch((err) => console.log(err));
3.Promise.all
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
p的状态由p1,p2,p3 决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
4.Promise.race
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
应用-->超时机制
// 只要`p1`、`p2`、`p3`之中有一个实例率先改变状态,`p`的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给`p`的回调函数。
var p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p1");
}, 2000);
});
var p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p2");
}, 1000);
});
var p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3");
}, 3000);
});
const p = Promise.race([p1, p2, p3]);
p.then((value) => {
console.log(value);
});
function ajax(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.send();
xhr.onload = function () {
if (/^2[0-9]{2,2}/.test(xhr.status)) {
// 请求成功
resolve(JSON.parse(xhr.responseText));
} else {
reject(xhr.responseText);
}
};
});
}
// 通过race实现超时机制
var p1 = ajax("http://localhost:3000/news");
var p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("超时");
}, 2000);
});
Promise.race([p1, p2])
.then((res) => console.log(res))
.catch((err) => console.log("error:", err));
5.Promise.allSettled
Promise.allSettled()
方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled”,包含了”fulfilled”和”rejected”两种情况。
const promises = [ ajax('/200接口'), ajax('/401接口') ];
Promise.allSettled(promises).then(results=>{
// 过滤出成功的请求
results.filter(item =>item.status === 'fulfilled');
// 过滤出失败的请求
results.filter(item=> item.status === 'rejected');
})
6.Promise.any
只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是Promise.any()
不会因为某个 Promise 变成rejected
状态而结束,必须等到所有参数 Promise 变成rejected
状态才会结束。
// 我们想要对一组 Promise 进行并行处理,直到有一个 Promise 成功,或者全部 Promise 都失败。
// Promise.any 方法可以实现这个功能。
var p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("p1 success");
}, 1000);
});
var p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p2 success");
}, 2000);
});
var p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3 success");
}, 3000);
});
Promise.any([p1, p2, p3])
.then((res) => {
// 任意一个成功的 Promise 都会调用这个回调函数
// 进行登陆处理
console.log(res);
})
.catch((err) => {
// 全部失败的 Promise 都会调用这个回调函数
// 进行错误处理,例如要求注册账户
console.log(err);
});
Promise链式调用
// 创建一个新的 Promise 对象,用于异步请求新闻数据
var resultPromise = new Promise(function (resolve, reject) {
// 创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
// 打开一个 GET 请求到服务器,请求新闻数据
xhr.open("GET", `http://localhost:3000/news/?author=${name}`);
// 发送请求
xhr.send();
// 设置 onload 回调函数,当请求完成时被调用
xhr.onload = function () {
// 如果响应状态码是 2xx,表示请求成功
if (/^2\d{2}$/.test(xhr.status)) {
// 解析响应文本并使用 resolve 函数解决 Promise
resolve(JSON.parse(xhr.responseText));
} else {
// 如果状态码不是 2xx,使用 reject 函数拒绝 Promise
reject(new Error(xhr.statusText));
}
};
})
// 使用 then 方法处理新闻数据请求成功的结果
.then((data) => {
console.log("请求新闻成功", data[0]); // 输出新闻信息
// 返回一个新的 Promise 对象,用于请求与新闻相关的评论数据
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
// 打开一个 GET 请求到服务器,请求评论数据
xhr.open(
"GET",
`http://localhost:3000/comments/?newsID=${data[0].id}` // 使用新闻数据中第一条新闻的 ID
);
// 发送请求
xhr.send();
// 设置 onload 回调函数,当请求完成时被调用
xhr.onload = function () {
// 如果响应状态码是 2xx,表示请求成功
if (/^2\d{2}$/.test(xhr.status)) {
// 解析响应文本并使用 resolve 函数解决 Promise
resolve(JSON.parse(xhr.responseText));
} else {
// 如果状态码不是 2xx,使用 reject 函数拒绝 Promise
reject(new Error(xhr.statusText));
}
};
});
})
// 使用 then 方法处理评论数据请求成功的结果
.then((data) => {
console.log("请求评论成功", data);
return "我是请求评论成功的回调函数then";
}) // 打印请求评论成功的信息
// 使用 catch 方法捕获并处理整个链中任何一个 Promise 失败的情况
.catch((error) => console.error("请求评论失败", error))
// 使用 catch 方法捕获并处理新闻数据请求失败的情况
.catch((error) => console.log("请求新闻失败", error));
console.log(resultPromise);
resolve()函数的参数是传递给.then()方法里的, reject()同理
.then()方法return的值
,将会传递给链式调用中最近的下一个.then()方法
/* 封装xhr请求的函数 */
function ajax(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.send();
xhr.onload = function () {
if (/^2[0-9]{2,2}/.test(xhr.status)) {
// 请求成功
resolve(JSON.parse(xhr.responseText));
} else {
reject(xhr.responseText);
}
};
});
}
ajax(`http://localhost:3000/news/?author=张三`)
.then((data) => {
console.log(data[0].id);
return ajax(`http://localhost:3000/comments/?newsID=${data[0].id}`);
})
.then((comments) => {
console.log(comments);
});
五. 手写Promise
function KerwinPromise(executor) {
this.status ="pending";
this.result = undefined;
this.cb = []
var _this = this;
function resolve(res) {
if (_this.status !=="pending") return;
// console.log(_this)
_this.status ="fulfilled"
_this.result = res;
_this.cb.forEach(item => {
item.successCB && item.successCB(_this.result)
});
}
function reject(res) {
if (_this.status !=="pending") return;
// console.log("reject")
_this.status ="rejected"
_this.result = res;
_this.cb.forEach(item => {
item.failCB && item.failCB(_this.result)
});
}
executor(resolve, reject)
}
KerwinPromise.prototype.then = function (successCB, failCB) {
if(!successCB){
successCB = value=>value
}
if(!failCB){
failCB = error=>error
}
// successCB()
return new KerwinPromise((resolve, reject) => {
if (this.status ==="fulfilled") {
var result = successCB && successCB(this.result)
// console.log(result);
if (result instanceof KerwinPromise) {
result.then(res => {
// console.log(res)
resolve(res);
}, err => {
// console.log(err)
reject(err)
})
} else {
resolve(result);
}
}
if (this.status ==="rejected") {
var result = failCB && failCB(this.result)
if (result instanceof KerwinPromise) {
result.then(res => {
// console.log(res)
resolve(res);
}, err => {
// console.log(err)
reject(err)
})
} else {
reject(result);
}
}
if (this.status ==="pending") {
//收集回调
this.cb.push({
successCB: () => {
var result = successCB && successCB(this.result)
if (result instanceof KerwinPromise) {
result.then(res => {
// console.log(res)
resolve(res);
}, err => {
// console.log(err)
reject(err)
})
} else {
resolve(result);
}
},
failCB: () => {
var result = failCB && failCB(this.result)
if (result instanceof KerwinPromise) {
result.then(res => {
// console.log(res)
resolve(res);
}, err => {
// console.log(err)
reject(err)
})
} else {
reject(result);
}
}
})
}
})
}
KerwinPromise.prototype.catch= function(failCB){
this.then(undefined,failCB)
}
六. Async与Await
1.Async
async 函数,使得异步操作变得更加方便。
- 更好的语义。
- 返回值是 Promise。
async function test(){
}
test()
2.Await
await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function test(){
var res1 = await ajax("http://localhost:3000/news1")
var res2 = await ajax("http://localhost:3000/news2")
return res2
}
test().then(res=>{
console.log("返回结果",res)
}).catch(err=>{
console.log("err",err)
})
3.错误处理
try{
var res1 = await ajax("http://localhost:3000/news1")
var res2 = await ajax("http://localhost:3000/news2")
}catch(err){
console.log("err",err)
}