基于Promise.resolve实现Koa请求队列中间件
本文作者为360奇舞团前端工程师
前言
最近在做一个
AIGC项目,后端基于Koa2实现。其中有一个需求就是调用兄弟业务线服务端AIGC能力生成图片。但由于目前兄弟业务线的AIGC项目也是处于测试阶段,能够提供的服务器资源有限,当并发请求资源无法满足时,会响应【服务器繁忙】,这样对于C端展示的我们是非常不友好的。基于当前的困境,第一想到的解决方案就是Kafka或RabbitMQ,但实际上对于我们目前的用户体量来说,简直就是大材小用。于是转换思路,是不是可以利用js模拟队列的方式解决问题呢,答案是:可以,Promise的Resolve队列!
分析
Resolve 的理解
Promise 的核心用法就是利用 Resolve 函数做链式传递。例如:
new Promise(resolve => {resolve('ok')
}).then(res => {console.log(res)
})
// 输出结果:ok 通过上边的例子我们可以理解,Resolve 将 Promise 对象的状态从 pending 变为 fullfilled ,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
核心点:异步
此时抛出一个问题:假如我把 resolve 回调函数都放入一个队列里,Promise 是不是一直处于pending 状态?pending 状态就意味着then函数一直处于 waitting 状态,直到队列中的 resolve 函数执行后,then 函数才能被执行?
制造阻塞的 Promise 函数
const queue = []
new Promise(resolve => {queue.push(resolve)
}).then(res => {console.log(res)
})
// 输出结果:Promise {<pending>}queue[0]('ok')
// 输出结果:ok 为了佐证,直接贴图:
异步转同步
Koa2 属于洋葱模型,当请求过来以后需要调用 next 函数继续穿透,而我们的需求是限流,这意味着我们要阻塞请求,此时此刻,await举起了双手,阻塞这种不要脸的事我在行呀!
const queue = []
const fn = async = () => {await new Promise(resolve => {queue.push(resolve)})// ...一大波操作
}
// queue[0]() 如果 queue[0] 不执行,代码就会一直处于阻塞状态。那我们就可以利用await写一个中间件实现阻塞某些 api 的需求了。
// 阻塞所有请求,知道queue中的resolve函数被执行才会执行next
const queue = []
module.exports = function () {return async function (ctx, next) {await new Promise(resolve => {queue.push(resolve)})await next();};
}; 实现中间件
原理和思路都捋直了,那就开搞吧。话不多说,贴代码:
const resolveMap = {};/*** 请求队列* @param {*} ctx* @param {*} ifer 是否是图生图* @param {*} maxReqNumber 最大请求数量* @returns* @description* 使用promise解决请求队列问题* 1. 用于限制aicg的并发请求* 2. 当文生图是,根据风格分类存储resolve,当前请求响应完成时,触发消费队列中下一个请求* 3. 当图生图是,直接存储resolve到image风格,当前请求响应完成时,触发消费队列中下一个请求* 4. 同时处理的请求数量不超过maxReqNumber个,否则加入队列等待。*/
function requestQueue(ctx, maxReqNumber) {const params = ctx.request.body ?? ctx.request.query ?? ctx.request.params ?? {};const style = params.style ?? 'pruned_cgfull';resolveMap[style] = resolveMap[style] || { list: [], processNumber: 0 };const currentResolve = resolveMap[style];((currentResolve) => {ctx.res.on('close', () => {saveNumberMinus(currentResolve);// 当前请求响应完成时,触发消费队列中下一个请求if (currentResolve.list.length !== 0) {const node = currentResolve.list.shift();node.resolve();currentResolve.processNumber++;}currentResolve = null;});})(currentResolve);// 当前请求正在处理中,将resolve存储到队列中if (currentResolve.processNumber + 1 > maxReqNumber) {// 利用promise阻塞请求return new Promise((resolve, reject) => {// 当前请求正在处理中,将resolve存储到队列中currentResolve.list.push({ resolve, reject, timeStamp: Date.now(), params });});} else {currentResolve.processNumber++;return Promise.resolve();}
}module.exports = function (options = {}) {const { maxReqNumber = 2, apis = [] } = options;return async function (ctx, next) {const url = ctx.url;if (apis.includes(url)) {try {await requestQueue(ctx, maxReqNumber);} catch (error) {console.log(error);ctx.body = {code: 0,msg: error,};return;}}await next();};
};const fiveMinutes = 5 * 60 * 1000;
setInterval(() => {Object.values(resolveMap).forEach((item) => {const { timeStamp, resolve } = item;if (Date.now() - timeStamp > fiveMinutes) {resolve(); // 执行并释放请求,防止用户请求因异常积压导致一直挂起saveNumberMinus(item);}});
}, 5 * 60 * 1000); 这里要着重提示一点,闭包的使用。之所以使用闭包是为了保证当前请求的
close事件触发时能够使用currentResolve对象。因为当前请求是放在自身对应风格的数组中,close时要消费下一个等待的请求,同时也不要忘了手动释放资源。
app.js 逻辑部分
const requsetQueue = require('./app/middleware/request-queue');
const app = new Koa();
app.use(requsetQueue({maxReqNumber: 1,apis: ['/api/aigc/image', '/api/aigc/textToImage', '/api/aigc/img2img'],})
);
app.listen(process.env.NODE_ENV === 'development' ? '9527' : '3000'); 总结
其实基于 Promise 的 Resolve 队列,我们还可以实现一些其他的功能,比如:前端代码中未登录状态下收集某些请求,等到登录成功后发送请求。也希望大家一起探索和讨论Promise的其他解决能力的实现方案。
- END -
关于奇舞团
奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

相关文章:
基于Promise.resolve实现Koa请求队列中间件
本文作者为360奇舞团前端工程师 前言 最近在做一个 AIGC 项目,后端基于 Koa2 实现。其中有一个需求就是调用兄弟业务线服务端 AIGC 能力生成图片。但由于目前兄弟业务线的 AIGC 项目也是处于测试阶段,能够提供的服务器资源有限,当并发请求资源…...
【结构型设计模式】C#设计模式之桥接模式
题目:设计一个桥接模式来实现图形和颜色之间的解耦。 解析: 桥接模式是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立变化。在这个例子中,抽象部分是图形(如圆形、正方形)&am…...
【12】Git工具 协同工作平台使用教程 Gitee使用指南 腾讯工蜂使用指南【Gitee】【腾讯工蜂】【Git】
tips:少量的git安装和使用教程,更多讲快速使用上手Gitee和工蜂平台 一、准备工作 1、下载git Git - Downloads (git-scm.com) 找到对应操作系统,对应版本,对应的位数 下载后根据需求自己安装,然后用git --version验…...
zookeeper增加IP白名单-安全设置
简介: zookeeper未授权访问漏洞,处理这个漏洞最简单,常用的应该就是给zookeeper添加用户名、密码验证,如果项目比较急,且代码不支持zookeeper的用户名、密码验证,那采用ip白名单过滤,无疑是最快…...
Mac 调试 ios safar
1. 打开Mac的 Safari 浏览器的“开发”菜单 运行 Safari 浏览器,然后依次选取“Safari 浏览器”>“偏好设置”,点按“高级”面板,然后勾选“在菜单栏中显示开发菜单”。 2. 开启IPhone的Safari调试模式 启用 Web 检查 功能,打…...
Linu网络服务NFS
linux网络服务NFS 一.NFS简介二.NFS原理三.NFS优势四.配置文件五.NFS共享存储服务的操作步骤 一.NFS简介 NFS(网络文件服务) NFS是一种基于tcp/ip传输的网络文件系统协议,最初由sun公司开放通过使用NFS协议,客户机可以像访问本地…...
24届近5年同济大学自动化考研院校分析
今天给大家带来的是同济大学控制考研分析 满满干货~还不快快点赞收藏 一、同济大学 学校简介 同济大学历史悠久、声誉卓著,是中国最早的国立大学之一,是教育部直属并与上海市共建的全国重点大学。经过115年的发展,同济大学已经…...
多源BFS
多源 超级源点和汇点最短距离[超级汇点]昂贵的聘礼 多源BFS矩阵距离 超级源点和汇点 超级源点跟超级汇点是模拟出来的虚拟点,多用于图中: <1>同时有多个汇点和一个源点,建立超级汇点 1、2、3、6分别到达4或者5或者7的最短路径…...
自制电子农历
水文大师上线。今天一水电子农历牌。 首先讲讲电子配件,一来是电子小屏幕的选择,遇到文字比较多的,尤其是汉字,不要选传统那款128x64 oled,绝对放不下(找到最牛的超小免费字体至少要在8pixel以上才能看清楚)。我选了i…...
解决nvm安装后,node生效但npm无效
问题描述 nvm安装后,node生效但npm无效 清除缓存 C:\Users\cc\AppData\Roaming cc是我的用户名改成你自己的就行删除 npm和npm-cache...
Chrome DevTools 与 WebSocket 数据查看失焦的问题
Chrome DevTools 在与 WebSocket 连接交互时可能会出现失焦的问题,这似乎是一个已知的 bug。当 DevTools 选中 WebSocket 消息时,如果有新的消息到达,DevTools 将会自动失焦,导致无法查看完整的消息内容。 虽然这个问题很令人困扰…...
Javascript 正则
基本语法 定义 JavaScript种正则表达式有两种定义方式 构造函数 var regnew RegExp(<%[^%>]%>,g);字面量 var reg/<%[^%>]%>/g;g: global,全文搜索,默认搜索到第一个结果接停止i:ingore case,忽略…...
C语言可变数组 嵌套的可变数组,翻过了山跨过了河 又掉进了坑
可变数组 专栏内容: postgresql内核源码分析 手写数据库toadb 并发编程 个人主页:我的主页 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. 概述 数组中元素是顺序存放,这一特性让我们…...
FFmpeg安装和使用
sudo apt install ffmpeg sudo apt-get install libavfilter-devcmakelist模板 CMakeLists.txt cmake_minimum_required(VERSION 3.16) project(ffmpeg_demo)# 设置ffmpeg依赖库及头文件所在目录,并存进指定变量 set(ffmpeg_libs_DIR /usr/lib/x86_64-linux-gnu) …...
HTTP代理编程:Python实用技巧与代码实例
今天我要与大家分享一些关于HTTP代理编程的实用技巧和Python代码实例。作为一名HTTP代理产品供应商,希望通过这篇文章,帮助你们掌握一些高效且实用的编程技巧,提高开发和使用HTTP代理产品的能力。 一、使用Python的requests库发送HTTP请求&a…...
java调用第三方接口工具类 (HttpClientUtils.java)
1. 依赖 <!--httpclient--> <dependency><groupId>commons-httpclient</groupId><artifactId>commons-httpclient</artifactId><version>3.1</version> </dependency><!-- 阿里JSON解析器 --> <dependency>…...
f1tenth仿真设置
文章目录 一、安装依赖二、进入工作空间克隆三、编译四、运行 一、安装依赖 tf2_geometry_msgs ackermann_msgs joy map_server sudo apt-get install ros-noetic-tf2-geometry-msgs ros-noetic-ackermann-msgs ros-melodic-joy ros-noetic-map-server 二、进入工作空间克隆…...
Technical debt (技术负债 / 技术债)
Technical debt (技术负债 / 技术债) In software development, or any other IT field (e.g., Infrastructure, Networking, etc.) technical debt (also known as design debt or code debt) is the implied cost of future reworking required when choosing an easy but li…...
【MATLAB第67期】# 源码分享 | 基于MATLAB的morris全局敏感性分析
【MATLAB第67期】# 源码分享 | 基于MATLAB的morris全局敏感性分析 一、代码展示 clear all npoint100;%在分位数超空间中要采样的点数(计算次数iternpoint*(nfac1) nfac20;%研究函数的不确定因素数量 [mu, order] morris_sa1((x)test_function(x), nfac, npoint)for t1:size…...
ruby send call 的简单使用
refer: ruby on rails - What does .call do? - Stack Overflow Ruby使用call 可以调用方法或者proc m 12.method("") # > method gets the method defined in the Fixnum instance # m.class # > Methodm.call(3) #> 15 # 3 is passed inside the…...
接口测试——pytest框架续集劫
智能体时代的代码范式转移与 C# 的战略转型 传统的 C# 开发模式,即所谓的“工程导向型”开发,要求开发者创建一个复杂的项目结构,包括项目文件(.csproj)、解决方案文件(.sln)、属性设置以及依赖…...
零基础部署Clawdbot+Qwen3-32B:一键开启智能对话Web界面
零基础部署ClawdbotQwen3-32B:一键开启智能对话Web界面 1. 为什么选择这个方案 你是否遇到过这样的困境:好不容易在本地部署了大语言模型,却卡在了如何让团队成员方便使用的环节?传统的API调用方式对非技术人员极不友好…...
【Chips】从“打两拍”到“异步FIFO”:跨时钟域同步方案的选择逻辑与实战边界
1. 跨时钟域同步的挑战与核心问题 第一次接触跨时钟域设计时,我盯着示波器上那些不稳定的波形整整发呆了半小时。当时正在调试一个简单的按键消抖电路,按键信号从20MHz的IO时钟域传递到100MHz的系统时钟域,结果发现每隔几次就会产生误触发。这…...
labview实现CPU温度的实时检测
上面的系统实现其实很简单,使用Windows 管理规范(WMI) 配合 LabVIEW 的 .NET 接口 实现的,属于系统级硬件监控。1. 核心实现方式:WMI(Windows Management Instrumentation)读取 CPU 温度&#x…...
DoL-Lyra整合包:三步打造你的专属Degrees of Lewdity游戏体验
DoL-Lyra整合包:三步打造你的专属Degrees of Lewdity游戏体验 【免费下载链接】DOL-CHS-MODS Degrees of Lewdity 整合 项目地址: https://gitcode.com/gh_mirrors/do/DOL-CHS-MODS 你是否厌倦了在Degrees of Lewdity英文原版游戏中费力查找词典?…...
【实战】Hermes Agent 深度体验:会自我进化的 AI 智能体,3大核心机制拆解与上手指南
本文从实际使用角度出发,拆解 Hermes Agent 的自动 Skill 生成、三层记忆架构和多平台网关三大核心机制,并附完整的安装部署指南和踩坑记录。适合想要搭建长期运行的个人 AI Agent 的开发者阅读。 目录前言一、Hermes Agent 是什么1.1 项目背景1.2 核心定…...
Graphormer模型原理图解:Visio绘制神经网络架构图
Graphormer模型原理图解:Visio绘制神经网络架构图 1. 引言:当Transformer遇见图数据 Graphormer模型代表了图神经网络领域的一次重要突破。想象一下,如果让Transformer这个在自然语言处理领域大放异彩的架构,来处理社交网络、分…...
Claude Mythos Preview发布文章解读
1. 引入 anthropic于4月7日发布了Mythos Preview模型相关的说明文章(参考1),并提出了目前不开放它的政策,还说了它在网安领域的能力很强。 那么,它的这些思路,是出于什么考虑呢? 2. 首次提到的内…...
从原理到实践:深入理解react-native-fetch-blob的底层架构设计
从原理到实践:深入理解react-native-fetch-blob的底层架构设计 【免费下载链接】react-native-fetch-blob A project committed to making file access and data transfer easier, efficient for React Native developers. 项目地址: https://gitcode.com/gh_mirr…...
Fish-Speech-1.5在JavaWeb项目中的集成实践
Fish-Speech-1.5在JavaWeb项目中的集成实践 1. 引言 想象一下,你的JavaWeb应用能够像真人一样说话——电商平台的商品介绍不再冰冷生硬,在线教育的内容讲解充满情感波动,智能客服的回应自然流畅。这就是Fish-Speech-1.5带来的变革。 Fish-…...
