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)
}






