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 requestItem();
}

如果使用 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 => {// ...
});

上面两个请求其实只会真正发出一次,并且同时收到相同的响应值。

那么,请求共享会有哪几个使用场景呢?我认为有以下三个:

  1. 当一个页面同时渲染多个内部自获取数据的组件时;

  2. 提交按钮未被禁用,用户连续点击了多次提交按钮;

  3. 在预加载数据的情况下,还未完成预加载就进入了预加载页面;

这也是alova[1]的高级功能之一,实现请求共享需要用到 promise 的缓存功能,即一个 promise 对象可以通过多次 await 获取到数据,简单的实现思路如下:

const pendingPromises = {};
function request(type, url, data) {// 使用请求信息作为唯一的请求key,缓存正在请求的promise对象// 相同key的请求将复用promiseconst 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 雷同,纯属巧合。

接下来我们分析一番

注意:以下内容对新手不太友好,请斟酌观看。

  1. 首先将中间件函数先保存起来,并在 listen 函数中接收到请求后就调用洋葱模型的执行。

function action(koaInstance, ctx) {// ...
}class Koa {middlewares = [];use(mid) {this.middlewares.push(mid);}listen(port) {// 伪代码模拟接收请求http.on('request', ctx => {action(this, ctx);});}
}
  1. 在接收到请求后,先从第一个中间件开始串行执行 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);
}
  1. 处理 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);
}

到此,一个简单的洋葱模型就实现了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/101079.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Rockchip平台 远程OTA服务搭建

Rockchip平台 远程OTA服务搭建 1. 概述 远程OTA升级服务是一种通过互联网远程更新Rockchip设备的固件和软件的方法。这种服务对于确保设备安全性、修复错误和添加新功能非常重要。 本文档将引导您完成在Rockchip平台上搭建远程OTA升级服务的过程。 在阅读本文的前&#xff…

Vue3新的状态管理库-Pinia(保姆级别教程)

目录 1.什么是Pinia2.为什么使用Pinia3.创建项目4.检查Pinia的安装版本5.main.js引入Pinia6.定义Store-组合式API写法(推荐)7.getters的实现8.action的异步实现9.storeToRefs 1.什么是Pinia Pinia是Vue的专属的最新状态管理库, 是Vuex状态管理工具的替代品 vue.js官网 https:…

L14D6内核模块编译方法

一、内核模块基础代码解析 一个内核模块代码错误仍然会导致的内核崩溃。 GPL协议&#xff1a;开源规定&#xff0c;使用内核一些函数需要 1、单内核的缺点 单内核扩展性差的缺点减小内核镜像文件体积&#xff0c;一定程度上节省内存资源提高开发效率不能彻底解决稳定性低的缺…

【新品发布】四核A53超高性价比!RK3562系列核心板及开发板震撼上市

RK3562系列产品采用 Rockchip 新一代 64 位处理器 RK3562&#xff08;Quad-core ARM Cortex-A53&#xff0c;主频最高 2.0GHz&#xff09;&#xff0c;最大支持 8GB 内存&#xff1b;内置独立的 NPU&#xff0c;可用于轻量级人工智能应用&#xff0c;RK3562 拥有 PCIE2.1 / USB…

图文验证码怎么测试及自动化测试怎么解决验证码问题?

前言 在对安全性有要求的软件&#xff08;系统&#xff09;中都存在验证码&#xff0c;那我们应该怎么进行测试呢&#xff0c;在自动化测试中又该怎么通过验证码使自动化顺利进行下去呢&#xff1f; 首先&#xff0c;来简单认识下验证码 测试验证码&#xff0c;首先我们应当…

Docker私有仓库打开2375端口(linux)

前言 在我们开发测试过程中&#xff0c;需要频繁的更新docker镜像&#xff0c;然而默认情况下&#xff0c;docker的2375端口是关闭的&#xff0c;下面介绍如何打开端口。 1、打开步骤 1.1、修改配置 登录docker所在服务器&#xff0c;修改docker.service文件 vi /usr/lib/sys…

如何正确高效使用墨西哥专线?

在当今全球化的物流行业中&#xff0c;跨境运输服务已经成为许多企业拓展国际市场的重要手段。然而&#xff0c;由于各国法律法规、文化差异以及运输环节的复杂性&#xff0c;企业在进行跨境运输时可能会遇到诸多挑战。为了解决这些问题&#xff0c;一些专业的物流公司推出了“…

SpringBoot Redis 基础使用

redis是一个key-value。和Memcached类似&#xff0c;它支持存储的value类型相对更多&#xff0c;包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash&#xff08;哈希类型&#xff09;。 Redis能做什么&#xff1a; 1. 缓存&#xff0c;毫无疑问这…

Centos中清除因程序异常终止,导致的残留的Cache/buff_drop_caches命令---linux工作笔记063

