16. Promise
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数, 更合理和更强大
ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象
- 指定回调函数方式更灵活易懂
- 解决异步 回调地狱 的问题
16.1 回调地狱
- 当一个回调函数嵌套一个回调函数的时候
- 就会出现一个嵌套结构
- 当嵌套的多了就会出现回调地狱的情况
- 比如发送三个 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>回调地狱</title>
</head>
<body>
<script>
function ajax(url, success, failcb) {
setTimeout(() => {
success("11111")
}, 1000)
}
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)
}
})
}
})
}
})
</script>
</body>
</html>
回调地狱,其实就是回调函数嵌套过多导致的
当代码成为这个结构以后,已经没有维护的可能了
16.2 Promise使用
Promise 是一个对象,可以获取异步操作的消息
语法:
new Promise(function (resolve, reject) {
// resolve 表示成功的回调
// reject 表示失败的回调
}).then(function (res) {
// 成功的函数
}).catch(function (err) {
// 失败的函数
})
<!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>
<script>
// promise
// 假设异步处理
// 成功时执行的函数 resolve
// 失败时执行的函数 reject
let pro = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
// 假设成功
resolve()
}, 1000)
})
pro.then(() => {
console.log("奖金");
}, () => {
console.log("没有");
})
let pro1 = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
// 假设失败
reject()
}, 1000)
})
pro1.then(() => {
console.log("奖金");
}, () => {
console.log("没有");
})
let pro2 = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
// 假设成功,设置参数
// resolve(1000)
// 假设失败,设置参数
reject("1111")
}, 1000)
})
pro2.then((res) => {
console.log("奖金",res);
}).catch((err) => {
console.log("没有", err);
})
</script>
</body>
</html>
16.3 Promise 对象的状态
Promise 对象通过自身的状态,来控制异步操作
-
Promise 实例具有三种状态
- 异步操作未完成(pending)
- 异步操作成功(fulfilled)
- 异步操作失败(rejected)
-
这三种的状态的变化途径只有两种
- 从“未完成”到“成功”
- 从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化
这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了
这也意味着,Promise 实例的状态变化只可能发生一次
- 因此,Promise 的最终结果只有两种
- 异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled
- 异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected
<!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>
<script>
let pro = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
// 两个函数放进同时执行
// 假设成功,设置参数
resolve(1000)
// 假设失败,设置参数
reject("1111")
}, 1000)
})
pro.then((res) => {
console.log("奖金", res);
}).catch((err) => {
console.log("没有", err);
})
</script>
</body>
</html>
16.4 Promise的链式调用
发送一个请求的处理方式
<!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>Document</title>
</head>
<body>
<script>
function ajax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open("get", url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// resolve(xhr.responseText)
// JSON 字符形式
resolve(JSON.parse(xhr.responseText))
} else {
reject(xhr.responseText)
}
}
}
})
}
ajax("65.json").then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
// 传入错误的 1.json
ajax("1.json").then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
</script>
</body>
</html>
连续发送两个请求的处理方式
<!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>
<script>
let pro = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
// 假设成功
resolve(1000)
// reject("no 1111")
}, 1000)
})
pro.then((res) => {
console.log("奖金1", res);
// 如果 return 非 promise 类型,将是 pending 到 fulfilled 的状态
//如果return promise类型,根据这个新的promise对象的结果,
// 决定 pending 到 fulfilled 的状态还是 pending 到 rejected 的状态
}).then((res) => {
console.log("奖金2", res);
}).catch((err) => {
console.log("没有", err);
})
// 打印出来的是 奖金1 1000 奖金2 undefined
// 为什么不是打印 奖金1 1000 就冻结了状态
// 因为 第一个执行完返回的时没具体写返回什么,所以是 undefined,
// 然后再次执行 then 和 catch语句
let pro1 = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
// 假设成功
resolve(1000)
// reject("no 1111")
}, 1000)
})
pro1.then((res) => {
console.log("奖金3", res);
return res
// 如果 return 非 promise 类型,将是 pending 到 fulfilled 的状态
//如果return promise类型,根据这个新的promise对象的结果,
// 决定 pending 到 fulfilled 的状态还是 pending 到 rejected 的状态
}).then((res) => {
console.log("奖金4", res);
}).catch((err) => {
console.log("没有", err);
})
</script>
</body>
</html>
Promise的链式调用
<!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>
<script>
function ajax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open("get", url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// resolve(xhr.responseText)
// JSON 字符形式
resolve(JSON.parse(xhr.responseText))
} else {
reject(xhr.responseText)
}
}
}
})
}
ajax("65.json").then(res => {
console.log(res);
return ajax("1.joson")
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
</script>
</body>
</html>
16.5 Promise.all
Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.all([p1, p2, p3]);
- p 的状态由p1,p2,p3 决定,分成两种情况
- 只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数 - 只要
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数
- 只有
<!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.all</title>
</head>
<body>
<script>
let pro = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
resolve(1000)
}, 1000)
})
let pro1 = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
resolve(2000)
}, 1000)
})
let pro2 = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
resolve(3000)
}, 1000)
})
// showloading
Promise.all([pro, pro1, pro2]).then(res => {
// hideloading
console.log(res);
}).catch(err => {
console.log(err);
})
</script>
</body>
</html>
16.6 Promise.race
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变
那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数
<!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.race</title>
</head>
<body>
<script>
let pro = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
resolve(1000)
}, 1000)
})
let pro1 = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
resolve(2000)
}, 2000)
})
let pro2 = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
resolve(3000)
}, 3000)
})
Promise.race([pro, pro1, pro2]).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
// 运行结果:1000
// 因为第一个先完成操作,改变了状态,
// 就直接返回第一个完成的 promise 的返回值
</script>
</body>
</html>
超时,可以用于当服务器挂掉或者超时了应做的处理
<!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.race</title>
</head>
<body>
<script>
let pro = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
resolve("成功的结果")
}, 30000)
})
let pro1 = new Promise(function (resolve, reject) {
// 执行器函数
setTimeout(() => {
reject(2000)
}, 2000)
})
Promise.race([pro, pro1]).then(res => {
console.log(res);
}).catch(err => {
console.log(err, "超时了");
})
// 运行结果:2000 '超时了'
// 因为第一个响应时间过长,执行了第二个
// 就直接返回第二个的 的 reject 的返回值
</script>
</body>
</html>
GitHub代码
gitee代码