同步与异步的概念
- 同步代码:逐行执行,需原地等待结果后,才继续向下执行
- 异步代码:调用后耗时,不阻塞代码继续执行(不必原地等待),在将来完成后触发回调函数传递结果
- 回答代码打印顺序:发现异步代码接收结果,使用的都是回调函数
1 2 3 4 5 6 7 8 9 10
| const result = 0 + 1 console.log(result) setTimeout(() => { console.log(2) }, 2000) document.querySelector('.btn').addEventListener('click', () => { console.log(3) }) document.body.style.backgroundColor = 'pink' console.log(4)
|
结果:1, 4, 2
按钮点击一次打印一次 3
回调地狱
概念
- 需求:展示默认第一个省,第一个城市,第一个地区在下拉菜单中

- 概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
- 缺点:可读性差,异常无法捕获,耦合性严重,牵一发动全身
1 2 3 4 5 6 7 8 9 10 11 12 13
| axios({ url: 'http://hmajax.itheima.net/api/province' }).then(result => { const pname = result.data.list[0] document.querySelector('.province').innerHTML = pname axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } }).then(result => { const cname = result.data.list[0] document.querySelector('.city').innerHTML = cname axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } }).then(result => { document.querySelector('.area').innerHTML = result.data.list[0] }) }) })
|
Promise链式调用
- 概念:依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束
- 细节:then() 回调函数中的返回值,会影响新生成的 Promise 对象最终状态和结果
- 好处:通过链式调用,解决回调函数嵌套问题

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
const p = new Promise((resolve, reject) => { setTimeout(() => { resolve('北京市') }, 2000) })
const p2 = p.then(result => { console.log(result) return new Promise((resolve, reject) => { setTimeout(() => { resolve(result + '--- 北京') }, 2000) }) })
p2.then(result => { console.log(result) })
console.log(p2 === p)
|
使用promise解决回调地狱
- 目标:使用 Promise 链式调用,解决回调函数地狱问题
- 做法:每个 Promise 对象中管理一个异步任务,用 then 返回 Promise 对象,串联起来

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
let pname = ''
axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => { pname = result.data.list[0] document.querySelector('.province').innerHTML = pname return axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }}) }).then(result => { const cname = result.data.list[0] document.querySelector('.city').innerHTML = cname return axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }}) }).then(result => { console.log(result) const areaName = result.data.list[0] document.querySelector('.area').innerHTML = areaName })
|
事实上,promise是callback风格的一个语法糖,它通过实现链式调用的方式来将回调函数的嵌套扁平化来达到解决回调地狱的目的。
往回调函数里按照成功和失败的类别分别传入两个回调函数,就可以分别处理成功和失败的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function foo(handleSucceed, handleFailed) { if(isMistake) { return handleFailed(new Error('It`s a mistake')); } else { return handleSucceed('You got it!'); } }
foo(function (result) { console.log(result); }, function (error) { console.error(error); });
|
与promise实现的版本进行对比:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const pms = new Promise((resolve, reject) => { if(isMistake) { return reject(new Error('It`s a mistake')); } else { return resolve('You got it!'); } });
pms.then(result => { console.log(result); }).catch(error => { console.error(error); });
|
会发现实质上,这里的resolve和reject正是两个回调函数,就如同前面一个例子里面的handleSucceed和handleFailed一样。而这两个回调函数的传入方式,从上一个例子的直接两个参数传入,变成了通过then方法和catch方法来进行传入。
相比而言,Promise的方式更加语义化,更容易理解——给主流程留下一个承诺,在之后可以通过该承诺获得子流程的执行结果。
async和await函数解决回调地狱
- 概念:在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值。
- async关键字作为一个关键字放到申明函数前面,表示该函数为要给异步任务,不会阻塞后面函数的执行。
async函数其实是一个返回值为Promise对象的函数。或者更准确地说:async关键字声明的函数被调用时,实质上是创建了一个Promise对象。
await则表示:执行后面的表达式——不论是异步还是同步的表达式——并获取到resolve的结果,如果执行出错,则抛出到外层async函数的catch回调中。await同时作为Promise中的resolve和reject工作了。
- 做法:使用 async 和 await 解决回调地狱问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
async function getData() { const pObj = await axios({url: 'http://hmajax.itheima.net/api/province'}) const pname = pObj.data.list[0] const cObj = await axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }}) const cname = cObj.data.list[0] const aObj = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }}) const areaName = aObj.data.list[0]
document.querySelector('.province').innerHTML = pname document.querySelector('.city').innerHTML = cname document.querySelector('.area').innerHTML = areaName }
getData()
|
async 函数和 await 捕获错误
try 和 catch 的作用:语句标记要尝试的语句块,并指定一个出现异常时抛出的响应
1 2 3 4 5 6 7
| try { } catch (error) { }
|
尝试把代码中 url 地址写错,观察 try catch 的捕获错误信息能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
async function getData() { try { const pObj = await axios({ url: 'http://hmajax.itheima.net/api/province' }) const pname = pObj.data.list[0] const cObj = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } }) const cname = cObj.data.list[0] const aObj = await axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } }) const areaName = aObj.data.list[0]
document.querySelector('.province').innerHTML = pname document.querySelector('.city').innerHTML = cname document.querySelector('.area').innerHTML = areaName } catch (error) { console.dir(error) } }
getData()
|
async函数其实是Promise的语法糖。并且它通过形似同步编程的形式,达成了前述Promise的链式调用,并以此解决回调地狱的问题。