add: promise的链式调用
This commit is contained in:
parent
bf8becb562
commit
63b59a4d5f
@ -1,6 +1,6 @@
|
||||
## 为什么需要 Promise?
|
||||
|
||||
我们在前面的文章《JavaScript基础:异步编程和Ajax/单线程和异步》中讲过,Javascript 是⼀⻔单线程语⾔。早期我们解决异步场景时,⼤部分情况都是通过回调函数来进⾏。
|
||||
我们在前面的文章《JavaScript 基础:异步编程和 Ajax/单线程和异步》中讲过,Javascript 是⼀⻔单线程语⾔。早期我们解决异步场景时,⼤部分情况都是通过回调函数来进⾏。
|
||||
|
||||
(如果你还不了解单线程和异步的概念,可以先去回顾上一篇文章。)
|
||||
|
||||
@ -328,9 +328,10 @@ function ajax(url, success, fail) {
|
||||
xmlhttp.send();
|
||||
xmlhttp.onreadystatechange = function () {
|
||||
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
|
||||
success(xmlhttp.responseText);
|
||||
success && success(xmlhttp.responseText);
|
||||
} else {
|
||||
fail(new Error('接口请求失败'));
|
||||
// 这里的 && 符号,意思是:如果传了 fail 参数,就调用后面的 fail();如果没传 fail 参数,就不调用后面的内容。因为 fail 参数不一定会传。
|
||||
fail && fail(new Error('接口请求失败'));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -348,6 +349,8 @@ ajax(
|
||||
|
||||
上面的传统写法里,定义和执行 ajax 时需要传⼊ success 和 fail 这两个回调函数,进而执行回调函数。
|
||||
|
||||
注意看注释,`callback && callback()`这种格式的写法,很常见。
|
||||
|
||||
### Promise 写法
|
||||
|
||||
有了 Promise 之后,我们不需要传入回调函数,而是:
|
||||
@ -363,12 +366,25 @@ ajax(
|
||||
写法 1:
|
||||
|
||||
```js
|
||||
const request = require('request');
|
||||
// 封装 ajax 请求:传入回调函数 success 和 fail
|
||||
function ajax(url, success, fail) {
|
||||
var xmlhttp = new XMLHttpRequest();
|
||||
xmlhttp.open('GET', url);
|
||||
xmlhttp.send();
|
||||
xmlhttp.onreadystatechange = function () {
|
||||
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
|
||||
success && success(xmlhttp.responseText);
|
||||
} else {
|
||||
// 这里的 && 符号,意思是:如果传了 fail 参数,就调用后面的 fail();如果没传 fail 参数,就不调用后面的内容。因为 fail 参数不一定会传。
|
||||
fail && fail(new Error('接口请求失败'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 第一步:model层的接口封装
|
||||
function promiseA() {
|
||||
return new Promise((resolve, reject) => {
|
||||
request('xxx_a.json', (res) => {
|
||||
ajax('xxx_a.json', (res) => {
|
||||
// 这里的 res 是接口的返回结果。返回码 retCode 是动态数据。
|
||||
if (res.retCode == 0) {
|
||||
// 接口请求成功时调用
|
||||
@ -404,7 +420,7 @@ promiseA()
|
||||
```js
|
||||
// 第一步:model层的接口封装
|
||||
const promiseB = new Promise((resolve, reject) => {
|
||||
request('xxx_a.json', (res) => {
|
||||
ajax('xxx_a.json', (res) => {
|
||||
// 这里的 res 是接口的返回结果。返回码 retCode 是动态数据。
|
||||
if (res.retCode == 0) {
|
||||
// 接口请求成功时调用
|
||||
@ -430,7 +446,7 @@ promiseB
|
||||
|
||||
注意,如果你用的是写法 1(将 promise 实例定义为函数),则调用 promise 的时候是`promiseA().then()`,如果你用的是写法 2(将 promise 实例定位为函数),则调用的时候用的是`promiseB.then()`。写法 1 多了个括号,不要搞混了。
|
||||
|
||||
## 捕获 reject 异常状态的两种写法
|
||||
## 处理 reject 失败状态的两种写法
|
||||
|
||||
我们有两种写法可以捕获并处理 reject 异常状态。这两种写法的代码举例如下:
|
||||
|
||||
@ -482,151 +498,6 @@ try-catch 主要用于捕获异常,注意,这里的异常是指**同步**函
|
||||
|
||||
(2)写法 1 中,`promiseA().then().catch()`和`promiseA().catch().then()`区别在于:前者可以捕获到 `then` 里面的异常,后者不可以。
|
||||
|
||||
|
||||
|
||||
## return 的函数返回值
|
||||
|
||||
return 后面的返回值,有两种情况:
|
||||
|
||||
- 情况 1:返回 Promise 实例对象。返回的该实例对象会调用下一个 then。
|
||||
|
||||
- 情况 2:返回普通值。返回的普通值会直接传递给下一个 then,通过 then 参数中函数的参数接收该值。
|
||||
|
||||
我们针对上面这两种情况,详细解释一下。
|
||||
|
||||
### 情况 1:返回 Promise 实例对象
|
||||
|
||||
举例如下:(这个例子,跟上一段 Ajax 链式调用 的例子差不多)
|
||||
|
||||
```html
|
||||
<!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 type="text/javascript">
|
||||
/*
|
||||
基于Promise发送Ajax请求
|
||||
*/
|
||||
function queryData(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState != 4) return;
|
||||
if (xhr.readyState == 4 && xhr.status == 200) {
|
||||
// 处理正常情况
|
||||
resolve(xhr.responseText);
|
||||
} else {
|
||||
// 处理异常情况
|
||||
reject('接口请求失败');
|
||||
}
|
||||
};
|
||||
xhr.responseType = 'json'; // 设置返回的数据类型
|
||||
xhr.open('get', url);
|
||||
xhr.send(null); // 请求接口
|
||||
});
|
||||
}
|
||||
// 发送多个ajax请求并且保证顺序
|
||||
queryData('http://localhost:3000/api1')
|
||||
.then(
|
||||
(data1) => {
|
||||
console.log(JSON.stringify(data1));
|
||||
return queryData('http://localhost:3000/api2');
|
||||
},
|
||||
(error1) => {
|
||||
console.log(error1);
|
||||
}
|
||||
)
|
||||
.then(
|
||||
(data2) => {
|
||||
console.log(JSON.stringify(data2));
|
||||
// 这里的 return,返回的是 Promise 实例对象
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve('qianguyihao');
|
||||
});
|
||||
},
|
||||
(error2) => {
|
||||
console.log(error2);
|
||||
}
|
||||
)
|
||||
.then((data3) => {
|
||||
console.log(data3);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 情况 2:返回 普通值
|
||||
|
||||
```html
|
||||
<!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 type="text/javascript">
|
||||
/*
|
||||
基于Promise发送Ajax请求
|
||||
*/
|
||||
function queryData(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState != 4) return;
|
||||
if (xhr.readyState == 4 && xhr.status == 200) {
|
||||
// 处理正常情况
|
||||
resolve(xhr.responseText);
|
||||
} else {
|
||||
// 处理异常情况
|
||||
reject('接口请求失败');
|
||||
}
|
||||
};
|
||||
xhr.responseType = 'json'; // 设置返回的数据类型
|
||||
xhr.open('get', url);
|
||||
xhr.send(null); // 请求接口
|
||||
});
|
||||
}
|
||||
// 发送多个ajax请求并且保证顺序
|
||||
queryData('http://localhost:3000/api1')
|
||||
.then(
|
||||
(data1) => {
|
||||
console.log(JSON.stringify(data1));
|
||||
return queryData('http://localhost:3000/api2');
|
||||
},
|
||||
(error1) => {
|
||||
console.log(error1);
|
||||
}
|
||||
)
|
||||
.then(
|
||||
(data2) => {
|
||||
console.log(JSON.stringify(data2));
|
||||
// 返回普通值
|
||||
return 'qianguyihao';
|
||||
},
|
||||
(error2) => {
|
||||
console.log(error2);
|
||||
}
|
||||
)
|
||||
/*
|
||||
既然上方返回的是 普通值,那么,这里的 then 是谁来调用呢?
|
||||
答案是:这里会产生一个新的 默认的 promise实例,来调用这里的then,确保可以继续进行链式操作。
|
||||
*/
|
||||
.then((data3) => {
|
||||
// 这里的 data3 接收的是 普通值 'qianguyihao'
|
||||
console.log(data3);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
了解这些内容之后, 你已经对 Promise 有了基本了解。下一篇文章,我们来讲一讲 Promise 在实战开发的常见用法。
|
||||
|
@ -16,9 +16,9 @@ function ajax(url, success, fail) {
|
||||
xmlhttp.send();
|
||||
xmlhttp.onreadystatechange = function () {
|
||||
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
|
||||
success(xmlhttp.responseText);
|
||||
success && success(xmlhttp.responseText);
|
||||
} else {
|
||||
fail(new Error('接口请求失败'));
|
||||
fail && fail(new Error('接口请求失败'));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -57,9 +57,9 @@ function ajax(url, success, fail) {
|
||||
xmlhttp.send();
|
||||
xmlhttp.onreadystatechange = function () {
|
||||
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
|
||||
success(xmlhttp.responseText);
|
||||
success && success(xmlhttp.responseText);
|
||||
} else {
|
||||
fail(new Error('接口请求失败'));
|
||||
fail && fail(new Error('接口请求失败'));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -95,9 +95,9 @@ new Promise((resolve, reject) => {
|
||||
|
||||
上面代码中,then 是可以链式调用的,一旦 return 一个新的 promise 实例之后,后面的 then 就可以拿到前面 resolve 出来的数据。这种**扁平化**的写法,更方便维护;并且可以更好的**管理**请求成功和失败的状态。
|
||||
|
||||
但是,你可能会奇怪,上面的代码,怎么这么多?而且有不少重复。这里只是采用了一种笨拙的方式来写,为的是方便大家理解 promise 的执行过程。我们其实可以对 promise 的链式调用进行进一步封装。
|
||||
但是,你可能会奇怪,上面的代码,怎么这么多?而且有不少重复。因为这里只是采用了一种笨拙的方式来写,为的是方便大家理解 promise 的执行过程。我们其实可以对 promise 的链式调用进行封装。
|
||||
|
||||
怎么个封装法呢?上面的代码中,每次在 return 一个 promise 的时候,只是 url 地址不一样,其他的代码是一样的。所以我们可以把重复的代码封装成函数。
|
||||
怎么个封装法呢?上面的代码中,每次在 return 一个 promise 的时候,只是 url 地址不一样,其他的代码是一样的。所以我们可以把重复的代码封装成函数。写法如下。
|
||||
|
||||
### Promise 链式调用(封装写法)
|
||||
|
||||
@ -111,9 +111,9 @@ function ajax(url, success, fail) {
|
||||
xmlhttp.send();
|
||||
xmlhttp.onreadystatechange = function () {
|
||||
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
|
||||
success(xmlhttp.responseText);
|
||||
success && success(xmlhttp.responseText);
|
||||
} else {
|
||||
fail(new Error('接口请求失败'));
|
||||
fail && fail(new Error('接口请求失败'));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -156,11 +156,11 @@ getPromise('a.json')
|
||||
});
|
||||
```
|
||||
|
||||
怎么样?上面代码中,是不是非常简洁?而且可读性很强。
|
||||
怎么样?上面代码是不是非常简洁?而且可读性很强。
|
||||
|
||||
代码写到这里,我们还可以再继续优化一下。细心的你可以发现,我们在依次请求三个接口的时候,里面针对 resolve 和 reject 的处理时机是一样的。
|
||||
代码写到这里,我们还可以再继续优化一下。细心的你可以发现,我们在做三次嵌套请求的时候,针对 resolve 和 reject 的处理时机是一样的。如果你的业务是针对**同一个接口**连续做了三次调用,只是请求**传参不同**,那么,按上面这样写是没有问题的。
|
||||
|
||||
但是,真正在实战中,我们在调不用的接口时,要处理的 resolve 和 reject 的时机往往是不同的。所以分开封装 不同的 Promise 实例。实战中的代码,应该是像下面这样写。
|
||||
但是,真正在实战中,我们往往需要嵌套请求**多个不同的接口**,要处理的 resolve 和 reject 的时机往往是不同的,所以需要分开封装不同的 Promise 实例,这在实战开发中更为常见。代码应该是像下面这样写。
|
||||
|
||||
### Promise 链式调用(封装写法,多个接口)
|
||||
|
||||
@ -174,9 +174,9 @@ function ajax(url, success, fail) {
|
||||
xmlhttp.send();
|
||||
xmlhttp.onreadystatechange = function () {
|
||||
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
|
||||
success(xmlhttp.responseText);
|
||||
success && success(xmlhttp.responseText);
|
||||
} else {
|
||||
fail(new Error('接口请求失败'));
|
||||
fail && fail(new Error('接口请求失败'));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -248,4 +248,156 @@ request1()
|
||||
|
||||
这段代码很经典,你一定要多看几遍,多默写几遍。倒背如流也不过分。
|
||||
|
||||
## 链式调用,如何处理 reject 失败状态
|
||||
|
||||
### 例 1:不处理 reject
|
||||
|
||||
```js
|
||||
getPromise('a.json')
|
||||
.then(
|
||||
(res) => {
|
||||
console.log(res);
|
||||
return getPromise('b.json'); // 继续请求 b
|
||||
},
|
||||
(err) => {
|
||||
// a 请求失败
|
||||
console.log('a: err');
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
// b 请求成功
|
||||
console.log(res);
|
||||
return getPromise('c.json'); // 继续请求 c
|
||||
})
|
||||
.then((res) => {
|
||||
// c 请求成功
|
||||
console.log('c:success');
|
||||
});
|
||||
```
|
||||
|
||||
上面的代码中,假设 a 请求失败,那么,后面的代码会怎么走呢?
|
||||
|
||||
打印结果:
|
||||
|
||||
```
|
||||
'a: err
|
||||
undefined
|
||||
c:success
|
||||
```
|
||||
|
||||
我们可以看到,虽然 a 请求失败,但后续的请求依然会继续执行。
|
||||
|
||||
为何打印结果的第二行是 undefined?这是因为,当 a 请求走到 reject 之后,我们并没有做任何处理。这就导致,代码走到第二个 `then`的时候,**其实是在执行一个空的 promise**。
|
||||
|
||||
### 例 2:单独处理 reject
|
||||
|
||||
```js
|
||||
getPromise('a.json')
|
||||
.then(
|
||||
(res) => {
|
||||
console.log(res);
|
||||
return getPromise('b.json'); // 继续请求 b
|
||||
},
|
||||
(err) => {
|
||||
// a 请求失败
|
||||
console.log('a: err');
|
||||
// 【重要】即使 a 请求失败,也依然继续执行 b请求
|
||||
return getPromise('b.json');
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
// b 请求成功
|
||||
console.log(res);
|
||||
return getPromise('c.json'); // 继续请求 c
|
||||
})
|
||||
.then((res) => {
|
||||
// c 请求成功
|
||||
console.log('c:success');
|
||||
});
|
||||
```
|
||||
|
||||
跟例 1 相比,例 2 在 reject 中增加了一行`return getPromise('b.json')`,意味着,即使 a 请求失败,也要继续执行 b。
|
||||
|
||||
这段代码,我们是单独处理了 a 请求失败的情况。
|
||||
|
||||
### 统一处理 reject
|
||||
|
||||
针对 a、b、c 这三个请求,不管哪个请求失败,我就希望做统一处理。这种代码要怎么写呢?我们可以在最后面写一个 catch。
|
||||
|
||||
代码举例如下:
|
||||
|
||||
```js
|
||||
getPromise('a.json')
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
return getPromise('b.json'); // 继续请求 b
|
||||
})
|
||||
.then((res) => {
|
||||
// b 请求成功
|
||||
console.log(res);
|
||||
return getPromise('c.json'); // 继续请求 c
|
||||
})
|
||||
.then((res) => {
|
||||
// c 请求成功
|
||||
console.log('c:success');
|
||||
})
|
||||
.catch((err) => {
|
||||
// 统一处理请求失败
|
||||
console.log(err);
|
||||
});
|
||||
```
|
||||
|
||||
上面的代码中,由于是统一处理多个请求的异常,所以只要有一个请求失败了,就会马上走到 catch,剩下的请求就不会继续执行了。比如说:
|
||||
|
||||
- a 请求失败:走到 catch,不执行 b 和 c
|
||||
|
||||
- a 请求成功,b 请求失败:走到 catch,不执行 c。
|
||||
|
||||
## return 的返回值
|
||||
|
||||
return 后面的返回值,有两种情况:
|
||||
|
||||
- 情况 1:返回 Promise 实例对象。返回的该实例对象会调用下一个 then。
|
||||
|
||||
- 情况 2:返回普通值。返回的普通值会直接传递给下一个 then,通过 then 参数中函数的参数接收该值。
|
||||
|
||||
我们针对上面这两种情况,详细解释一下。
|
||||
|
||||
### 情况 1:返回 Promise 实例对象
|
||||
|
||||
举例如下:(这个例子,跟上一段 Ajax 链式调用 的例子差不多)
|
||||
|
||||
```js
|
||||
getPromise('a.json')
|
||||
.then((res) => {
|
||||
// a 请求成功。从 resolve 获取正常结果:接口请求成功后,打印a接口的返回结果
|
||||
console.log(res);
|
||||
// 这里的 return,返回的是 Promise 实例对象
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve('qianguyihao');
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
```
|
||||
|
||||
### 情况 2:返回 普通值
|
||||
|
||||
```js
|
||||
getPromise('a.json')
|
||||
.then((res) => {
|
||||
// a 请求成功。从 resolve 获取正常结果:接口请求成功后,打印a接口的返回结果
|
||||
console.log(res);
|
||||
// 返回普通值
|
||||
return 'qianguyihao';
|
||||
})
|
||||
/*
|
||||
既然上方代码并没有返回 promise,那么,这里的 then 是谁来调用呢?
|
||||
答案是:这里会产生一个新的 默认的 promise实例,来调用这里的then,确保可以继续进行链式操作。
|
||||
*/
|
||||
.then((res2) => {
|
||||
// 这里的 res2 接收的是 普通值 'qianguyihao'
|
||||
console.log(res2);
|
||||
});
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user