前言

主要记录从XMLHttpRequestPromiseaxios的技术转变,也包括回调函数地狱问题和解决方法。

本篇文章会利用node.js快速搭建一个express服务器,用于编写样例所需要的API接口,方便测试及展示。

安装express的命令:npm install express --save


XMLHttpRequest

介绍XMLHttpRequest(XHR)之前首先简单介绍一下AJAX是什么:

AJAX是“Asynchronous JavaScript And XML” 的缩写,即“异步的JavaScript和XML”。是一种实现无页面刷新获取服务器数据的技术。

XML是可扩展标记语言,是一种特征类似HTML,用于标记电子文件使其具有结构性的标记语言,它是用来承载数据的。

而目前使用最多的JSON,它是仅仅一种数据格式,在JSON没有成为主流传输数据格式之前,人们大量使用XML作为数据传输的载体。

AJAX技术的核心就是XHR:

**XMLHttpRequest(XHR)**对象用于与服务器交互。通过XMLHttpRequest可以在不刷新页面的情况下请求特定的URL并获取数据。这允许网页在不影响用户操作的情况下更新页面的局部内容。

基本使用

XHR的使用语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
// 1.创建XMLHttpRequest对象
const xhr = new XMLHttpRequest()
// 2.利用open()方法配置请求方式和请求url地址
xhr.open('请求方式(大小写均可)', '请求url地址')
// 3.给XHR对象绑定一个loadend事件(监听该事件),并接收响应结果
xhr.addEventListener('loadend', () => {
// 响应结果
console.log(xhr.response)
})
// 4.发起请求
xhr.send()
</script>

另外有种写法如下:

1
2
3
4
5
6
7
8
9
10
11
<script>
const xhr = new XMLHttpRequest()
xhr.open('请求方式(大小写均可)', '请求url地址')
// 利用XHR对象的onreadystatechange属性(通常为匿名函数)监听XHR对象的状态改变
xhr.onreadystatechange = function() {
if((xhr.readyState==4) && (xhr.status==200)){
console.log(xhr.responseText)
}
}
xhr.send()
</script>

(出于简洁,使用第一种方式:直接利用addEventListener绑定监听事件,一旦监听到Loaded状态(即「请求/响应」动作完成),就直接响应结果。)

XHR对象继承的部分属性如下:

  • response/responseText:包含响应主体返回的文本。

    response‌:该属性可以获取任意格式的响应数据,包括文本、JSON、XML等。(例:当设置了responseTypejson时,可以直接通过response属性获取解析好的JSON对象,而不需要手动解析‌)

    responseText:该属性用于获取文本格式的响应数据。

  • readyState:表示在「请求/响应」过程中当前的活动阶段。(这个属性的值从 0 开始,直到接收到完整的 HTTP 响应,该值增加到 4)

    状态 名称 描述
    0 Uninitialized 初始化状态。XHR对象已创建或已被abort()方法重置。
    1 Open open()方法已调用,但是send()方法未调用。请求还没有被发送。
    2 Sent send()方法已调用,HTTP请求已发送到Web服务器。未接收到响应。
    3 Receiving 所有响应头部都已经接收到。响应体开始接收但未完成。
    4 Loaded HTTP响应已经完全接收。

    readyState的值不会递减,除非当一个请求在处理过程中的时候调用了abort()open()方法,每次这个属性的值增加的时候,都会触发onreadystatechange事件句柄。

  • status:响应的HTTP状态(2xx - 5xx)

  • statusText:HTTP 服务器返回的响应状态,与status不同的是,它包含完整的响应状态文本(例如,“200 OK”)

XHR对象继承的方法如下:

  • open():用于准备启动一个AJAX请求
  • setRequestHeader():用于设置请求头部信息
  • send():用于发送AJAX请求
  • abort():用于取消异步请求

使用案例

发送GET请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest()
xhr.open('get', 'http://localhost:11000')
xhr.addEventListener('loadend', () => {
console.log(xhr.response)
})
xhr.send()
</script>
</body>
</html>

服务端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let express = require('express')
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})

app.get('/', (req, res) => {
res.json({
msg: '收到get请求',
})
})

app.listen(11000, () => {
console.log('http://localhost:11000')
})

服务端输出结果:

1
http://localhost:11000

携带参数

GET请求

需要手动将参数编写到访问的url中。

