记录--整会promise这8个高级用法
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
发现很多人还只会promise常规用法
在js项目中,promise的使用应该是必不可少的,但我发现在同事和面试者中,很多中级或以上的前端都还停留在promiseInst.then()、promiseInst.catch()、Promise.all等常规用法,连async/await也只是知其然,而不知其所以然。
但其实,promise还有很多巧妙的高级用法,也将一些高级用法在alova请求策略库内部大量运用。
现在,我把这些毫无保留地在这边分享给大家,看完你应该再也不会被问倒了,最后还有压轴题哦。
觉得对你有帮助还请点赞收藏评论哦!
1. promise数组串行执行
例如你有一组接口需要串行执行,首先你可能会想到使用await
const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];
for (const requestItem of requestAry) {
await promiseItem();
}
如果使用promise的写法,那么你可以使用then函数来串联多个promise,从而实现串行执行。
const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];
const finallyPromise = requestAry.reduce(
(currentPromise, nextRequest) => currentPromise.then(() => nextRequest()),
Promise.resolve(); // 创建一个初始promise,用于链接数组内的promise
);
2. 在new Promise作用域外更改状态
假设你有多个页面的一些功能需要先收集用户的信息才能允许使用,在点击使用某功能前先弹出信息收集的弹框,你会怎么实现呢?
以下是不同水平的前端同学的实现思路:
初级前端:我写一个模态框,然后复制粘贴到其他页面,效率很杠杠的!
中级前端:你这不便于维护,我们要单独封装一下这个组件,在需要的页面引入使用!
高级前端:封什么装什么封!!!写在所有页面都能调用的地方,一个方法调用岂不更好?
看看高级前端怎么实现的,以vue3为例来看看下面的示例。
<!-- App.vue -->
<template> <!-- 以下是模态框组件 -->
<div class="modal" v-show="visible">
<div>
用户姓名:<input v-model="info.name" />
</div>
<!-- 其他信息 -->
<button @click="handleCancel">取消</button>
<button @click="handleConfirm">提交</button>
</div> <!-- 页面组件 -->
</template> <script setup>
import { provide } from 'vue'; const visible = ref(false);
const info = reactive({
name: ''
});
let resolveFn, rejectFn; // 将信息收集函数函数传到下面
provide('getInfoByModal', () => {
visible.value = true;
return new Promise((resolve, reject) => {
// 将两个函数赋值给外部,突破promise作用域
resolveFn = resolve;
rejectFn = reject;
});
}) const handleConfirm = info => {
resolveFn && resolveFn(info);
};
const handleCancel = () => {
rejectFn && rejectFn(new Error('用户已取消'));
};
</script>
接下来直接调用getInfoByModal即可使用模态框,轻松获取用户填写的数据。
<template>
<button @click="handleClick">填写信息</button>
</template> <script setup>
import { inject } from 'vue'; const getInfoByModal = inject('getInfoByModal');
const handleClick = async () => {
// 调用后将显示模态框,用户点击确认后会将promise改为fullfilled状态,从而拿到用户信息
const info = await getInfoByModal();
await api.submitInfo(info);
}
</script>
这也是很多UI组件库中对常用组件的一种封装方式。
3. async/await的另类用法
很多人只知道在async函数调用时用await接收返回值,但不知道async函数其实就是一个返回promise的函数,例如下面两个函数是等价的:
const fn1 = async () => 1;
const fn2 = () => Promise.resolve(1); fn1(); // 也返回一个值为1的promise对象
而await在大部分情况下在后面接promise对象,并等待它成为fullfilled状态,因此下面的fn1函数等待也是等价的:
await fn1(); const promiseInst = fn1();
await promiseInst;
然而,await还有一个鲜为人知的秘密,当后面跟的是非promise对象的值时,它会将这个值使用promise对象包装,因此await后的代码一定是异步执行的。如下示例:
Promise.resolve().then(() => {
console.log(1);
});
await 2;
console.log(2);
// 打印顺序位:1 2
等价于
Promise.resolve().then(() => {
console.log(1);
});
Promise.resolve().then(() => {
console.log(2);
});
4. promise实现请求共享
当一个请求已发出但还未响应时,又发起了相同请求,就会造成了请求浪费,此时我们就可以将第一个请求的响应共享给第二个请求。
request('GET', '/test-api').then(response1 => {
// ...
});
request('GET', '/test-api').then(response2 => {
// ...
});
上面两个请求其实只会真正发出一次,并且同时收到相同的响应值。
那么,请求共享会有哪几个使用场景呢?我认为有以下三个:
- 当一个页面同时渲染多个内部自获取数据的组件时;
- 提交按钮未被禁用,用户连续点击了多次提交按钮;
- 在预加载数据的情况下,还未完成预加载就进入了预加载页面;
这也是alova的高级功能之一,实现请求共享需要用到promise的缓存功能,即一个promise对象可以通过多次await获取到数据,简单的实现思路如下:
const pendingPromises = {};
function request(type, url, data) {
// 使用请求信息作为唯一的请求key,缓存正在请求的promise对象
// 相同key的请求将复用promise
const requestKey = JSON.stringify([type, url, data]);
if (pendingPromises[requestKey]) {
return pendingPromises[requestKey];
}
const fetchPromise = fetch(url, {
method: type,
data: JSON.stringify(data)
})
.then(response => response.json())
.finally(() => {
delete pendingPromises[requestKey];
});
return pendingPromises[requestKey] = fetchPromise;
}
5. 同时调用resolve和reject会怎么样?
大家都知道promise分别有pending/fullfilled/rejected三种状态,但例如下面的示例中,promise最终是什么状态?
const promise = new Promise((resolve, reject) => {
resolve();
reject();
});
正确答案是fullfilled状态,我们只需要记住,promise一旦从pending状态转到另一种状态,就不可再更改了,因此示例中先被转到了fullfilled状态,再调用reject()也就不会再更改为rejected状态了。
6. 彻底理清then/catch/finally返回值
先总结成一句话,就是以上三个函数都会返回一个新的promise包装对象,被包装的值为被执行的回调函数的返回值,回调函数抛出错误则会包装一个rejected状态的promise。,好像不是很好理解,我们来看看例子:
// then函数
Promise.resolve().then(() => 1); // 返回值为 new Promise(resolve => resolve(1))
Promise.resolve().then(() => Promise.resolve(2)); // 返回 new Promise(resolve => resolve(Promise.resolve(2)))
Promise.resolve().then(() => {
throw new Error('abc')
}); // 返回 new Promise(resolve => resolve(Promise.reject(new Error('abc'))))
Promise.reject().then(() => 1, () = 2); // 返回值为 new Promise(resolve => resolve(2)) // catch函数
Promise.reject().catch(() => 3); // 返回值为 new Promise(resolve => resolve(3))
Promise.resolve().catch(() => 4); // 返回值为 new Promise(resolve => resolve(调用catch的promise对象)) // finally函数
// 以下返回值均为 new Promise(resolve => resolve(调用finally的promise对象))
Promise.resolve().finally(() => {});
Promise.reject().finally(() => {});
7. then函数的第二个回调和catch回调有什么不同?
promise的then的第二个回调函数和catch在请求出错时都会被触发,咋一看没什么区别啊,但其实,前者不能捕获当前then第一个回调函数中抛出的错误,但catch可以。
Promise.resolve().then(
() => {
throw new Error('来自成功回调的错误');
},
() => {
// 不会被执行
}
).catch(reason => {
console.log(reason.message); // 将打印出"来自成功回调的错误"
});
其原理也正如于上一点所言,catch函数是在then函数返回的rejected状态的promise上调用的,自然也就可以捕获到它的错误。
8. (压轴)promise实现koa2洋葱中间件模型
koa2框架引入了洋葱模型,可以让你的请求像剥洋葱一样,一层层进入再反向一层层出来,从而实现对请求统一的前后置处理。