我这里因为nifi程序背压设置的不合理,导致,内存和CPU消耗过高,系统崩溃,但是重启NIFI以后,发现 对应的执行top命令,看到,系统的buff/cache 依然没有减少,说明内存被浪费了,残留在这里没有被回收. 用这个办法执行这个命令; linux会自动触发清理,但是只有在内存不够用的时候才会…

【AN-Animate教程——熟悉工作区】

【AN-Animate教程——熟悉工作区】 初始页面创建舞台主舞台界面其他常用板块 本篇内容&#xff1a;Animate用途 重点内容&#xff1a;熟悉工作区&#xff0c;以及基本操作 工 具&#xff1a;Adobe Animate 2022 初始页面 在初始页面当中&#xff0c;我们可以看到一个忍者和一个…

Mainflux IoT:Go语言轻量级开源物联网平台,支持HTTP、MQTT、WebSocket、CoAP协议

Mainflux是一个由法国的创业公司开发并维护的安全、可扩展的开源物联网平台&#xff0c;使用 Go语言开发、采用微服务的框架。Mainflux支持多种接入设备&#xff0c;包括设备、用户、APP&#xff1b;支持多种协议&#xff0c;包括HTTP、MQTT、WebSocket、CoAP&#xff0c;并支持…

Redis根据中心点坐标和半径筛选符合的数据

目录 1.启动Redis​编辑 2.导入maven依赖 3.添加redis配置 4.编写RedisService 5.使用 6.验证 1.启动Redis 2.导入maven依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifac…

pnpm、npm、yarn 包管理工具『优劣对比』及『环境迁移』

前言 博主在开发前端网站的时候&#xff0c;发现随着开发的项目的逐渐增多&#xff0c;安装的依赖包越来越臃肿&#xff0c;依赖包的安装速度也是非常越来越慢&#xff0c;多项目开发管理也是比较麻烦。之前我就了解过 pnpm&#xff0c;但是当时担心更换包管理环境可能会出现的…

[modern c++] 函数式编程与 std::ref

参考&#xff1a; std::ref, std::cref - cppreference.comhttps://en.cppreference.com/w/cpp/utility/functional/ref 正文&#xff1a; 如果不涉及函数式编程&#xff0c;那么基本上不需要使用到 std::ref &#xff0c; 这个功能式是用来解决函数式编程时入参只能进行值传…

使用poi-tl循环导出word报表

先看模板和导出的效果 模板 效果 根据模板循环生成表格&#xff0c;每个表格再循环填充数据&#xff0c;也就是两层循环&#xff0c;第一层循环是学生学期信息&#xff0c;第二层循环是学生的成绩数据。 第一个循环 {{?listTable}} {{/}}第二个循环 {{reportList}} 表格…

电脑图片jpeg怎么转jpg格式?jpeg和jpg的转换方法

很多平台对上传的图片格式都有严格的要求&#xff0c;当我们遇到图片格式不对的时候&#xff0c;就需要改图片格式了&#xff0c;下面以jpeg转jpg&#xff08;在线图片格式转换器&#xff08;jpg、png、gif、webp、bmp、tiff&#xff09;-压缩图&#xff09;为例子&#xff0c;…

SpringBoot项目整合

一、创建项目 IDEA中采用spring initialzer...创建&#xff0c;jdk选择8&#xff0c;maven,jar。。。springboot版本2.5.0&#xff08;稳定&#xff09; 项目依赖&#xff1a; 二、项目结构&#xff1a; 原始pom.xml文件 <?xml version"1.0" encoding"UT…

tcpdump(五)命令行参数讲解(四)

一 案例讲解 tcpdump官方参考文档 最全的tcpdump手册 强调&#xff1a; -nn 选项一般是must 必选 ① 现场分析并保留现场信息 tcpdump -l | tee dat 使用tee来把tcpdump的输出同时放到文件dat和标准输出中场景&#xff1a; 自己现场分析同时把现场信息保留下来 ② …

朋友圈一键转发(可修改文案),无需多个账号复制粘贴

相信很多人手上都有不止一个微信&#xff0c;每次发个朋友圈都要在多个账号切换&#xff0c;重复发送&#xff0c;好不麻烦。而一些朋友想要一键跟随转发朋友圈&#xff0c;却总是需要一个个复制粘贴&#xff0c;麻烦而且容易漏发。 那实现朋友圈一键跟随转发&#xff0c;无需多…

wifi管理软件 WiFi Signal mac中文介绍

WiFi Signal mac是一款WiFi信号强度监测工具&#xff0c;它可以帮助用户实时监测WiFi信号的强度、频率、噪声等信息&#xff0c;并提供详细的图表和统计数据。 WiFi Signal可以自动扫描附近的WiFi网络&#xff0c;并显示它们的信号强度和频率。用户可以通过WiFi Signal来找到最…