案例:GET请求携带两个参数,分别为nameage

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 1.查询参数字符串
const queryObj = {
name: '花猪',
age: 18
}
// 2.创建 URLSearchParams 对象 (可以和1合并)
const paramsObj = new URLSearchParams(queryObj);
// 3.生成指定格式查询参数字符串
const queryString = paramsObj.toString() // name=花猪&age=18

const xhr = new XMLHttpRequest()
xhr.open('get', `http://localhost:11000?${queryString}`)
xhr.addEventListener('loadend', () => {
console.log(xhr.response)
})
xhr.send()
</script>
</body>
</html>

可以通过创建URLSearchParams对象并调用toString()方法得到查询参数字符串(格式:参数名1=值1&参数名2=值2&...),再通过字符串模板的写法将查询参数串添加到url中。

也可以直接手动添加参数到url中:http://localhost:11000?name=花猪&age=18

服务端代码如下:

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
let express = require('express')
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})

app.get('/', (req, res) => {
const queryParams = req.query;
const name = req.query.name;
const age = req.query.age;
res.json({
msg: `收到get请求,其中 name = ${name},age = ${age}`,
})
console.log(`name = ${name}`)
console.log(`age = ${age}`)
})

app.listen(11000, () => {
console.log('http://localhost:11000')
})

服务端输出结果:

1
2
3
http://localhost:11000
name = 花猪
age = 18

POST请求

案例:POST请求携带两个参数,分别为usernamepassword

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest()
xhr.open('post', 'http://localhost:11000')
xhr.addEventListener('loadend', () => {
console.log(xhr.response)
})

// 1. 告诉服务器,传递的内容类型,是 JSON 字符串
xhr.setRequestHeader('Content-Type', 'application/json')
// 2. 准备数据并转成 JSON 字符串
const params = {
username: 'HuaZhu',
password: '123456'
}
const paramsStr = JSON.stringify(params)
// 3. 发送请求体数据
xhr.send(paramsStr)
</script>
</body>
</html>

首先利用setRequestHeader()设置请求头内容,告知服务器传递的内容为JSON字符串,接下来就利用send()方法将构建好的参数(JSON字符串)发送至服务器。

服务端代码如下:

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
30
31
let express = require('express')
let bodyParser = require('body-parser'); // 1.引入body-parser
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})

// 2.使用body-parser中间件解析JSON类型的请求体
app.use(bodyParser.json());

app.post('/', (req, res) => {
// 3.通过req.body访问POST请求的参数
const postData = req.body;
res.json({
msg: `收到post请求,其中 username = ${postData.username},password = ${postData.password}`,
})
console.log(`username = ${postData.username}`)
console.log(`password = ${postData.password}`)
})
app.listen(11000, () => {
console.log('http://localhost:11000')
})

注:express要想接收POST请求参数,可以使用内置的body-parser中间件来解析POST请求的参数,首先需要安装:

npm install body-parser --save

1
2
3
4
5
6
// 1.引入body-parser
let bodyParser = require('body-parser');
// 2.使用body-parser中间件解析JSON类型的请求体
app.use(bodyParser.json());
// 3.通过req.body访问POST请求的参数
const postData = req.body;

服务端输出结果:

1
2
3
http://localhost:11000
username = HuaZhu
password = 123456

Promise

Promise对象用于表示(管理)一个异步操作的最终状态(成功或失败)及其结果值。

优势:

  • 使处理异步操作的逻辑更清晰(处理成功或失败会关联后续的处理函数)
  • 可以解决回调函数地狱问题

基本使用

Promise的使用语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
// 1. 创建 Promise 对象
const p = new Promise((resolve, reject) => {
// 2. 执行异步任务-并传递结果
// 成功调用: resolve(值) 触发 then() 执行
// 失败调用: reject(值) 触发 catch() 执行
})
// 3. 接收结果
p.then(result => {
// 成功
}).catch(error => {
// 失败
})
</script>

Promise的三种状态

一个Promise对象必然处于以下三种状态之一:

  • pending(待定):初始状态,既没有被兑现,也没有被拒绝。(一旦Promise对象被创建就进入此状态)
  • fulfilled(已兑现):操作成功完成。如果操作成功,就会触发resolve()函数,接着触发调用then()函数。
  • rejected(已拒绝):操作失败。如果操作失败,就会触发reject()函数,接着触发调用catch()函数。

使用示例

