Web/06-JavaScript基础:异步编程/07-Promise实例的方法.md

850 lines
24 KiB
Markdown
Raw Normal View History

## Promise 实例的方法简介
Promise 的 API 分为两种:
- Promise 实例的方法也称为Promis的实例方法
- Promise 类的方法也称为Promise的静态方法
Promise **实例**的方法:我们需要先 new 一个 promise 实例对象,然后通过 promise 实例去调用 `then`、`catch`、`finally`方法。这几个方法就是 Promise 的实例方法。
Promise 实例提供了如下方法:
- promise.then():获取异步任务的正常结果。
- promise.catch():获取异步任务的异常结果。
- promise.finaly():异步任务无论成功与否,都会执行。
我们在上一篇文章《Promise入门详解》中用到的都是Promise实例的方法。本篇文章我们来把这三个实例方法进一步详细学习一下。
## Promise 实例的 then()方法
then()方法是 Promise实例上的一个方法。它其实是放在Promise的原型上的 `Promise.prototype.then`
### then()方法的参数
then()方法可以接收一个参数,也可以接收两个参数。两个参数时,分别代表两个回调函数,这两个函数一直处于**监听状态**
- 参数1当 Promise 的状态变为 fulfilled意思是任务执行成功时会立即执行的回调函数。
- 参数2当 Promise 的状态为 rejected任务执行失败时会立即执行的回调函数。
下面这两种写法是等价的。处理 rejected 失败状态的回调函数,既可以放在 then() 方法的第二个参数里,也可以单独放在 catch() 方法的参数里。
写法1
```js
const promise = new Promise((resolve, reject) => {
reject('qianguyihao');
});
promise.then(
res => {
console.log('res:', res);
},
err => {
console.log('err:', err);
}
);
```
写法2
```js
const promise = new Promise((resolve, reject) => {
reject('qianguyihao');
});
promise
.then(res => {
console.log('res:', res);
})
.catch(err => {
console.log('err:', err);
});
```
### then()方法可以被多次调用
一个 Promise 的 then() 方法可以被多次调用。每次调用时我们都可以传入对应fulfilled状态的回调函数。当 Promise 的状态变为 fulfilled 时,这些回调函数都会被执行。
then被调用多次的伪代码
```js
const myPromise = new Promise();
myPromise.then();
myPromise.then();
myPromise.then();
```
代码举例:
```js
const myPromise = new Promise((resolve, reject) => {
resolve('qianguyihao');
});
myPromise.then(res => {
console.log('成功回调1');
console.log('res1:', res);
});
myPromise.then(res => {
console.log('成功回调2');
console.log('res2:', res);
});
myPromise.then(res => {
console.log('成功回调3');
console.log('res3', res);
});
```
打印结果:
```
成功回调1
res1: qianguyihao
成功回调2
res2: qianguyihao
成功回调3
res3 qianguyihao
```
代码解释:
当 myPromise 状态为 fulfilled 时,下面的四个 then() 方法**都在监听**,所以这四个 then() 方法都会收到状态确定的通知,进而都会执行。
此外then() 被调用多次还有一种**链式调用**的写法,它的打印结果与上面的打印结果不同,想要了解 Promise 的链式调用,需要先学习 then() 方法的返回值,我们继续往下看。
## then() 方法的返回值(重要)
> 这一段的知识点略有难度,但是非常重要,是我们学习 Promise 链式调用的理论基础。
then()方法本身是有返回值的它会返回一个新的Promise对象。因为 then()方法的返回值永远是一个 Promise 对象,所以我们才可以对它进行**链式调用**。
Promise 链式调用的伪代码:
```js
// 伪代码
myPromise.then().then().catch()
```
2023-06-08 13:15:20 +08:00
上方代码中,因为 myPromise.then() 的返回值本身就是一个 Promise所以才可以继续调用 then()、继续调用 catch()。
那么,**then()方法返回的 Promise 对象处于什么状态呢**then()方法的参数里,是一个回调函数。这取决于回调函数的返回值是什么。情况如下:
2023-06-08 13:15:20 +08:00
1、当then()方法中的回调函数在执行时那么Promise 处于pending状态。
2023-06-08 13:15:20 +08:00
2、当 then()方法中的回调函数中,手动 return 一个返回值时,那么 Promise 的状态取决于返回值的类型。当返回值这行代码执行完毕后, Promise 会立即决议,进入确定状态(成功 or 失败)。具体情况如下:
2023-06-08 13:15:20 +08:00
- 情况1如果没有返回值相当于 return undefined或者返回值是**普通值/普通对象**,那么 Promise 的状态为fulfilled。这个值会作为then()回调的参数。
2023-06-08 13:15:20 +08:00
- 情况2如果返回值是**另外一个新的 Promise**,那么原 Promise 的状态将**交给新的 Promise 决定**。
- 情况3如果返回值是一个对象并且这个对象里有实现then()方法(这种对象称为 **thenable** 对象那就会执行该then()方法,并且根据**then()方法的结果来决定Promise的状态**。
还有一种特殊情况:
- 情况4当then()方法传入的回调函数遇到异常或者手动抛出异常时,那么, Promise 处于rejected 状态。
2023-06-08 13:15:20 +08:00
**小结**then()方法里,我们可以通过 return **传递结果和状态**给下一个新的Promise。
2023-06-08 13:15:20 +08:00
### 默认返回值
如果then()方法的回调函数里没写返回值(相当于 return undefined那么then()方法的返回值是一个新的Promise。新 Promise 的状态为fulfilled其then()方法里res的值为 undefined。
then() 链式调用的代码举例:
```js
const myPromise = new Promise((resolve, reject) => {
resolve('qianguyihao');
});
myPromise
.then(res => {
console.log('成功回调1');
console.log('res1:', res);
/*
这里虽然什么都没写,底层默认写了如下代码:
return new Promise((resolve, reject) => {
2023-05-21 21:20:23 +08:00
resolve(); // resolve() 的参数是空,相当于 resolve(undefined)
})
*/
})
.then(res => {
console.log('成功回调2');
console.log('res2:', res);
})
.then(res => {
console.log('成功回调3');
console.log('res3', res);
});
```
打印结果:
```
成功回调1
res1: qianguyihao
成功回调2
res2: undefined
成功回调3
res3undefined
```
代码解释:
第一个 then()里的回调,是由 myPromise 进行决议。第二个then()、第三个then() 也在**等待决议**。
但是,**第二个 then() 的回调是由第一个 then()传入的回调函数,返回的 Promise 进行决议**;第三个 then() 的回调是由第二个 then()传入的回调函数,返回的 Promise 进行决议以此类推。所以这两个then()里面的打印参数的结果是 undefined并没有打印 myPromise 的决议结果。
换句话说,第一个 then() 在等待 myPromise 的决议结果,有决议结果后执行;第二个 then() 在等待第一个 then()参数里返回的新 Promise的决议结果有决议结果后执行第三个 then() 在等待第二个 then()参数里返回的新 Promise的决议结果有决议结果后执行。
### 返回普通值
我们也可以在 then()方法的回调函数里,手动 return 自己想要的数据,比如一个普通值 value1。这个普通值就可以传递给下一个新的Promise。新 Promise 的状态为fulfilled其then()方法里res的值为 value1。
代码举例:
```js
const myPromise = new Promise((resolve, reject) => {
resolve('1号');
});
myPromise
.then(res => {
console.log('res1:', res);
// return一个普通值把这个值传递给下一个Promise
return '2号';
/*
上面这行 return相当于
return new Promise((resolve, reject)=> {
resolve('2号');
})
*/
})
.then(res => {
// res可以接收到上一个 Promise 传递的值
console.log('res2:', res);
})
.then(res => {
console.log('res3:', res);
});
```
返回结果:
```
res1: 1号
res2: 2号
res3: undefined
```
### 返回新的 Promise
1、在 then() 方法中return一个成功的Promise代码举例
```js
const myPromise = new Promise((resolve, reject) => {
resolve('qianguyihao fulfilled 1');
});
const myPromise2 = new Promise((resolve, reject) => {
resolve('qianguyihao fulfilled 2');
});
myPromise
.then(res => {
console.log('res1:', res);
return myPromise2;
})
.then(res => {
// 监听 myPromise2 的成功状态
console.log('res2:', res);
})
.then(res => {
console.log('res3', res);
});
```
打印结果:
```
res1: qianguyihao fulfilled 1
res2: qianguyihao fulfilled 2
res3 undefined
```
2、通过then()的第二个参数走了失败的回调函数后再走then() 会怎么样?代码举例:
```js
const myPromise = new Promise((resolve, reject) => {
resolve('qianguyihao fulfilled 1');
});
const myPromise2 = new Promise((resolve, reject) => {
reject('qianguyihao rejected 2');
});
myPromise
.then(res => {
console.log('res1:', res);
return myPromise2;
})
.then(res => {
console.log('res2:', res);
}, err => {
// 如果 myPromise2 为失败状态,可以通过 then() 的第二个参数(即失败的回调函数)捕获异常,然后就可以继续往下执行其他 Promise
console.log('err2:', err);
// 这里相当于 return undefined
})
.then(res => {
console.log('res3', res);
}, err => {
console.log('err3:', err);
});
```
打印结果:
```
res1: qianguyihao fulfilled 1
err2: qianguyihao rejected 2
res3: undefined
```
上方代码可以看到,最后一个 Promise 走的是成功回调而不是失败回调。这例子一定要记住。这说明了在多个Promise的链式调用里**如果中间的某个Promise 执行失败,还想让剩下的其他 Promise 顺利执行**的话,那就请在中间**那个失败的Promise里加一个失败的回调函数**捕获异常后便可继续往下执行其他的Promise。
### 返回 thenable 对象
代码举例:
```js
const myPromise = new Promise((resolve, reject) => {
resolve('qianguyihao fulfilled 1');
});
myPromise
.then(res => {
console.log('res1:', res);
return {
then: (resolve, reject) => {
resolve('thenable fulfilled');
},
};
})
.then(res => {
console.log('res2:', res);
})
.then(res => {
console.log('res3', res);
});
```
打印结果:
```
res1: qianguyihao fulfilled 1
res2: thenable fulfilled
res3 undefined
```
## Promise 实例的 catch() 方法
catch()方法是 Promise实例上的一个方法。它其实是放在Promise的原型上的 `Promise.prototype.catch`
### catch() 方法的参数
catch()方法可以接收一个参数。这个参数是一直处于**监听状态**的回调函数。当 Promise 的状态为 rejected任务执行失败时会立即执行这个回调函数。
代码举例:
```js
const promise = new Promise((resolve, reject) => {
reject('qianguyihao reject');
});
promise
.then(res => {
console.log('res:', res);
})
.catch(err => {
console.log('err:', err);
});
```
打印结果:
```
err: qianguyihao reject
```
### catch() 方法可以被多次调用
一个 Promise 的 catch() 方法可以被多次调用。每次调用时我们都可以传入对应 rejected 状态的回调函数。当 Promise 的状态变为 rejected 时,这些回调函数都会被执行。
catch() 被调用多次的伪代码:
```js
const myPromise = new Promise();
myPromise.catch();
myPromise.catch();
myPromise.catch();
```
代码举例:
```js
const myPromise = new Promise((resolve, reject) => {
reject('qianguyihao rejected');
});
myPromise.catch(err => {
console.log('失败回调1');
console.log('err1:', err);
});
myPromise.catch(err => {
console.log('失败回调2');
console.log('err2:', err);
});
myPromise.catch(err => {
console.log('失败回调3');
console.log('err3:', err);
});
```
打印结果:
```
失败回调1
err1: qianguyihao rejected
失败回调2
err2: qianguyihao rejected
失败回调3
err3: qianguyihao rejected
```
代码解释:
当 myPromise 状态为 rejected 时,下面的四个 catch() 方法**都在监听**,所以这四个 catch() 方法都会收到状态确定的通知,进而都会执行。
## catch() 方法的返回值(重要)
与 then() 方法类似catch()方法默认也是有返回值的,它会**返回一个新的Promise对象**。因为 catch()方法的返回值永远是一个 Promise 对象,所以我们才可以对它进行**链式调用**。
Promise 链式调用的伪代码:
```js
// 伪代码
myPromise.then().then().catch().then()
```
上方代码中,因为 myPromise.catch() 的返回值本身就是一个 Promise所以才可以继续调用 then()、继续调用 catch()。
与 then() 方法类似,**catch()方法返回的 Promise 对象处于什么状态呢**catch()方法的参数里,是一个回调函数。这取决于回调函数的返回值是什么。情况如下:
1、当catch()方法中的回调函数在执行时那么Promise 处于 pending 状态。
2、当 catch方法中的回调函数中手动 return 一个返回值时,那么 Promise 的状态取决于返回值的类型。当返回值这行代码执行完毕后, Promise 会立即决议,进入确定状态(成功 or 失败进而触发下一个then/catch 函数的执行。同时可以给下一个 then/catch 传递参数。具体情况如下:
- 情况1如果没有返回值相当于 return undefined或者返回值是**普通值/普通对象**,那么 Promise 的状态为fulfilled。这个值会作为then()回调的参数。
- 情况2如果返回值是**另外一个新的 Promise**,那么原 Promise 的状态将**交给新的 Promise 决定**。
- 情况3如果返回值是一个对象并且这个对象里有实现then()方法(这种对象称为 **thenable** 对象那就会执行该then()方法,并且根据**then()方法的结果来决定Promise的状态**。
还有一种特殊情况:
- 情况4当catch()方法传入的回调函数遇到异常或者手动抛出异常时,那么, Promise 处于rejected 状态。
**小结**then()方法里,我们可以通过 return **传递结果**给下一个新的Promise。
### 默认返回值
如果catch()方法的回调函数里没写返回值(相当于 return undefined那么catch()方法的返回值是一个新的Promise。新 Promise 的状态为fulfilled其then()方法里res的值为 undefined。
代码举例:
```js
const myPromise = new Promise((resolve, reject) => {
reject('qianguyihao rejected');
});
myPromise
.catch(err => {
console.log('err:', err);
/*
这里虽然什么都没写,底层默认写了如下代码:
return new Promise((resolve, reject) => {
resolve(); // resolve() 的参数是空
})
*/
})
.then(res => {
console.log('res:', res);
});
```
打印结果:
```
err: qianguyihao rejected
res: undefined
```
### 返回普通值
我们也可以在 catch()方法的回调函数里,手动 return 自己想要的数据,比如一个普通值 value1。这个普通值就可以传递给下一个新的Promise。新 Promise 的状态为fulfilled其then()方法里res的值为 value1。
代码举例:
```js
const myPromise = new Promise((resolve, reject) => {
reject('1号');
});
myPromise
.catch(err => {
console.log('err1:', err);
return '2号';
/*
上面这行 return相当于
return new Promise((resolve, reject)=> {
resolve('2号');
})
*/
})
.then(res => {
console.log('res2:', res);
})
.then(res => {
console.log('res3:', res);
});
```
返回结果:
```
err1: 1号
res2: 2号
res3: undefined
```
2023-05-21 21:20:23 +08:00
## catch() 方法的执行时机
### 找到最近的 catch() 去执行
我们先来看一段代码:
```js
const myPromise = new Promise((resolve, reject) => {
reject('qianguyihao rejected');
});
myPromise
.then(res => {
console.log('res1:', res);
})
.then(res => {
console.log('res2:', res);
})
.catch(err => {
console.log('err:', err);
});
```
打印结果:
```
err: qianguyihao rejected
```
上方代码中的 catch() 是属于哪个 Promise 实例的方法呢其实没有严格的界限。myPromise 的状态进入 rejected之后它会找到**最近的catch()**去执行。这是 Promise的内部机制。
### Promise 抛出 rejected 异常时,一定要捕获并处理
当 Promise 状态为 rejected 时,表示抛出异常,如果不处理失败的回调,行不行呢?不行,会报错。代码举例:
```js
const promise = new Promise((resolve, reject) => {
// 在这里抛出异常
reject('qianguyihao reject');
});
promise.then(res => {
console.log('res:', res);
});
```
![image-20230521135912267](https://img.smyhvae.com/image-20230521135912267.png)
这个报错的意思是:未捕获 rejected 失败状态的 Promise 异常。必须要加一个 catch() 进行捕获。
书写 Promise 时,比较好的习惯是,无论如何都要在末尾写一个 catch() 方法。
### 可在 then() 中通过 throw 抛出异常
先来看一段代码:
```js
const myPromise = new Promise((resolve, reject) => {
resolve('aaa');
});
myPromise
.then(res => {
console.log('res1:', res);
// 如果我想在这里 return 一个失败状态的promise该怎么做
})
.then(res => {
console.log('res2:', res);
})
.catch(err => {
console.log('err:', err);
});
```
2023-05-21 21:44:41 +08:00
注意看注释如果在那个位置return 一个失败状态的Promise该怎么做
2023-05-21 21:20:23 +08:00
做法1
```js
return new Promise((resolve, reject)=> {
reject('第二个 promise 执行失败');
})
```
做法2
```js
throw new Error('第二个 Promise 执行失败');
```
做法2比做法1更为常用完整代码如下
```js
const myPromise = new Promise((resolve, reject) => {
resolve('aaa');
});
myPromise
.then(res => {
console.log('res1:', res);
2023-05-21 21:44:41 +08:00
// 抛出异常:相当于 return 一个失败状态的 Promise
2023-05-21 21:20:23 +08:00
throw new Error('第二个 Promise 执行失败');
})
.then(res => {
console.log('res2:', res);
})
.catch(err => {
console.log('err:', err);
});
```
打印结果:
```
res1: aaa
err: Error: 第二个 Promise 执行失败
```
当通过 throw 抛出异常后,当前 then() 里的后续代码会暂停执行,后续的 then() 也会暂停执行,直接往后走到最近的 catch()。
throw 这种写法在实战开发中很常用,需要理解并记住。
2023-05-21 21:44:41 +08:00
## Promise 实例的 finally() 方法
2023-05-21 21:20:23 +08:00
2023-05-21 21:44:41 +08:00
finally() 方法是在ES9ES 2018中新增的一个特性表示 Promise 对象无论变成 fulfilled 状态 还是 rejected 状态finally() 里传入的回调函数都会被执行。
2023-05-21 21:20:23 +08:00
2023-05-21 21:44:41 +08:00
finally() 里可传入一个参数,这个参数是一个回调函数。回调函数不传参数,因为前面无论是 fulfilled 状态,还是 rejected状态这个回调函数都会执行。
2023-05-21 21:20:23 +08:00
2023-05-21 21:44:41 +08:00
finally() 方法很实用,可以避免我们写很多重复代码,它的执行时机也有很重要的应用场景。
代码举例:
```js
const promise1 = new Promise((resolve, reject) => {
resolve('promise1 fulfilled');
});
const promise2 = new Promise((resolve, reject) => {
reject('promise2 rejected');
});
promise1
.then(res => {
console.log('res1:', res);
})
.catch(err => {
console.log('err1:', err);
})
.finally(() => {
console.log('promise1 决议后都会执行的代码');
});
promise2
.then(res => {
console.log('res2:', res);
})
.catch(err => {
console.log('err2:', err);
})
.finally(() => {
console.log('promise2 决议后都会执行的代码');
});
```
打印结果:
```
res1: promise1 fulfilled
err2: promise2 rejected
promise1 决议后都会执行的代码
promise2 决议后都会执行的代码
```
## 处理 rejected 失败状态的两种写法
> 这一段是针对本文知识点的回顾和应用。
我们有两种写法可以捕获并处理 reject 异常状态:
- 写法 1通过 catch 方法捕获 状态变为已 reject 时的 promise
- 写法 2then 可以传两个参数,第⼀个参数为 resolve 后执⾏,第⼆个参数为 reject 后执⾏。
### 代码格式
这两种写法的**代码格式**如下:
```js
// 第一步model层的接口封装
function promiseA() {
return new Promise((resolve, reject) => {
// 这里做异步任务(比如 ajax 请求接口,或者定时器)
...
...
});
}
const onResolve = function (res) {
console.log(res);
};
const onReject = function (err) {
console.log(err);
};
// 写法1通过 catch 方法捕获 状态变为已拒绝时的 promise
promiseA().then(onResolve).catch(onReject);
// 写法2then 可以传两个参数,第⼀个参数为 resolve 后执⾏,第⼆个参数为 reject 后执⾏
promiseA().then(onResolve, onReject);
// 【错误写法】写法3通过 try catch 捕获 状态变为已拒绝时的 promise
// 这种写法是错误的,因为 try catch只能捕获同步代码里的异常而 promise.reject() 是异步代码。
try {
promiseA().then(onResolve);
} catch (e) {
// 语法上catch必须要传入一个参数否则报错
onReject(e);
}
```
如注释所述:前面的段落里,我们捕获 reject 异常用的就是写法 1。如果你写法 2 也是可以的。
需要注意的是,上面的写法 3 是错误的。运行之后,控制台会报如下错误:
![](http://img.smyhvae.com/20210430_1553.png)
[解释如下](https://blog.csdn.net/xiaoluodecai/article/details/107297404)
try-catch 主要用于捕获异常,注意,这里的异常是指**同步**函数的异常。如果 try 里面的异步方法出现了异常,此时 catch 是无法捕获到异常的。
原因是:当异步函数抛出异常时,对于宏任务而言,执行函数时已经将该函数推入栈,此时并不在 try-catch 所在的栈,所以 try-catch 并不能捕获到错误。对于微任务而言(比如 promisepromise 的构造函数的异常只能被自带的 reject 也就是.catch 函数捕获到。
2写法 1 中,`promiseA().then().catch()`和`promiseA().catch().then()`区别在于:前者可以捕获到 `then` 里面的异常,后者不可以。
### 代码举例
这两种写法在实战开发中的**代码举例**如下:
```js
function promiseA() {
return new Promise((resolve, reject) => {
// 这里做异步任务(比如 ajax 请求接口,或者定时器)
...
...
});
}
// 写法1
promiseB()
.then((res) => {
// 从 resolve 获取正常结果
console.log('接口请求成功时,走这里');
console.log(res);
})
.catch((err) => {
// 从 reject 获取异常结果
console.log('接口请求失败时,走这里');
console.log(err);
})
.finally(() => {
console.log('无论接口请求成功与否,都会走这里');
});
// 写法 2和写法 1 等价)
promiseB()
.then(
(res) => {
// 从 resolve 获取正常结果
console.log('接口请求成功时,走这里');
console.log(res);
},
(err) => {
// 从 reject 获取异常结果
console.log('接口请求失败时,走这里');
console.log(err);
}
)
.finally(() => {
console.log('无论接口请求成功与否,都会走这里');
});
```
**代码解释**:写法 1 和写法 2 的作用是完全等价的。只不过,写法 2 是把 catch 里面的代码作为 then 里面的第二个参数而已。
2023-05-21 21:20:23 +08:00
## Promise 规范
Promise 是⼀个拥有 then ⽅法的对象或函数。任何符合 promise 规范的对象或函数都可以成为 Promise。
关于 promise 规范的详细解读,可以看下面这个链接:
- Promises/A+ 规范:<https://promisesaplus.com/>
2023-05-21 22:17:52 +08:00
## 赞赏作者
创作不易,你的赞赏和认可,是我更新的最大动力:
![](https://img.smyhvae.com/20220401_1800.jpg)