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// 失败时执行的函数 rejectlet 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.jsonajax("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)})// showloadingPromise.all([pro, pro1, pro2]).then(res => {// hideloadingconsole.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代码