结合promise和XHR实现异步GET请求访问:

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
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 创建Promise对象
const p = new Promise((resolve, reject) => {
// 创建XMLHttpRequest对象
const xhr = new XMLHttpRequest()
xhr.open('get', 'http://localhost:11000')
xhr.addEventListener('loadend', () => {
// 判断成功响应
if(xhr.status >= 200 && xhr.status < 300) {
// 触发resolve()函数,接着触发then()执行
resolve(xhr.response)
}
// 判断失败响应
else {
// 触发reject()函数,接着触发catch()执行
reject(new Error('请求失败'))
}
})
xhr.send()
})

p.then(result => {
console.log(result)
}).catch(error => {
// 错误对象要用console.dir详细打印
console.dir(error)
})
</script>
</body>
</html>
  • 访问:http://localhost:11000响应成功:

  • 访问http://localhost:11001响应失败:

服务端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let express = require('express')
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})

app.get('/', (req, res) => {
res.json({
msg: '收到get请求',
})
})

app.listen(11000, () => {
console.log('http://localhost:11000')
})

封装Promise和XHR实现异步请求

为了更好的复用Promise+XHR实现异步请求,我们可以将其封装为一个函数(myAxios),如果有异步请求需要就去调用该函数,其整体结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
// 构建myAxios函数,可以传入配置文件对象
function myAxios(config) {
// 返回Promise对象
return new Promise((resolve, reject) => {
// XHR 请求
// 调用成功/失败的处理程序
})
}

// 调用myAxios函数,并传入配置参数
myAxios({
// url:
// method:
// params:
// ...
}).then(result => {
// 成功
}).catch(error => {
// 失败
})
</script>

使用案例

服务端代码如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
let express = require('express')
const bodyParser = require('body-parser');
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})
// 处理GET请求
app.get('/', (req, res) => {
const queryParams = req.query;
const name = req.query.name;
const age = req.query.age;
res.json({
msg: `收到get请求,其中 name = ${name},age = ${age}`,
})
console.log(`name = ${name}`)
console.log(`age = ${age}`)
})


// 使用body-parser中间件解析JSON类型的请求体
app.use(bodyParser.json());
// 处理POST请求
app.post('/', (req, res) => {
// 通过req.body访问POST请求的参数
const postData = req.body;
res.json({
msg: `收到post请求,其中 username = ${postData.username},password = ${postData.password}`,
})
console.log(`username = ${postData.username}`)
console.log(`password = ${postData.password}`)
})

app.listen(11000, () => {
console.log('http://localhost:11000')
})

GET请求

GET请求携带两个参数,分别为nameage

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数,可以传入配置文件对象
function myAxios(config) {
// 返回Promise对象
return new Promise((resolve, reject) => {
// 创建XHR对象
const xhr = new XMLHttpRequest()
// 如果有查询参数,就将参数拼接到url中
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
// 指定请求方式(默认GET请求)和url
xhr.open(config.method || 'GET', config.url)
// 添加监听事件,如果XHR对象状态变为'loadend',就处理
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
// 如果响应成功,调用resolve()函数,并触发then()函数
resolve(JSON.parse(xhr.response))
} else {
// 如果响应失败,调用reject()函数,并触发catch()函数
reject(new Error(xhr.response))
}
})
// 如果有data选项,就携带请求体发送请求
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
// 如果没有data选项,就直接发送请求
else {
xhr.send()
}
})
}

// 调用myAxios函数,并传入配置参数
myAxios({
url: 'http://localhost:11000',
method: 'GET',
params: {
name: '花猪',
age: 18
}
}).then(result => {
console.log(result)
}).catch(error => {
console.dir(error)
})
</script>
</body>
</html>
  • 访问:http://localhost:11000响应成功:

    服务端输出结果:

    1
    2
    3
    http://localhost:11000
    name = 花猪
    age = 18
  • 访问:http://localhost:11001响应失败:

POST请求

POST请求携带两个参数,分别为usernamepassword

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数,可以传入配置文件对象
function myAxios(config) {
// 返回Promise对象
return new Promise((resolve, reject) => {
// 创建XHR对象
const xhr = new XMLHttpRequest()
// 如果有查询参数,就将参数拼接到url中
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
// 指定请求方式(默认GET请求)和url
xhr.open(config.method || 'GET', config.url)
// 添加监听事件,如果XHR对象状态变为'loadend',就处理
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
// 如果响应成功,调用resolve()函数,并触发then()函数
resolve(JSON.parse(xhr.response))
} else {
// 如果响应失败,调用reject()函数,并触发catch()函数
reject(new Error(xhr.response))
}
})
// 如果有data选项,就携带请求体发送请求
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
// 如果没有data选项,就直接发送请求
else {
xhr.send()
}
})
}

