add: promise的链式调用

This commit is contained in:
qianguyihao 2021-05-23 18:00:26 +08:00
parent bf8becb562
commit 63b59a4d5f
2 changed files with 188 additions and 165 deletions

View File

@ -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 在实战开发的常见用法

View File

@ -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('csuccess');
});
```
上面的代码中假设 a 请求失败那么后面的代码会怎么走呢
打印结果
```
'a: err
undefined
csuccess
```
我们可以看到虽然 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('csuccess');
});
```
跟例 1 相比 2 reject 中增加了一行`return getPromise('b.json')`意味着即使 a 请求失败也要继续执行 b
这段代码我们是单独处理了 a 请求失败的情况
### 统一处理 reject
针对 abc 这三个请求不管哪个请求失败我就希望做统一处理这种代码要怎么写呢?我们可以在最后面写一个 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('csuccess');
})
.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);
});
```