Web/06-JavaScript异步编程:Ajax和Promise/07-Promise的静态方法.md
2021-09-24 20:02:27 +08:00

536 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 07-Promise的静态方法
publish: true
---
<ArticleTopAd></ArticleTopAd>
## Promise 的常用 API 分类
### Promise 的实例方法
**实例方法**我们需要先 new 一个 promise 实例对象然后通过 promise 对象去调用 `then``catch``finally`方法这几个方法就是 Promise 的实例方法
Promise 的自带 API 提供了如下实例方法
- promise.then()获取异步任务的正常结果
- promise.catch()获取异步任务的异常结果
- promise.finaly()异步任务无论成功与否都会执行
### Promise 的静态方法
前面的几篇文章讲的都是 Promise **实例方法**今天这篇文章我们来详细讲一下 Promise **静态方法**
**静态方法**可以直接通过大写的`Promise.xxx`调用的方法这里的`xxx`就称之为静态方法
Promise 的自带 API 提供了如下静态方法
- `Promise.resolve()`
- `Promise.reject()`
- `Promsie.all()`并发处理多个异步任务所有任务都执行成功才算成功走到 resolve只要有一个失败就会马上走到 reject整体都算失败
- `Promise.race()`并发处理多个异步任务返回的是第一个执行完成的 promise且状态和第一个完成的任务状态保持一致
- `Promise.allSettled()`并发处理多个异步任务返回所有任务的执行结果包括成功失败当你有多个彼此不依赖的异步任务执行完成时或者你想知道每个 promise 的结果时通常使用它
- `Promise.all()`
- `Promise.any()`
## Promise.resolve() Promise.reject()
当我们在定义一个 promise 的过程中如果涉及到异步操作那就需要通过`new Promise`的方式创建一个 Promise 实例
但有些场景下我们并没有异步操作**仍然想调用 promise.then**此时我们可以用 `Promise.resolve()` 将其包装成成功的状态同理`Promise.reject()`可以包装成失败的状态
比如说有的时候promise 里面并不涉及异步操作我只是**单纯地想通过 promise 对象返回一个字符串**有的业务就是有这样的需求那就可以通过 `Promise.reslove('字符串')` `Promise.reject('字符串')` 这种**简写**的方式返回
这两种情况我们来对比看看
1
```js
function foo(flag) {
if (flag) {
return new Promise((resolve) => {
// 这里可以做异步操作
resolve('success');
});
// return Promise.resolve('success2');
} else {
return new Promise((reslove, reject) => {
// 这里可以做异步操作
reject('fail');
});
}
}
// 执行 reslove 的逻辑
foo(true).then((res) => {
console.log(res);
});
// 执行 reject 的逻辑
foo(false).catch((err) => {
console.log(err);
});
```
2见证奇迹的时刻
```js
function foo(flag) {
if (flag) {
// Promise的静态方法直接返回字符串
return Promise.resolve('success');
} else {
// Promise的静态方法直接返回字符串
return Promise.reject('fail');
}
}
// 执行 reslove 的逻辑
foo(true).then((res) => {
console.log(res);
});
// 执行 reject 的逻辑
foo(false).catch((err) => {
console.log(err);
});
```
1 和例 2 的打印结果是一样的这两段代码的区别在于 1 里面可以封装异步任务 2 只能单纯的返回一个字符串等变量不能封装异步任务
## Promise.all()
`Promsie.all([p1, p2, p3])`并发处理多个异步任务所有任务都执行成功才算成功才会走到 then只要有一个任务失败就会马上走到 catch整体都算失败参数里传的是 多个 promise 实例组成的数组
### 语法举例
**1异步任务都执行成功时**
```js
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise1');
resolve('promise 1 成功');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise2');
resolve('promise 2 成功');
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise3');
resolve('promise 3 成功');
}, 3000);
});
Promise.all([promise1, promise2, promise3])
.then((res) => {
// 三个异步任务都执行成功,才会走到这里
// 这里拿到的 res是三个成功的返回结果组成的数组
console.log(JSON.stringify(res));
})
.catch((err) => {
// 只要有一个异步任务执行失败,就会马上走到这里
console.log(err);
});
```
打印结果
```js
// 1秒后
执行 promise1
// 2秒后
执行 promise2
// 3秒后
执行 promise3
["promise 1 成功","promise 2 成功","promise 3 成功"]
```
**2异步任务有至少一个执行失败时**
```js
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise1');
resolve('promise 1 成功');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise2');
// 这里通过 reject() 的方式,表示任务执行失败
reject('promise 2 失败');
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise3');
resolve('promise 3 成功');
}, 3000);
});
Promise.all([promise1, promise2, promise3])
.then((res) => {
// 三个异步任务都执行成功,才会走到这里
console.log('走到 then:' + JSON.stringify(res));
})
.catch((err) => {
// 只要有一个异步任务执行失败,就会马上走到这里
console.log('走到 catch:' + err);
});
```
打印结果
```js
// 1秒后
执行 promise1
// 2秒后
执行 promise2
走到 catch:promise 2 失败
// 3秒后
执行 promise3
```
可以看到 promise2 执行失败之后马上就走到了 catch而且 promise3 里的 resolve 并没有执行
### Promise.all()举例多张图片上传
比如说现在有一个**图片上传**的接口每次请求接口时只能上传一张图片需求是当用户连续上传完九张图片正好凑齐九宫格之后给用户一个上传成功的提示这个时候我们就可以使用`Promsie.all()`
代码举例如下
```js
const imgArr = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg', '6.jpg', '7.jpg', '8.jpg', '9.jpg'];
const promiseArr = [];
imgArr.forEach((item) => {
const p = new Promise((resolve, reject) => {
// 在这里做图片上传的异步任务。图片上传成功后,接口会返回图片的 url 地址
// upload img ==> return imgUrl
if (imgUrl) {
// 单张图片上传完成
resolve(imgUrl);
} else {
reject('单张图片上传失败');
}
});
promiseArr.push(p);
});
Promise.all(promiseArr)
.then((res) => {
console.log('图片全部上传完成');
console.log('九张图片的url地址组成的数组' + res);
})
.catch((res) => {
console.log('部分图片上传失败');
});
```
上方代码解释
1只有九张图片都上传成功才会走到 then
2按时间顺序来看假设第一张图片上传成功第二张图片上传失败那么最终的表现是
- 对于前端来说九张图都会走到 reject整体会走到 catch不会走到 then
- 对于后端来说第一张图片会上传成功因为写入 DB 是不可逆的第二张图上传失败剩下的七张图会正常请求 upload img 接口
3**特别说明**
- 第一张图会成功调 upload 接口并返回 imgUrl但不会走到 resolve因为要等其他八张图的执行结果再决定是一起走 resolove 还是一起走 reject
- 当执行 Promise.all() / Promise.race() / Promise.any() 的时候**其实九张图的 upload img 请求都已经发出去了**对于后端来说是没有区别的而且读写 DB 的操作不可逆只是在前端的交互表现不同走到 resolve / reject / then / catch 的时机不同而已
上面这个例子在实际的项目开发中经常遇到属于高频需求需要记住并理解
4**思维拓展**
- 拓展 1如果你希望九张图同时上传并且想知道哪些图上传成功哪些图上传失败则可以这样做**无论 upload img 接口请求成功与否全都执行 reslove**这样的话最终一定会走到 then然后再根据接口返回的结果判断九张图片的上传成功与否
- 拓展 2实战开发中在做多张图片上传时可能是一张一张地单独上传各自的上传操作相互独立此时 `Promise.all`便不再适用这就得具体需求具体分析了
## Promise.race()
`Promise.race([p1, p2, p3])`并发处理多个异步任务返回的是第一个执行完成的 promise且状态和第一个完成的任务状态保持一致参数里传的是多个 promise 实例组成的数组
上面这句话第一次读时可能很绕口我说的再通俗一点在多个同时执行的异步任务中先找出哪个异步任务**最先执行完成**无论是走到 resolve还是走到 reject都算执行完成整体的状态就跟这个任务保持一致如果这个任务执行成功那整体就算成功走到 then如果这个任务执行失败那整体就算失败走到 catch
`race`的中文翻译可以理解为竞赛意思是谁先抢到名额就认定谁了无论这个人最终的结局是成功或者失败整体的结局都以这个人的结局为准
我刚开始学 Promise.race()的时候误以为它的含义是只要有一个异步**执行成功**整体就算成功走到 then所有任务都执行失败整体才算失败走到 catch现在想来真是大错特错过于懵懂
现在我顿悟了准确来说Promise.race()强调的是只要有一个异步任务**执行完成**整体就是**完成**
Promise.race()**应用场景**在众多 Promise 实例中最终结果只取一个 Promise**谁返回得最快就用谁的 Promise**
我们来看看各种场景的打印结果便能擦干泪水继续前行
### 语法举例
**场景 1所有任务都执行成功时**
```js
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise1');
resolve('promise 1 成功');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise2');
resolve('promise 2 成功');
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise3');
resolve('promise 3 成功');
}, 3000);
});
Promise.race([promise1, promise2, promise3])
.then((res) => {
// 第一个完成的任务,如果执行成功,就会走到这里
// 这里拿到的 res是第一个成功的 promise 返回的结果,不是数组
console.log(JSON.stringify(res));
})
.catch((err) => {
// 第一个完成的任务,如果执行失败,就会走到这里
console.log(err);
});
```
打印结果
```js
// 1秒后
执行 promise1
"promise 1 成功"
// 2秒后
执行 promise2
// 3秒后
执行 promise3
```
**场景 2第一个任务成功第二个任务失败时**
```js
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise1');
resolve('promise 1 成功');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise2');
// 第二个任务执行失败时
reject('promise 2 失败');
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise3');
resolve('promise 3 成功');
}, 3000);
});
Promise.race([promise1, promise2, promise3])
.then((res) => {
// 第一个完成的任务,如果执行成功,就会走到这里
console.log('走到then:' + res);
})
.catch((err) => {
// 第一个完成的任务,如果执行失败,就会走到这里
console.log('走到catch:' + err);
});
```
打印结果
```js
// 1秒后
执行 promise1
走到then:promise 1 成功
// 2秒后
执行 promise2
// 3秒后
执行 promise3
```
可以看出场景 2 的打印结果和场景 1 的打印结果是一样的因为最新执行完成的任务是成功的所以整体会马上走到 then且整体就算成功
**场景 3第一个任务失败第二个任务成功时**
```js
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise1');
// 第一个任务执行失败时
reject('promise 1 失败');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise2');
resolve('promise 2 成功');
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise3');
resolve('promise 3 成功');
}, 3000);
});
Promise.race([promise1, promise2, promise3])
.then((res) => {
// 第一个完成的任务,如果执行成功,就会走到这里
console.log('走到then:' + res);
})
.catch((err) => {
// 第一个完成的任务,如果执行失败,就会走到这里
console.log('走到catch:' + err);
});
```
打印结果
```js
// 1秒后
执行 promise1
走到catchpromise 1 失败
// 2秒后
执行 promise2
// 3秒后
执行 promise3
```
看清楚了没场景 3 的最终打印结果是走到了 catch任务 2 和任务 3 里的 resolve并没有继续执行
场景 3 的代码一定好好好理解
### Promise.race()举例图片加载超时
现在有个需求是这样的前端需要加载并显示一张图片如果图片在三秒内加载成功那就显示图片如果三秒内没有加载成功那就按异常处理前端提示加载超时或者请求超时
代码实现
```js
// 图片请求的Promise
function getImg() {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = function () {
// 图片的加载,是异步任务
resolve(img);
};
img.src = 'https://img.smyhvae.com/20200102.png';
});
}
// 加载超时的 Promise
function timeout() {
return new Promise((resolve, reject) => {
// 采用 Promise.race()之后,如果 timeout() 的 promise 比 getImg() 的 promise先执行说明定时器时间到了那就算超时。整体的最终结果按失败处理。
setTimeout(() => {
reject('图片加载超时');
}, 3000);
});
}
Promise.race([getImg(), timeout()])
.then((res) => {
// 图片加载成功
console.log(res);
})
.catch((err) => {
// 图片加载超时
console.log(err);
});
```
如代码注释所述采用 Promise.race() 之后如果 timeout() promise getImg() promise 先执行说明定时器时间到了那就算超时整体的最终结果按失败处理
这个思路很巧妙用同样的思路我们还可以处理网络请求超时的问题如果接口请求时长超过 3 就按超时处理也就是下面我们要举的例子
### Promise.race()举例网络请求超时
现在有这种需求如果接口请求时长超过 3 就按超时处理
基于这种需求我们可以用 Promise.race() 来实现一个 Promise 用于 请求接口另一个 Promise 用于执行 setTimeout()把这两个 Promise Promise.race()组装在一起谁先执行那么最终的结果就以谁的为准
代码举例
```js
function query(url, delay = 4000) {
let promiseArr = [
myAajax(url),
new Promise((resolve, reject) => {
setTimeout(() => {
reject('网络请求超时');
}, delay);
}),
];
return Promise.race(promiseArr);
}
query('http://localhost:8899/xxx_url', 3000)
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
});
```
## 总结
Promise 不仅能解决嵌套异步任务的**回调地域**问题也可做多个异步任务的**并发请求**还可以进行舒适简洁的状态管理
Promise 本身不是异步的但是它可以封装异步任务并对异步操作进行良好的状态管理这便是 Promise 的魅力所在
## 我的公众号
想学习**更多技能**不妨关注我的微信公众号**千古壹号**
扫一扫你将发现另一个全新的世界而这将是一场美丽的意外
![](https://img.smyhvae.com/20200102.png)