// 调用myAxios函数,并传入配置参数
myAxios({
url: 'http://localhost:11000',
method: 'POST',
data: {
username: 'HuaZhu',
password: 123456
}
}).then(result => {
console.log(result)
}).catch(error => {
console.dir(error)
})
</script>
</body>
</html>
  • 访问:http://localhost:11000响应成功:

    服务端输出结果:

    1
    2
    3
    http://localhost:11000
    username = HuaZhu
    password = 123456
  • 访问:http://localhost:11001响应失败:

异步代码

关于异步代码:

  • 异步代码调用后耗时,不阻塞代码继续执行(不必原地等待),在将来完成后触发一个回调函数用来接收结果。
  • JavaScript中的异步代码:setTimeout() / setInterval(),事件,AJAX

本小节的案例是依次发出以下请求:请求到四川省、成都市、武侯区的信息。

  1. 访问http://localhost:11000/province请求全国省份列表数据,并拿到四川省信息
  2. 携带参数“四川省”访问http://localhost:11000/city请求四川省城市列表数据,并拿到成都市信息
  3. 携带参数“成都市”访问http://localhost:11000/area请求成都市地区县列表数据,并拿到武侯区信息

本小节服务端代码如下,只是针对案例做了简单的数据返回:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
let express = require('express')
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})

// 定义全国省份列表数据
const province = {
message: '获取省份成功',
list: ["北京","天津","河北省","山西省","内蒙古自治区","辽宁省","吉林省","黑龙江省","上海","江苏省","浙江省","安徽省","福建省","江西省","山东省","河南省","湖北省","湖南省","广东省","广西壮族自治区","海南省","重庆","四川省","贵州省","云南省","西藏自治区","陕西省","甘肃省","青海省","宁夏回族自治区","新疆维吾尔自治区","台湾","香港特别行政区","澳门特别行政区"]
}
// 定义四川省城市列表数据
const city = {
message: '获取城市成功',
list: ["成都市","自贡市","攀枝花市","泸州市","德阳市","绵阳市","广元市","遂宁市","内江市","乐山市","南充市","眉山市","宜宾市","广安市","达州市","雅安市","巴中市","资阳市","阿坝藏族羌族自治州","甘孜藏族自治州","凉山彝族自治州"]
}
// 定义成都市地区县列表数据
const area = {
message: '获取地区县成功',
list: ["锦江区","青羊区","金牛区","武侯区","成华区","龙泉驿区","青白江区","新都区","温江区","金堂县","双流县","郫县","大邑县","蒲江县","新津县","都江堰市","彭州市","邛崃市","崇州市"]
}

// GET请求(无参),返回全国省份列表数据
app.get('/province', (req, res) => {
res.json({
message: province.message,
list: province.list
})
console.log(province.message)
console.log(province.list)
})

// GET请求(含参)
// 如果没传参数或参数不为'四川省',则返回错误信息
// 如果参数为'四川省',返回四川省城市列表数据
app.get('/city', (req, res) => {
if(req.query === undefined || req.query.province != '四川省') {
res.status(400).send('参数错误')
console.log('获取城市失败')
} else {
res.json({
message: city.message,
list: city.list
})
console.log(city.message)
console.log(city.list)
}
})

// GET请求(含参)
// 如果没传参数或参数不为'成都市',则返回错误信息
// 如果参数为'成都市',返回成都市地区县列表数据
app.get('/area', (req, res) => {
if(req.query === undefined || req.query.city != '成都市') {
res.status(400).send('参数错误')
console.log('获取地区县失败')
} else {
res.json({
message: area.message,
list: area.list
})
console.log(area.message)
console.log(area.list)
}
})

app.listen(11000, () => {
console.log('http://localhost:11000')
})

回调函数地狱

关于回调函数地狱:在回调函数中一直向下嵌套新的回调函数,会形成回调函数地狱,会存在如:可读性差异常捕获困难高耦合的问题。

