1.首先需要知道什么是回调函数
我们在写代码时可能会碰到一些异步代码,而这些异步代码中,往往会有伴随着回调函数,就比如:
setTimeout(function(){
console.log(1)
},3000)
这就是一个异步代码,相反的就是同步代码,同步代码会在主线程上运行,只有前面的任务运行完后才会执行后面的任务,异步代码会进入异步队列,这里一般会先执行同步代码,在同步代码执行完后,在从异步队列中抓取异步任务执行。上面的匿名函数就是一个回调函数,回调函数不会立即执行,会在满足一定条件下,才会执行。
2.了解回调地狱的造成原因
而我们有时不得会处理一些回调函数嵌套问题,就比如:
setTimeout(function () { //第一层
console.log('1');
setTimeout(function () { //第二程
console.log('2');
setTimeout(function () { //第三层
console.log('3');
}, 1000)
}, 2000)
}, 3000)
我们想要这样的代码正常按顺序运行(按顺序输出1,2,3),往往会采取这种回调函数嵌套的形式,但是这种会造成一种问题,就是回调地狱。这会造成代码可读性差,牵一动全身的问题。
1.在Ajax中也会遇到这样的问题
myAxios({
url: 'http://hmajax.itheima.net/api/province',
}).then((result) => {
const pname = result.list[0]
document.querySelector('.province').innerHTML = pname
myAxios({
url: 'http://hmajax.itheima.net/api/city',
params: {
pname: pname
}
}).then((result) => {
const cname = result.list[0]
document.querySelector('.city').innerHTML = cname
myAxios({
url: 'http://hmajax.itheima.net/api/area1',
params: {
pname,
cname
}
}).then((result) => {
document.querySelector('.area').innerHTML = result.list[0]
})
})
}).catch((error) => {
console.dir(error)
}) myAxios({
url: 'http://hmajax.itheima.net/api/province',
}).then((result) => {
const pname = result.list[0]
document.querySelector('.province').innerHTML = pname
myAxios({
url: 'http://hmajax.itheima.net/api/city',
params: {
pname: pname
}
}).then((result) => {
const cname = result.list[0]
document.querySelector('.city').innerHTML = cname
myAxios({
url: 'http://hmajax.itheima.net/api/area1',
params: {
pname,
cname
}
}).then((result) => {
document.querySelector('.area').innerHTML = result.list[0]
})
})
}).catch((error) => {
console.dir(error)
})
就比如这段代码(这里的myAxios模仿axios的,基本原理一样),是想要获取网址里的数据,并将其打印到页面上,但是发现第一个获取省名的参数需要传到下面的代码中,相同的第二个获取城市名的参数也需要传参到下面的代码中,这样不断叠加,就会造成回调地狱的问题。
从里面异步代码中抛出的问题,最外层无法接收到,而内层的代码也是耦合度非常高。
3.使用链式调用法解决回调地狱
1.首先我们需要了解Promise的原理,Promise本身分为三种状态,pending,resolved,rejected三种状态,这三种状态不会从一种状态转化到另一种状态
2.了解then属性的原理,then是Promise的一个属性,其中有两个参数一个是resolved,另一个是rejected,但Promise对象从pending状态状态转换到resolved是会调用函数1,若是转换到rejected则会调用函数2
p.then(函数1[,函数2])
3.then的返回值,then()方法的返回值也是一个promise对象,所以它支持链式写法。但是要注意的是它的返回值是一个新的promise对象,与调用then方法的并不是同一个对象。
4.换句话说,then()会封装一个全新的promise对象p2。那既然 p2也是一个promise对象,那么,p2的状态(promiseStatus)和值(promiseValue)分别是什么?规则如下:
如果p1的状态是pending,则p2的状态也是pending。
•如果p1的状态是resolved,then()会去执行f_ok,则p2的状态由f_ok的返回值决定。
•如果f_ok返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_ok函数的return值。•如果f_ok返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。•如果f_ok这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。
如果p1的状态是rejected,then()会去执行f_err,则p2的状态由f_err的返回值决定。
•如果f_err返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_err函数的return值。•如果f_err返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
•如果f_err这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。
5.这样我们就可以利用then这种返回值所创建的新的Promise对象,实现链式调用
let pname = ''
myAxios({
url: 'http://hmajax.itheima.net/api/province'
}).then((result) => {
// console.log(result.list[0])
pname = result.list[0]
document.querySelector('.province').innerHTML = pname
return myAxios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
}).then((result) => {
// console.log(result.list[0])
const cname = result.list[0]
document.querySelector('.city').innerHTML = cname
return myAxios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })
}).then((result) => {
// console.log(result)
document.querySelector('.area').innerHTML = result.list[0]
})
这种不断利用then创建新的Promise对象,实现了Promise的链式调用,从而解决了回调地狱。