我们来看一个简单的koa2洋葱模型:
const app = new Koa();
app.use(async (ctx, next) => {
console.log('a-start');
await next();
console.log('a-end');
});
app.use(async (ctx, next) => {
console.log('b-start');
await next();
console.log('b-end');
}); app.listen(3000);
以上的输出为 a-start -> b-start -> b-end -> a-end,这么神奇的输出顺序是如何做到的呢,某人不才,使用了20行左右的代码简单实现了一番,如有与koa雷同,纯属巧合。
接下来我们分析一番
注意:以下内容对新手不太友好,请斟酌观看。
- 首先将中间件函数先保存起来,并在listen函数中接收到请求后就调用洋葱模型的执行。
function action(koaInstance, ctx) {
// ...
}
class Koa {
middlewares = [];
use(mid) {
this.middlewares.push(mid);
}
listen(port) {
// 伪代码模拟接收请求
http.on('request', ctx => {
action(this, ctx);
});
}
}
- 在接收到请求后,先从第一个中间件开始串行执行next前的前置逻辑。
// 开始启动中间件调用
function action(koaInstance, ctx) {
let nextMiddlewareIndex = 1; // 标识下一个执行的中间件索引 // 定义next函数
function next() {
// 剥洋葱前,调用next则调用下一个中间件函数
const nextMiddleware = middlewares[nextMiddlewareIndex];
if (nextMiddleware) {
nextMiddlewareIndex++;
nextMiddleware(ctx, next);
}
}
// 从第一个中间件函数开始执行,并将ctx和next函数传入
middlewares[0](ctx, next);
}
- 处理next之后的后置逻辑
function action(koaInstance, ctx) {
let nextMiddlewareIndex = 1;
function next() {
const nextMiddleware = middlewares[nextMiddlewareIndex];
if (nextMiddleware) {
nextMiddlewareIndex++;
// 这边也添加了return,让中间件函数的执行用promise从后到前串联执行(这个return建议反复理解)
return Promise.resolve(nextMiddleware(ctx, next));
} else {
// 当最后一个中间件的前置逻辑执行完后,返回fullfilled的promise开始执行next后的后置逻辑
return Promise.resolve();
}
}
middlewares[0](ctx, next);
}
本文转载于:
https://juejin.cn/post/7263089207128850489
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--整会promise这8个高级用法的更多相关文章
- Android优秀资源整理合集(论菜鸟到高级攻城狮)
转载请注明转自:http://blog.csdn.net/u011176685/article/details/51434702 csdn文章:Android优秀资源整理合集(论菜鸟到高级攻城狮) 时 ...
- SQL[连载3]sql的一些高级用法
SQL[连载3]sql的一些高级用法 SQL 高级教程 SQL SELECT TOP SQL SELECT TOP 子句 SELECT TOP 子句用于规定要返回的记录的数目. SELECT TOP ...
- 使用wget做站点镜像及wget的高级用法
本文为大家介绍 使用wget做站点镜像及wget的高级用法,供大家学习参考. # wget -r -p -np -k http://xxx.edu.cn -r 表示递归下载,会下载所有的链接,不过要注 ...
- Visual Studio 宏的高级用法
因为自 Visual Studio 2012 开始,微软已经取消了对宏的支持,所以本篇文章所述内容只适用于 Visual Studio 2010 或更早期版本的 VS. 在上一篇中,我已经介绍了如何编 ...
- Solr学习总结(六)SolrNet的高级用法(复杂查询,分页,高亮,Facet查询)
上一篇,讲到了SolrNet的基本用法及CURD,这个算是SolrNet 的入门知识介绍吧,昨天写完之后,有朋友评论说,这些感觉都被写烂了.没错,这些基本的用法,在网上百度,资料肯定一大堆,有一些写的 ...
- 再谈Newtonsoft.Json高级用法
上一篇Newtonsoft.Json高级用法发布以后收到挺多回复的,本篇将分享几点挺有用的知识点和最近项目中用到的一个新点进行说明,做为对上篇文章的补充. 阅读目录 动态改变属性序列化名称 枚举值序列 ...
- Android(java)学习笔记264:Android下的属性动画高级用法(Property Animation)
1. 大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法,当然也是最常用的一些用法,这些用法足以覆盖我们平时大多情况下的动画需求了.但是,正如上篇文章当中所说到的,属性动画对补间动画 ...
- nmap命令-----高级用法
探测主机存活常用方式 (1)-sP :进行ping扫描 打印出对ping扫描做出响应的主机,不做进一步测试(如端口扫描或者操作系统探测): 下面去扫描10.0.3.0/24这个网段的的主机 nmap ...
- 细说 ASP.NET Cache 及其高级用法
许多做过程序性能优化的人,或者关注过程程序性能的人,应该都使用过各类缓存技术. 而我今天所说的Cache是专指ASP.NET的Cache,我们可以使用HttpRuntime.Cache访问到的那个Ca ...
- 细说 ASP.NET Cache 及其高级用法【转】
阅读目录 开始 Cache的基本用途 Cache的定义 Cache常见用法 Cache类的特点 缓存项的过期时间 缓存项的依赖关系 - 依赖其它缓存项 缓存项的依赖关系 - 文件依赖 缓存项的移除优先 ...
随机推荐
- NC224933 漂亮数
题目链接 题目 题目描述 小红定义一个数满足以下条件为"漂亮数": 该数不是素数. 该数可以分解为2个素数的乘积. 4 是漂亮数,因为 4=2*2 21 是漂亮数,因为 21=3* ...
- CoreDNS笔记
因为项目的原因需要在客户端启动DNS服务,拦截本机DNS请求,考察了一下开源的DNS Server项目,适合在Windows下使用的只有CoreDNS. 说明 CoreDNS的项目地址 https:/ ...
- MySQL5.7的账号回收权限
因MySQL无排除表权限功能,测试隔离表回收权限以下方案: 1,代理账号(角色)方案 方案:创建一个代理账号(角色),对库的800多张表逐个赋权,58张隔离表赋只读,其他30多个账号绑定到这个代理账号 ...
- win10下开启IIS部署网站
今天测试一个有关IIS的安全漏洞用了下win10下的IIS,记录一下,供网友参考. 1.启用IIS 依次点开: 控制面板-----程序----启用或关闭Windows功能 以下图片中打钩的地方全勾上. ...
- OpenResty中如何实现,按QPS、时间范围、来源IP进行限流
OpenResty是一个基于Nginx与Lua的高性能Web平台,它通过LuaJIT在Nginx中运行高效的Lua脚本和模块,可以用来处理复杂的网络请求,并且支持各种流量控制和限制的功能. 近期研究在 ...
- 详解SSL证书系列(2)SSL证书对网站的好处
在如今谷歌.百度等互联网巨头强制性要求网站 HTTPS 化的情况下, 网站部署 SSL 证书已然成为互联网的发展趋势,我们也知道了 SSL证书可以防止网络安全威胁.那么除此外为网站部署 SSL 证书还 ...
- 问题解决:由于找不到msvcr110.dll,无法继续执行代码
报错 解决 下载地址:https://www.microsoft.com/zh-cn/download/details.aspx?id=30679
- 我的第一个项目(九) :飞机大战Vue版本塞到主页
好家伙, 这是未进行分包的vue版本的飞机大战 效果如下: 这里说明一下,大概使用逻辑是提供一个<div> 然后在这<div>中渲染游戏 游戏主界面代码如下: 1 < ...
- 【Azure Developer】Windows中通过pslist命令查看到Java进程和线程信息,但为什么和代码中打印出来的进程号不一致呢?
通过PSLIST查看Windwos中的进程信息及线程信息 一:下载PSLIST小工具:https://docs.microsoft.com/en-us/sysinternals/downloads/p ...
- docker知识整理(备份)
概念: 镜像:Docker 镜像类似于虚拟机镜像,可以将它理解为一个只读的模板 .镜像是创建 Docker 容器的基础.通过版本管理和增量的文件系统, Docker 提供了一套十分简单的机制来创建和更 ...