如下例所示,依次请求到四川省、成都市、武侯区的信息:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
else {
xhr.send()
}
})
}

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 调用myAxios请求获取省份信息
myAxios({
url: 'http://localhost:11000/province',
method: 'GET',
}).then(result => {
Province = result.list[22] // 获取四川省
console.log(result)
console.log(`获取到省份:${Province}`)
// 在then()内部再次调用myAxios请求获取城市信息
myAxios({
url: 'http://localhost:11000/city',
method: 'GET',
params: {
province: Province
}
}).then(result => {
City = result.list[0] // 获取成都市
console.log(result)
console.log(`获取到城市:${City}`)
// 在then()内部再次调用myAxios请求获取地区县信息
myAxios({
url: 'http://localhost:11000/area',
method: 'GET',
params: {
city: City
}
}).then(result => {
Area = result.list[3] // 获取武侯区
console.log(result)
console.log(`获取到地区县:${Area}`)
})
})
})
</script>
</body>
</html>

访问成功,控制台输出以下信息:

这样遵循访问顺序而嵌套调用myAxios()函数的代码耦合性太高,而且最关键的是没有办法正确捕获异常:

案例:假设在最外层链接一个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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
else {
xhr.send()
}
})
}

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 调用myAxios请求获取省份信息
myAxios({
url: 'http://localhost:11000/province',
method: 'GET',
}).then(result => {
Province = result.list[22] // 获取四川省
console.log(result)
console.log(`获取到省份:${Province}`)
// 在then()内部再次调用myAxios请求获取城市信息
myAxios({
url: 'http://localhost:11000/city',
method: 'GET',
// 不携带请求参数
// params: {
// province: Province
// }
}).then(result => {
City = result.list[0] // 获取成都市
console.log(result)
console.log(`获取到城市:${City}`)
// 在then()内部再次调用myAxios请求获取地区县信息
myAxios({
url: 'http://localhost:11000/area',
method: 'GET',
// 不携带请求参数
// params: {
// city: City
// }
}).then(result => {
Area = result.list[3] // 获取武侯区
console.log(result)
console.log(`获取到地区县:${Area}`)
})
})
}).catch(error => { // catch()用于捕获错误
console.dir(error)
console.log('请求发生错误')
})
</script>
</body>
</html>

控制台输出以下信息:

可以看到,控制台只是抛出了访问城市的myAxios()函数内部的错误,而最外层的catch()函数并没有成功捕获到错误信息。这就是回调函数地狱带来的问题,一旦在回调函数嵌套中的某个环节出现了问题,没有办法及时捕捉到问题究竟出现在哪里。

Promise的链式调用

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
else {
xhr.send()
}
})
}

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 1.得到-获取省份Promise对象
myAxios({
url: 'http://localhost:11000/province',
method: 'GET',
}).then(result => {
Province = result.list[22] // 获取四川省
console.log(result)
console.log(`获取到省份:${Province}`)
// 2.得到-获取得到-获取城市Promise对象
return myAxios({
url: 'http://localhost:11000/city',
method: 'GET',
params: {
province: Province
}
})
}).then(result => {
City = result.list[0] // 获取成都市
console.log(result)
console.log(`获取到城市:${City}`)
// 3.得到-获取地区县Promise对象
return myAxios({
url: 'http://localhost:11000/area',
method: 'GET',
params: {
city: City
}
})
}).then(result => {
Area = result.list[3] // 获取武侯区
console.log(result)
console.log(`获取到地区县:${Area}`)
}).catch(error => { // 捕获错误
console.dir(error)
console.log('请求发生错误')
})
</script>
</body>
</html>

访问成功,控制台输出以下信息:

一旦其中某一个访问出现错误,利用最后的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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
else {
xhr.send()
}
})
}

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 1.得到-获取省份Promise对象
myAxios({
url: 'http://localhost:11000/province',
method: 'GET',
}).then(result => {
Province = result.list[22] // 获取四川省
console.log(result)
console.log(`获取到省份:${Province}`)
// 2.得到-获取城市Promise对象
return myAxios({
url: 'http://localhost:11000/city',
method: 'GET',
// params: {
// province: Province
// }
})
}).then(result => {
City = result.list[0] // 获取成都市
console.log(result)
console.log(`获取到城市:${City}`)
// 3.得到-获取地区县Promise对象
return myAxios({
url: 'http://localhost:11000/area',
method: 'GET',
// params: {
// city: City
// }
})
}).then(result => {
Area = result.list[3] // 获取武侯区
console.log(result)
console.log(`获取到地区县:${Area}`)
}).catch(error => { // 捕获错误
console.dir(error)
console.log('请求发生错误')
})
</script>
</body>
</html>

控制台输出以下信息:

async 和 await 关键字

