Skip to content

Commit

Permalink
udpate: promise封装异步操作
Browse files Browse the repository at this point in the history
  • Loading branch information
qianguyihao committed May 20, 2021
1 parent 40d518a commit bcf8ac0
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 162 deletions.
331 changes: 171 additions & 160 deletions 06-JavaScript基础:ES6语法/10-Promise入门详解.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,157 +155,7 @@ myPromise()

Promise 的精髓在于**对异步操作的状态管理**

接下来,我们来具体看看, promise 的代码是怎么写的。

### Promise 处理异步任务的过程

通过 Promise 处理异步任务的典型写法如下:

```js
// 第一步:model层的接口封装
function promiseA() {
return new Promise((resolve, reject) => {
// 这里做异步任务(比如 ajax 请求接口。这里暂时用定时器代替)
setTimeout(() => {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据,返回码 retCode 是动态数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
}
// 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据
promiseA()
.then((data) => {
// 从 resolve 获取正常结果
console.log(data);
})
.catch((e) => {
// 从 reject 获取异常结果
console.log(e);
});
```

上方代码中,当从接口返回的数据`data.retCode`的值(接口返回码)不同时,可能会走 resolve,也可能会走 reject,这个由你自己的业务决定。

上面的写法中,是将 promise 实例定义成了一个**函数** `promiseA`。我们也可以将 promise 实例定义成一个**变量** `promiseB`,达到的效果是一模一样的。写法如下:(写法上略有区别)

```js
// 第一步:model层的接口封装
const promiseB = new Promise((resolve, reject) => {
// 这里做异步任务(比如ajax 请求接口。这里暂时用定时器代替)
setTimeout(() => {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据,返回码 retCode 是动态数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
// 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据
promiseB
.then((data) => {
// 从 resolve 获取正常结果
console.log(data);
})
.catch((e) => {
// 从 reject 获取异常结果
console.log(e);
});
```

### 捕获 reject 异常状态的两种写法

我们有两种写法可以捕获并处理 reject 异常状态。上一小段中,用的就是其中一种写法。

这两种写法的代码举例如下:

```js
// 第一步:model层的接口封装
function promiseA() {
return new Promise((resolve, reject) => {
// 这里做异步任务(比如 ajax 请求接口。这里暂时用定时器代替)
setTimeout(() => {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据,返回码 retCode 是动态数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
}
const onResolve = function (value) {
console.log(value);
};
const onReject = function (e) {
console.log(e);
};
// 写法1:通过 catch 方法捕获 状态变为已拒绝时的 promise
promiseA().then(onResolve).catch(onReject);
// 写法2:then 可以传两个参数,第⼀个参数为 resolve 后执⾏,第⼆个参数为 reject 后执⾏
promiseA().then(onResolve, onReject);
// 【错误写法】写法3:通过 try catch 捕获 状态变为已拒绝时的 promise
// 这种写法是错误的,因为 try catch只能捕获同步代码里的异常,而 promise.reject() 是异步代码。
try {
promiseA().then(onResolve);
} catch (e) {
// 语法上,catch必须要传入一个参数,否则报错
onReject(e);
}
```

需要注意的是:

(1)上面的写法 3 是错误的。运行之后,控制台会报如下错误:

![](http://img.smyhvae.com/20210430_1553.png)

[解释如下](https://blog.csdn.net/xiaoluodecai/article/details/107297404)

try-catch 主要用于捕获异常,注意,这里的异常是指**同步**函数的异常。如果 try 里面的异步方法出现了异常,此时 catch 是无法捕获到异常的。

原因是:当异步函数抛出异常时,对于宏任务而言,执行函数时已经将该函数推入栈,此时并不在 try-catch 所在的栈,所以 try-catch 并不能捕获到错误。对于微任务而言(比如 promise)promise 的构造函数的异常只能被自带的 reject 也就是.catch 函数捕获到。

(2)写法 1 中,`promiseA().then().catch()``promiseA().catch().then()`区别在于:前者可以捕获到 `then` 里面的异常,后者不可以。

### 小结

1、promise 有三种状态:等待中、成功、失败。等待中状态可以更改为成功或失败,已经更改过状态后⽆法继续更改(例如从失败改为成功)。

2、promise 实例中需要传⼊⼀个函数,他接受两个函数参数,执⾏第⼀个参数之后就会改变当前 promise 为「成功」状态,执⾏第⼆个参数之后就会变为「失败」状态。

3、通过 .then ⽅法,即可在上⼀个 promise 达到成功时继续执⾏下⼀个函数或 promise。同时通过 resolve reject 时传⼊参数,即可给下⼀个函数或 promise 传⼊初始值。

4、失败的 promise,后续可以通过 promise 自带的 .catch ⽅法或是 .then ⽅法的第⼆个参数进⾏捕获。

## Promise 规范

### Promise 规范解读

Promise 是⼀个拥有 then ⽅法的对象或函数。任何符合 promise 规范的对象或函数都可以成为 Promise。

关于 promise 规范的详细解读,可以看下面这个链接:

- Promises/A+ 规范:<https://promisesaplus.com/>

## promise 对象的 3 个状态
### promise 对象的 3 个状态

- 初始化(等待中):pending

Expand All @@ -327,7 +177,7 @@ Promise 是⼀个拥有 then ⽅法的对象或函数。任何符合 promise 规

另外,resolve() reject()这两个方法,是可以给 promise.then()传递参数的。

完整代码举例如下
关于promise的状态改变,伪代码及注释如下

```javascript
let promise = new Promise((resolve, reject) => {
Expand Down Expand Up @@ -367,7 +217,7 @@ promise.then(
```js
const p = new Promise((resolve, reject) => {
resolve(1); // 代码执行到这里时, promise状态是 fulfilled
reject(2); // 尝试修改状态为 rejected,是不行的
reject(2); // 尝试修改状态为 rejected,是不行的。因为状态执行到上一行时,已经被改变了。
});
p.then((res) => {
Expand All @@ -376,7 +226,31 @@ p.then((res) => {
console.log(err);
```

上方代码的打印结果是1,而不是2。
上方代码的打印结果是1,而不是2,详见注释。

### 小结

1、promise 有三种状态:等待中、成功、失败。等待中状态可以更改为成功或失败,已经更改过状态后⽆法继续更改(例如从失败改为成功)。

2、promise 实例中需要传⼊⼀个函数,这个函数接收两个参数,执⾏第⼀个参数之后就会改变当前 promise 为「成功」状态,执⾏第⼆个参数之后就会变为「失败」状态。

3、通过 .then ⽅法,即可在上⼀个 promise 达到成功时继续执⾏下⼀个函数或 promise。同时通过 resolve reject 时传⼊参数,即可给下⼀个函数或 promise 传⼊初始值。

4、失败的 promise,后续可以通过 promise 自带的 .catch ⽅法或是 .then ⽅法的第⼆个参数进⾏捕获。


### Promise 规范

Promise 是⼀个拥有 then ⽅法的对象或函数。任何符合 promise 规范的对象或函数都可以成为 Promise。

关于 promise 规范的详细解读,可以看下面这个链接:

- Promises/A+ 规范:<https://promisesaplus.com/>

了解这些常见概念之后,接下来,我们来具体看看 promise 的代码是怎么写的。




## 如何封装异步操作为 promise

Expand Down Expand Up @@ -449,6 +323,8 @@ fun2().then(() => {

### Promise 封装 Ajax 请求

// todo 代码简化

**传统写法**

```js
Expand Down Expand Up @@ -498,23 +374,26 @@ ajax(
```js
const request = require('request');
// Promise 定义接口
// 第一步:model层的接口封装
function request1() {
return new Promise((resolve, reject) => {
request('https://www.baidu.com', res => {
if (res.retCode == 200) {
// 这里的 res 是接口1的返回结果
request('xxx_a.json', res => {
// 这里的 res 是接口的返回结果。返回码 retCode 是动态数据。
if (res.retCode == 0) {
// 接口请求成功时调用
resolve('request1 success' + res);
} else {
reject('接口请求失败');
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
});
});
}
// 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据
request1()
.then((res) => {
// 接口1请求成功后,打印接口1的返回结果
// 从 resolve 获取正常结果:接口请求成功后,打印接口1的返回结果
console.log(res);
// return request2();
})
Expand All @@ -524,6 +403,138 @@ request1()
});
```


### Promise 处理异步任务

通过 Promise 处理异步任务的典型写法如下:

```js
// 第一步:model层的接口封装
function promiseA() {
return new Promise((resolve, reject) => {
// 这里做异步任务(比如 ajax 请求接口。这里暂时用定时器代替)
setTimeout(() => {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据,返回码 retCode 是动态数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
}
// 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据
promiseA()
.then((data) => {
// 从 resolve 获取正常结果
console.log(data);
})
.catch((e) => {
// 从 reject 获取异常结果
console.log(e);
});
```

上方代码中,当从接口返回的数据`data.retCode`的值(接口返回码)不同时,可能会走 resolve,也可能会走 reject,这个由你自己的业务决定。

上面的写法中,是将 promise 实例定义成了一个**函数** `promiseA`。我们也可以将 promise 实例定义成一个**变量** `promiseB`,达到的效果是一模一样的。写法如下:(写法上略有区别)

```js
// 第一步:model层的接口封装
const promiseB = new Promise((resolve, reject) => {
// 这里做异步任务(比如ajax 请求接口。这里暂时用定时器代替)
setTimeout(() => {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据,返回码 retCode 是动态数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
// 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据
promiseB
.then((data) => {
// 从 resolve 获取正常结果
console.log(data);
})
.catch((e) => {
// 从 reject 获取异常结果
console.log(e);
});
```

### 捕获 reject 异常状态的两种写法

我们有两种写法可以捕获并处理 reject 异常状态。上一小段中,用的就是其中一种写法。

这两种写法的代码举例如下:

```js
// 第一步:model层的接口封装
function promiseA() {
return new Promise((resolve, reject) => {
// 这里做异步任务(比如 ajax 请求接口。这里暂时用定时器代替)
setTimeout(() => {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据,返回码 retCode 是动态数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
}
const onResolve = function (value) {
console.log(value);
};
const onReject = function (e) {
console.log(e);
};
// 写法1:通过 catch 方法捕获 状态变为已拒绝时的 promise
promiseA().then(onResolve).catch(onReject);
// 写法2:then 可以传两个参数,第⼀个参数为 resolve 后执⾏,第⼆个参数为 reject 后执⾏
promiseA().then(onResolve, onReject);
// 【错误写法】写法3:通过 try catch 捕获 状态变为已拒绝时的 promise
// 这种写法是错误的,因为 try catch只能捕获同步代码里的异常,而 promise.reject() 是异步代码。
try {
promiseA().then(onResolve);
} catch (e) {
// 语法上,catch必须要传入一个参数,否则报错
onReject(e);
}
```

需要注意的是:

(1)上面的写法 3 是错误的。运行之后,控制台会报如下错误:

![](http://img.smyhvae.com/20210430_1553.png)

[解释如下](https://blog.csdn.net/xiaoluodecai/article/details/107297404)

try-catch 主要用于捕获异常,注意,这里的异常是指**同步**函数的异常。如果 try 里面的异步方法出现了异常,此时 catch 是无法捕获到异常的。

原因是:当异步函数抛出异常时,对于宏任务而言,执行函数时已经将该函数推入栈,此时并不在 try-catch 所在的栈,所以 try-catch 并不能捕获到错误。对于微任务而言(比如 promise)promise 的构造函数的异常只能被自带的 reject 也就是.catch 函数捕获到。

(2)写法 1 中,`promiseA().then().catch()``promiseA().catch().then()`区别在于:前者可以捕获到 `then` 里面的异常,后者不可以。




## 总结

了解这些内容之后, 你已经对 Promise 有了基本了解。下一篇文章,我们来讲一讲 Promise 在实战开发的常见用法。
Expand Down
Loading

0 comments on commit bcf8ac0

Please sign in to comment.