使用async关键字修饰函数可以声明一个async函数,该函数是AsyncFunction构造函数的实例,并且其中允许使用await关键字。利用asyncawait关键字可以以一种更简洁的方式编写基于Promise的异步函数调用,而无需刻意的链式调用promise

使用案例

可以这样理解:在async函数内,使用await关键字可以取代then()函数,等待获取Promise对象成功状态的结果值

案例:利用asyncawait关键字重新实现依次请求到四川省、成都市、武侯区的信息:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
else {
xhr.send()
}
})
}

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 利用async修饰getData()函数
async function getData() {
// await等待获取省份Promise对象成功的结果
const provinceObj = await myAxios({url: 'http://localhost:11000/province'})
Province = provinceObj.list[22] // 获取四川省
// await等待获取城市Promise对象成功的结果
const cityObj = await myAxios({url: 'http://localhost:11000/city', params: {province: Province}})
City = cityObj.list[0] // 获取成都市
// await等待获取地区县Promise对象成功的结果
const areaObj = await myAxios({url: 'http://localhost:11000/area', params: {city: City}})
Area = areaObj.list[3] // 获取武侯区

console.log(`获取到省份:${Province}`)
console.log(`获取到城市:${City}`)
console.log(`获取到地区县:${Area}`)
}
getData()
</script>
</body>
</html>

访问成功,控制台输出以下信息:

捕获错误

可以利用trycatch 捕获同步流程的错误:

1
2
3
4
5
6
try {
// 要执行的代码
} catch (error) {
// error 接收的是,错误消息
// try 里代码,如果有错误,直接进入这里执行
}

案例:利用trycatch 进行错误捕获:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
else {
xhr.send()
}
})
}

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 利用async修饰getData()函数
async function getData() {
try {
// await等待获取省份Promise对象成功的结果
const provinceObj = await myAxios({url: 'http://localhost:11000/province'})
Province = provinceObj.list[22] // 获取四川省
console.log(`获取到省份:${Province}`)

// await等待获取城市Promise对象成功的结果
const cityObj = await myAxios({url: 'http://localhost:11000/city'})
City = cityObj.list[0] // 获取成都市
console.log(`获取到城市:${City}`)

// await等待获取地区县Promise对象成功的结果
const areaObj = await myAxios({url: 'http://localhost:11000/area'})
Area = areaObj.list[3] // 获取武侯区
console.log(`获取到地区县:${Area}`)
} catch (error) {
console.dir(error)
console.log('请求发生错误')
}
}
getData()
</script>
</body>
</html>

控制台输出以下信息:

Promise.all() 静态方法

Promise.all()静态方法允许将多个Promise对象合并为一个Promise对象。如果被合并的所有Promise对象都请求成功,则新合成的Promise对象执行then()中的回调函数,result以列表的形式包含所有Promise对象的返回;如果被合并的所有Promise对象中有一个请求失败,则新合成的Promise对象执行catch()中的回调函数,捕获第一个失败的Promise对象抛出的异常。

语法如下:

1
2
3
4
5
6
const p = Promise.all([Promise对象, Promise对象, ...])
p.then(result => {
// result 结果: [Promise对象成功结果, Promise对象成功结果, ...]
}).catch(error => {
// 第一个失败的 Promise 对象抛出的异常对象
})

案例:利用Promise.all()合并多个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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
else {
xhr.send()
}
})
}

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 得到-获取省份Promise对象
const provinceObj = myAxios({url: 'http://localhost:11000/province'})
// 得到-获取城市Promise对象
const cityObj = myAxios({url: 'http://localhost:11000/city', params: {province: '四川省'}})
// 得到-获取地区县Promise对象
const areaObj = myAxios({url: 'http://localhost:11000/area', params: {city: '成都市'}})

// 使用Promise.all()合并多个Promise对象
const p = Promise.all([provinceObj, cityObj, areaObj])

p.then(result => {
// result包含三个Promise对象的返回
Province = result[0].list[22] // 四川省
City = result[1].list[0] // 成都市
Area = result[2].list[3] // 武侯区

console.log(`获取到省份:${Province}`)
console.log(`获取到城市:${City}`)
console.log(`获取到地区县:${Area}`)
}).catch(error => {
console.dir(error)
console.log('请求发生错误')
})

</script>
</body>
</html>

访问成功,控制台输出以下信息:

如果其中一个Promise对象请求发生错误,则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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 构建myAxios函数
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if(config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
if(config.data) {
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
}
else {
xhr.send()
}
})
}

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 得到-获取省份Promise对象
const provinceObj = myAxios({url: 'http://localhost:11000/province'})
// 得到-获取城市Promise对象
const cityObj = myAxios({url: 'http://localhost:11000/city'})
// 得到-获取地区县Promise对象
const areaObj = myAxios({url: 'http://localhost:11000/area'})

// 使用Promise.all()合并多个Promise对象
const p = Promise.all([provinceObj, cityObj, areaObj])

p.then(result => {
// result包含三个Promise对象的返回
Province = result[0].list[22] // 四川省
City = result[1].list[0] // 成都市
Area = result[2].list[3] // 武侯区

console.log(`获取到省份:${Province}`)
console.log(`获取到城市:${City}`)
console.log(`获取到地区县:${Area}`)
}).catch(error => {
console.dir(error)
console.log('请求发生错误')
})

</script>
</body>
</html>

控制台输出以下信息:

Axios

实际上,已经有名为axios第三方库封装好了XHR和Promise,可以快速实现异步请求。(同之前手动封装的myAxios()一样,是异步请求的终极策略)

安装axios:

  • node环境:npm install axios --save
  • 浏览器:

axios的使用语法如下,其返回也是Promise对象,同样可以使用链式编程:

1
2
3
4
5
6
7
8
9
10
11
// 调用axios函数,并传入配置参数
axios({
// url:
// method:
// params:
// ...
}).then(result => {
// 成功
}).catch(error => {
// 失败
})

GET请求

GET请求携带两个参数,分别为nameage

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 引入axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios({
url: 'http://localhost:11000',
method: 'GET',
params: {
name: '花猪',
age: 18
}
}).then(result => {
console.log(result)
}).catch(error => {
console.dir(error)
})
</script>
</body>
</html>

访问成功,控制台输出以下信息:

服务端代码如下:

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
let express = require('express')
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})

app.get('/', (req, res) => {
const queryParams = req.query;
const name = req.query.name;
const age = req.query.age;
res.json({
msg: `收到get请求,其中 name = ${name},age = ${age}`,
})
console.log(`name = ${name}`)
console.log(`age = ${age}`)
})

app.listen(11000, () => {
console.log('http://localhost:11000')
})

服务端输出如下:

1
2
3
http://localhost:11000
name = 花猪
age = 18

POST请求

POST请求携带两个参数,分别为usernamepassword

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 引入axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios({
url: 'http://localhost:11000',
method: 'POST',
data: {
username: 'HuaZhu',
password: 123456
}
}).then(result => {
console.log(result)
}).catch(error => {
console.dir(error)
})
</script>
</body>
</html>

访问成功,控制台输出以下信息:

服务端代码如下:

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
30
31
32
let express = require('express')
const bodyParser = require('body-parser');
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})

// 使用body-parser中间件解析JSON类型的请求体
app.use(bodyParser.json());

app.post('/', (req, res) => {
// 通过req.body访问POST请求的参数
const postData = req.body;
res.json({
msg: `收到post请求,其中 username = ${postData.username},password = ${postData.password}`,
})
console.log(`username = ${postData.username}`)
console.log(`password = ${postData.password}`)
})

app.listen(11000, () => {
console.log('http://localhost:11000')
})

服务端输出如下:

1
2
3
http://localhost:11000
username = HuaZhu
password = 123456

链式调用

本小节的案例同样是依次发出以下请求:请求到四川省、成都市、武侯区的信息。

  1. 访问http://localhost:11000/province请求全国省份列表数据,并拿到四川省信息
  2. 携带参数“四川省”访问http://localhost:11000/city请求四川省城市列表数据,并拿到成都市信息
  3. 携带参数“成都市”访问http://localhost:11000/area请求成都市地区县列表数据,并拿到武侯区信息

本小节服务端代码如下,只是针对案例做了简单的数据返回:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
let express = require('express')
let app = express()

app.all('*', function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header('Access-Control-Allow-Origin', '*')
//允许的header类型
res.header('Access-Control-Allow-Headers', 'content-type')
//跨域允许的请求方式
res.header('Access-Control-Allow-Methods', 'DELETE,PUT,POST,GET,OPTIONS')
if (req.method.toLowerCase() == 'options') res.send(200)
//让options尝试请求快速结束
else next()
})

// 定义全国省份列表数据
const province = {
message: '获取省份成功',
list: ["北京","天津","河北省","山西省","内蒙古自治区","辽宁省","吉林省","黑龙江省","上海","江苏省","浙江省","安徽省","福建省","江西省","山东省","河南省","湖北省","湖南省","广东省","广西壮族自治区","海南省","重庆","四川省","贵州省","云南省","西藏自治区","陕西省","甘肃省","青海省","宁夏回族自治区","新疆维吾尔自治区","台湾","香港特别行政区","澳门特别行政区"]
}
// 定义四川省城市列表数据
const city = {
message: '获取城市成功',
list: ["成都市","自贡市","攀枝花市","泸州市","德阳市","绵阳市","广元市","遂宁市","内江市","乐山市","南充市","眉山市","宜宾市","广安市","达州市","雅安市","巴中市","资阳市","阿坝藏族羌族自治州","甘孜藏族自治州","凉山彝族自治州"]
}
// 定义成都市地区县列表数据
const area = {
message: '获取地区县成功',
list: ["锦江区","青羊区","金牛区","武侯区","成华区","龙泉驿区","青白江区","新都区","温江区","金堂县","双流县","郫县","大邑县","蒲江县","新津县","都江堰市","彭州市","邛崃市","崇州市"]
}

// GET请求(无参),返回全国省份列表数据
app.get('/province', (req, res) => {
res.json({
message: province.message,
list: province.list
})
console.log(province.message)
console.log(province.list)
})

// GET请求(含参)
// 如果没传参数或参数不为'四川省',则返回错误信息
// 如果参数为'四川省',返回四川省城市列表数据
app.get('/city', (req, res) => {
if(req.query === undefined || req.query.province != '四川省') {
res.status(400).send('参数错误')
console.log('获取城市失败')
} else {
res.json({
message: city.message,
list: city.list
})
console.log(city.message)
console.log(city.list)
}
})

// GET请求(含参)
// 如果没传参数或参数不为'成都市',则返回错误信息
// 如果参数为'成都市',返回成都市地区县列表数据
app.get('/area', (req, res) => {
if(req.query === undefined || req.query.city != '成都市') {
res.status(400).send('参数错误')
console.log('获取地区县失败')
} else {
res.json({
message: area.message,
list: area.list
})
console.log(area.message)
console.log(area.list)
}
})

app.listen(11000, () => {
console.log('http://localhost:11000')
})

案例,使用axios依次请求到四川省、成都市、武侯区的信息:

(注意:返回的内容在result.data中)

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 引入axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 1.得到-获取省份Promise对象
axios({
url: 'http://localhost:11000/province',
method: 'GET',
}).then(result => {
Province = result.data.list[22] // 获取四川省
console.log(result)
console.log(`获取到省份:${Province}`)
// 2.得到-获取得到-获取城市Promise对象
return axios({url: 'http://localhost:11000/city', params: {province: Province}})
}).then(result => {
City = result.data.list[0] // 获取成都市
console.log(result)
console.log(`获取到城市:${City}`)
// 3.得到-获取地区县Promise对象
return axios({url: 'http://localhost:11000/area', params: {city: City}})
}).then(result => {
Area = result.data.list[3] // 获取武侯区
console.log(result)
console.log(`获取到地区县:${Area}`)
}).catch(error => { // 捕获错误
console.dir(error)
console.log('请求发生错误')
})
</script>
</body>
</html>

访问成功,控制台输出以下信息:

一旦其中某一个访问出现错误,利用最后的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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 引入axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>

let Province; // 定义省份
let City; // 定义城市
let Area; // 定义地区县

// 1.得到-获取省份Promise对象
axios({
url: 'http://localhost:11000/province',
method: 'GET',
}).then(result => {
Province = result.data.list[22] // 获取四川省
console.log(result)
console.log(`获取到省份:${Province}`)
// 2.得到-获取得到-获取城市Promise对象
return axios({url: 'http://localhost:11000/city'})
}).then(result => {
City = result.data.list[0] // 获取成都市
console.log(result)
console.log(`获取到城市:${City}`)
// 3.得到-获取地区县Promise对象
return axios({url: 'http://localhost:11000/area'})
}).then(result => {
Area = result.data.list[3] // 获取武侯区
console.log(result)
console.log(`获取到地区县:${Area}`)
}).catch(error => { // 捕获错误
console.dir(error)
console.log('请求发生错误')
})
</script>
</body>
</html>

控制台输出以下信息:


后记

此篇加深了对axios的理解。