发动态
综合 最新发布 最新回复
图文
列表
一、方案背景在前端中经常会出现多接口并发的场景,而针对请求常规方式会一股脑发送给浏览器,浏览器中会对大量请求进行排队,但大量请求推送至浏览器就会占用内存,最后达到一定量级导致页面卡顿,甚至假死常规请求图如下二、解决思路针对此模式,我们可以创建如下序列,待发送请求池作为中间层,只作为载体,每次推送固定数量到浏览器,当浏览器主线程中存在接口执行完毕后,代表主线程中接口数量减少,在从待发送请求中取出新的接口放入主线程,从而实现控制主线程发送数量避免内存溢出创建待发送请求池控制最大并行请求数量指定接口缓存,如数据源,结合请求,响应拦截器进行处理流程图如下<<<顺手说一个:有新的技术大厂在→要人,前端/后端还有测试,一线城市及双一线城市几乎都有坑位,待遇稳定性都还成,感兴趣的可以瞅瞅~三、具体代码实现请求并发控制方案概述这是一个基于请求池(Request Pool)的并发控制方案,用于管理前端应用中的 HTTP 请求。通过双队列机制控制并发数量,避免同时发起过多请求导致的性能问题和浏览器限制。核心特性并发控制:限制同时执行的请求数量(默认 20 个)双队列设计:待执行队列(pendingQueue)+ 执行中队列(runningQueue)请求取消:支持取消单个或全部请求,基于 AbortController缓存支持:可配置的请求结果缓存机制内存优化:请求完成后自动清理引用,防止内存泄漏非阻塞调度:使用 setTimeout(0) 让出主线程,避免阻塞 UI防重复调度:通过 isProcessing 标志位避免重复调度架构设计1. 状态管理const poolState = { pendingQueue: [], // 待执行队列 runningQueue: [], // 执行中队列 cacheStore: new Map(), // 缓存存储 maxConcurrent: 20, // 最大并发数 maxPendingQueue: Infinity, // 待执行队列最大长度 isProcessing: false // 防止重复调度标志位 }; 2、核心机制防重复调度使用 isProcessing 标志位防止多次调用 scheduleExecute() 时重复创建定时器: const scheduleExecute = () => { if (poolState.isProcessing) return; // 已在处理中,直接返回 poolState.isProcessing = true; setTimeout(() => { processQueue(); poolState.isProcessing = false; // 处理完成,重置标志 }, 0); }; 批量处理每次调度时,根据可用槽位批量启动请求:const processQueue = () => { // 计算可用槽位 const slots = poolState.maxConcurrent - poolState.runningQueue.length; const count = Math.min(slots, poolState.pendingQueue.length); // 批量启动 for (let i = 0; i < count; i++) { const task = poolState.pendingQueue.shift(); if (task) { poolState.runningQueue.push(task); executeRequest(task); } } }; API 文档addRequest(config, axiosInstance)添加请求到队列参数:config (Object): axios 请求配置对象axiosInstance (Function): axios 实例返回:Promise: 请求的 Promise 对象示例:import axios from 'axios'; import { addRequest } from '@/utils/requestPool'; const config = { method: 'get', url: '/api/users', params: { page: 1 } }; addRequest(config, axios) .then(response => console.log(response.data)) .catch(error => console.error(error)); setMaxConcurrent(max)设置最大并发数参数:max (Number): 最大并发数示例:import { setMaxConcurrent } from '@/utils/requestPool'; // 设置最大并发数为 10 setMaxConcurrent(10); clearAllRequests()取消所有请求(包括待执行和执行中的请求)返回:Object: 取消的请求数量统计pending (Number): 待执行队列中取消的数量running (Number): 执行中队列中取消的数量示例:import { clearAllRequests } from '@/utils/requestPool'; const count = clearAllRequests(); console.log(`取消了 ${count.pending} 个待执行请求`); console.log(`取消了 ${count.running} 个执行中请求`); getPoolState()获取请求池当前状态(用于调试和监控)返回:Object: 请求池状态信息示例:import { getPoolState } from '@/utils/requestPool'; const state = getPoolState(); console.log('待执行:', state.pendingCount); console.log('执行中:', state.runningCount); console.log('缓存数:', state.cacheCount); getCacheKey(config)生成缓存键(预留功能)参数:config (Object): axios 配置对象,需包含 cacheParams 字段返回:String | null: 缓存键,如果未配置 cacheParams 则返回 nullgetCache(key) / setCache(key, data) / clearCache(key)缓存操作方法(预留功能)使用示例1. 基础集成 - Axios 拦截器在 axios 实例中集成请求池:// src/utils/request.js import axios from 'axios'; import { addRequest, clearAllRequests } from './requestPool'; const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 }); // 请求拦截器 service.interceptors.request.use( config => { // 标记使用请求池 config.usePool = true; return config; }, error => Promise.reject(error) ); // 响应拦截器 service.interceptors.response.use( response => response.data, error => { console.error('请求失败:', error); return Promise.reject(error); } ); // 重写 axios 请求方法,使用请求池 const originalRequest = service.request.bind(service); service.request = function(config) { if (config.usePool !== false) { // 使用请求池 return addRequest(config, originalRequest); } // 不使用请求池,直接请求 return originalRequest(config); }; export default service; export { clearAllRequests }; 2. API 调用示例 // src/api/user.js import request from '@/utils/request'; // 获取用户列表 export const getUserList = (params) => { return request({ url: '/api/users', method: 'get', params }); }; // 获取用户详情 export const getUserDetail = (id) => { return request({ url: `/api/users/${id}`, method: 'get' }); }; // 创建用户 export const createUser = (data) => { return request({ url: '/api/users', method: 'post', data }); }; 3. 组件中使用<template> <div> <button @click="loadData">加载数据</button> <button @click="cancelAll">取消所有请求</button> <div>待执行: {{ poolState.pendingCount }}</div> <div>执行中: {{ poolState.runningCount }}</div> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { getUserList } from '@/api/user'; import { clearAllRequests, getPoolState } from '@/utils/requestPool'; const poolState = ref({ pendingCount: 0, runningCount: 0 }); // 更新请求池状态 const updatePoolState = () => { const state = getPoolState(); poolState.value = state; }; // 加载数据 const loadData = async () => { try { // 同时发起 50 个请求,但只有 20 个会并发执行 const promises = Array.from({ length: 50 }, (_, i) => getUserList({ page: i + 1 }) ); // 定时更新状态 const timer = setInterval(updatePoolState, 100); const results = await Promise.all(promises); clearInterval(timer); console.log('所有请求完成:', results.length); } catch (error) { console.error('请求失败:', error); } }; // 取消所有请求 const cancelAll = () => { const count = clearAllRequests(); console.log(`已取消 ${count.pending + count.running} 个请求`); updatePoolState(); }; onMounted(() => { updatePoolState(); }); </script> 4. 路由切换时取消请求// src/router/index.js import { createRouter, createWebHistory } from 'vue-router'; import { clearAllRequests } from '@/utils/requestPool'; const router = createRouter({ history: createWebHistory(), routes: [ // 路由配置... ] }); // 路由切换前取消所有请求 router.beforeEach((to, from, next) => { if (from.path !== '/') { const count = clearAllRequests(); console.log(`路由切换,取消了 ${count.pending + count.running} 个请求`); } next(); }); export default router; 5. 批量请求示例// 批量加载用户详情 async function loadUserDetails(userIds) { const promises = userIds.map(id => getUserDetail(id)); try { const results = await Promise.all(promises); console.log('加载完成:', results); return results; } catch (error) { console.error('批量加载失败:', error); throw error; } } // 使用示例 const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; loadUserDetails(userIds); 6. 动态调整并发数import { setMaxConcurrent, getPoolState } from '@/utils/requestPool'; // 根据网络状况动态调整 function adjustConcurrency() { const connection = navigator.connection; if (connection) { const effectiveType = connection.effectiveType; switch (effectiveType) { case 'slow-2g': case '2g': setMaxConcurrent(5); break; case '3g': setMaxConcurrent(10); break; case '4g': default: setMaxConcurrent(20); break; } console.log('网络类型:', effectiveType); console.log('当前并发数:', getPoolState().maxConcurrent); } } // 监听网络变化 if (navigator.connection) { navigator.connection.addEventListener('change', adjustConcurrency); } 7. 请求优先级(扩展示例)如果需要实现请求优先级,可以扩展 addRequest 方法:// 扩展版本 - 支持优先级 export const addRequestWithPriority = (config, axiosInstance, priority = 0) => { return new Promise((resolve, reject) => { const abortController = new AbortController(); const task = { config: { ...config, signal: abortController.signal }, resolve, reject, axiosInstance, abortController, priority // 添加优先级字段 }; // 根据优先级插入队列 const index = poolState.pendingQueue.findIndex(t => t.priority < priority); if (index === -1) { poolState.pendingQueue.push(task); } else { poolState.pendingQueue.splice(index, 0, task); } scheduleExecute(); }); }; // 使用示例 addRequestWithPriority(config, axios, 10); // 高优先级 addRequestWithPriority(config, axios, 0); // 普通优先级 性能优化要点1. 内存管理请求完成后自动清理任务对象的引用:task.config = null; task.resolve = null; task.reject = null; task.axiosInstance = null; task.abortController = null; 2. 非阻塞调度使用 setTimeout(0) 让出主线程:setTimeout(() => { processQueue(); poolState.isProcessing = false; }, 0); 3. 批量处理一次调度处理多个请求,减少调度次数:const count = Math.min(slots, poolState.pendingQueue.length); for (let i = 0; i < count; i++) { // 批量启动 } 注意事项并发数设置:默认 20 个,可根据实际情况调整请求取消:路由切换或组件卸载时记得取消请求错误处理:已取消的请求不会触发 reject,避免重复处理缓存功能:当前代码中已预留,可根据需要启用浏览器限制:不同浏览器对同域名并发请求有限制(通常 6-8 个),但通过请求池可以更好地控制——转载自:米西米西1
前端必看!接口并发方案
开源硬件平台
B站demo演示视频:我把传感器塞进网球避震器,测了一下发球速度【项目背景】网球发球训练缺乏即时量化反馈工具。现有方案(Zepp、Babolat Play等)体积大、数据维度有限,无法捕捉发力结构细节。做了一个避震器形态的传感器模块,目标是在不显著改变球拍挥重的前提下,采集发球过程中的关键力学参数。【迭代过程】V1(已废弃):- 直接用蓝牙模组内置MCU做开发,没有独立单片机- 想法是单IMU做完整动作轨迹重建- 实测结果:误差累计太大,加速度二次积分漂移无法在这个成本和体积下解决- 教训:没搞过固件就上蓝牙模组MCU开发,调试手段有限,效率极低V2(当前版本):- 砍掉了"全动作记录"需求,聚焦发球训练——单次重复动作,每次击球间可归零,回避积分漂移问题- 加了独立STM32单片机,开发调试效率大幅提升- 换了高量程陀螺仪——发球动作核心是旋转发力(内旋),陀螺仪数据能覆盖主要需求,不再依赖加速度积分【当前方案概述】MCU:STM32C011F6P6IMU:LSM6DSVTR蓝牙模组:PB-03f供电:软包锂电核心模块重量:5.2g(不含外壳,未做工程优化)算法输出指标:- 拍头速度(发球瞬间)- 掉拍头幅度- 发力速度- 手臂内旋发力占比【当前进展】V2硬件完成并验证- 算法和数据链路跑通- 与手机App的BLE通信稳定- 已完成多次球场实测验证,四项指标实时输出【下阶段目标】做工程样机,小型化、外壳优化、电池选型及充电设计。发工程样机给网球爱好者、教练、运动员测试,吸收反馈迭代产品功能。【期望交流】希望能与做过类似消费电子/穿戴设备的,有工程化经验的朋友交流。我的邮箱:nospoon@qq.com小红书(记录了开发的部分过程):there is no spoon#DIY设计#
DIY网球发球训练数据采集模块,希望能与感兴趣的朋友交流
开源硬件平台
前言如果你点的椰果奶茶被做成了珍珠奶茶,虽然也能喝,但就是完全不是你想要的,至少对于我这种有点强迫症的人。那么 JavaScript 就是这样一个 “随性” 的奶茶店老板,而 TypeScript 就是那个拿着订单反复跟你确认 “少糖少冰” 的靠谱店员,从根源上避免了 “错单” 的尴尬。用一句话来说其实就是:TypeScript 是更严谨的 JavaScript。一、有了 JavaScript 为什么还要有 TypeScript ?写 JavaScript 就像开盲盒,你永远不知道下一个变量里装的是 数字、字符串还是 薛定谔的 undefined。我统称它们为 盲盒变量。比如这段代码:let n = 1, m = 0; n = 'hello'; // 数字秒变字符串,JS 主打一个“灵活” function add(a, b) { if (typeof a === 'number' && typeof b === 'number') { return a + b; } } // 传入字符串,函数直接返回 undefined,Bug 这不就来了 console.log(add(1, '2')); 你以为你在写 “动态灵活” 的代码,其实是在给未来的自己 埋雷。比如上面这段代码,可能你知道等会要传 2个number类型,但是如果别人直接拿来Ctrl + cv 用你封装的函数,传了一个 string类型 那就坏了。直到 TypeScript 出现,让变量从 “盲盒” 变成了 “明码标价的商品”。二、弱类型:自由过了火就是混乱在上面代码中有这样一个情况:let n = 1; n = 'hello'; // 在 JavaScript里面不报错 如果你是 C++、Java或者Go的工程师,你肯定会觉得这人怕不是敲代码敲疯了吧。在C++、Java或者Golang里面这代码直接就报错了。这就是因为 JavaScript 是典型的弱类型语言,变量不需要提前声明类型,随时可以 “变身”。打印结果为 hello:你可以让数字 a 一秒变成字符串,编辑器连个警告都没有。这种 “自由” 在小项目里或许能跑,但项目一复杂,就会出现 add(1, '2') 这种隐蔽 Bug,排查起来堪比大海捞针。三、强类型:给变量上 “户口”TypeScript 作为 JavaScript 的超集,核心就是给变量加上了类型声明。首先要用 TypeScript,我们需要去下载它:npm install -g typescript # 全局安装 TypeScript tsc -v # 查看 TypeScript的版本 tsc project.ts # project 是你的文件名,编译 TypeScript文件 当编译完你会发现编译器给你编译出了一份对等的JavaScript文件:这个时候你就可以用Node.js去跑这份文件,因为 TypeScript 本身不能直接运行,需要先编译成 JavaScript 再运行。不过现在有一些工具可以简化这个过程,比如 ts-node、deno 等。我这里简要介绍下 ts-node的使用:ls package.json # 首先检查项目是否有 package.json 文件 npm init -y # 如果没有,初始化一个 npm install -g ts-node # 然后安装 ts-node npm install --save-dev ts-node # 或本地安装 # 运行 TypeScript文件 ts-node 2.ts # 全局安装时 npx ts-node 2.ts # 本地安装时 一般 TypeScript 都是在 React项目 等环境下运行,所以直接运行一个文件的比较少见,这里我们主要看 TypeScript 语法的使用和基础知识。同样的代码,在 TypeScript 里面就会报错:let a: number = 1; a = 'hello'; // 编辑器直接标红:不能将类型 “string” 分配给类型 “number” console.log(a); 细心的你很快就发现了猫腻:TypeScript 相较于 JavaScript 不同的地方就在于 TypeScript 的写法中明确标注了变量是什么类型。就比如这里 a 被明确声明为 number 类型,如果你想把它改成字符串,TypeScript 会立刻报错,把问题扼杀在编码阶段。这样就使得文件更加严谨。<<<顺便说句,技术大厂,前后端-测试机会,一线双一线城市坑位充足,感兴趣可以看看这个~四、TypeScript 数据类型全家桶在之前我写过几篇 JavaScript数据类型 的文章,那我们现在来看看 TypeScript 类型全家桶,他们并不完全一样,但还是有很高的相似度。比如这段代码:let isDone: boolean = false; // boolean类型 let count: number = 123; // number类型 let str: string = 'Trae'; // string类型 const symbol: symbol = Symbol(); // symbol类型 let obj: object = { [symbol]: 'Trae' // object类型(对象) }; let list: number[] = [1, 2, 3]; // array类型(数组) enum Color { Red, Green, // 类似于结构体 Blue } let color: Color = Color.Red; let notSure: any = 10; // any类型 notSure = '123'; // any 类型可以随便变,是 TypeScript 里的 “漏网之鱼” let value: unknown = 10; // unknown类型 value = '123'; let abc: string = 'hello'; // unknown 类型不能直接赋值给其他类型,比 any 更安全 // abc = value; // 报错 abc = notSure; // 不报错 let tuple: [number, string] = [10, 'hello']; // 元组:固定长度和类型的数组 function user1(): number { return 123; } function user2(): Function { return function fn(): number { return 123; } } // 报错 // function user2(): string { // return 123; // } function user3(): void {} // void 表示没有返回值 let u: undefined = undefined; // undefined类型 let n: null = null; // null类型 基本上都与 JavaScript 相似,可以去看我之前写的 JavaScript数据类型。从基础的 boolean、number、string,到复杂的 enum、tuple、unknown,TypeScript 让每个变量都有了明确的 “身份”。这里有一个注意的点就是 unknown 类型 和 any类型。unknown 类型不能直接赋值给其他类型,而 any 类型可以随便变,所以下次报错的时候看看,是不是这个原因。五、对象与类型:不是所有空对象都一样TypeScript 对对象的类型约束更严格:const obj: object = {}; const obj2: Object = {}; const obj3: {} = {}; // 错误 // obj.a = 1; // 编译错误 // obj3.a = 1; // 编译错误 // 正确(类型断言) (obj2 as any).a = 1; console.log(obj2); // 输出: { a: 1 } const hello = 'hello'; const a: 'hello' = 'hello'; object、Object 和 {} 看似相似,实际约束力度不同;字面量类型更是把变量锁死在特定值上,杜绝了 “意外变身”。六、类型守卫🛡️:给你的代码装上 “火眼金睛”TypeScript 的类型守卫,就像给你的代码配上了一个智能安检员,能在运行时精准识别变量类型。// 类型守卫 interface Person { name: string; age: number; sex?: unknown; // 可选属性,不是每个人都需要填写 } const person: Person = { name: 'henry', age: 18, sex: '男' // 可选属性,写不写都不会报错 }; // 举个类型守卫的例子:判断一个值是不是 Person 类型 function isPerson(value: unknown): value is Person { return ( typeof value === 'object' && value !== null && 'name' in value && 'age' in value ); } function printUserInfo(value: unknown) { if (isPerson(value)) { // 进入这个分支后,TypeScript 就知道 value 是 Person 类型了 console.log(`姓名:${value.name},年龄:${value.age}`); if (value.sex) { console.log(`性别:${value.sex}`); } } else { console.log('这不是一个合法的 Person 对象'); } } printUserInfo(person); // 输出:姓名:henry,年龄:18 printUserInfo({ name: 'lucy' }); // 输出:这不是一个合法的 Person 对象 七、类型转换与组合:灵活不代表放纵如果遇到类型不确定的场景,TypeScript 提供了类型断言来 “手动担保”:let someValue: any = '123'; let strLength = (someValue as string).length; // 写法一 let strLength2 = (someValue).length; // 写法二 还可以用 type 定义联合类型和交叉类型:type Person = string | number | boolean; const a: Person = 'hello'; const b: Person = 123; const c: Person = true; type PartialX = {x: number} type Point = PartialX & {y: number} // 交叉类型:合并多个类型 const p: Point = { x: 10, y: 20 } 八、泛型:写一次,适配所有类型泛型是 TypeScript 的 “秘密武器”,让函数和组件更通用。function identity(value: T) { return value; } identity (100); // 指定 T 为 number 类型 function identity2 (value: T, msg: U): T { console.log(msg); return value; } identity2 (100, 'hello'); // 多泛型参数 let arr: Array = [1, 2, 3]; let arr2: Array = [1, 2, 3, 'hello']; 泛型让 identity 函数既能处理数字,也能处理字符串,不用写多个重复函数,代码复用性直接拉满。结语从 JavaScript 的 “盲盒变量” 到 TypeScript 的 “精准类型”,本质是从 “靠运气写代码” 到 “靠逻辑写代码” 的转变。写的代码都不严谨,那还写什么代码呢😄。TypeScript 不是给你套枷锁,而是给你装护栏 —— 它不会限制你的创造力,只会帮你提前避开那些低级 Bug。所以,不要害怕红色的报错,而是试着去解决它。——转载自:风止何安啊
为什么要有 TypeScript?让 JS 告别 “薛定谔的 Bug”
开源硬件平台
AI硬件电路
开源硬件平台
最近圈子炸了两次。第一次是Claude Code源码泄露事件。有人把Anthropic的核心代码扔到了GitHub上,虽然官方火速处理,但技术圈已经炸开了锅——大家突然发现,原来AI编程已经进化到了这个程度。第二次更刺激:豆包9块9。对,你没看错,9块9就能用上一个完整的AI编程工具。这价格,连一杯奶茶都买不到,但它已经开始抢程序员的饭碗了。然后就出现了两种声音:1.AI要把程序员干掉了2.AI最终不靠谱,锅还是要人来背的我专门花了三个月研究这件事,结论是——AI确实在抢饭碗,但抢的是另一群程序员的。什么意思?你会发现,现在最慌的不是那些真正写核心逻辑、做架构设计的人,而是那些每天写CRUD、做简单前端页面的"代码搬运工"。AI工具对这部分工作的替代效率,高得可怕。但反过来,那些真正能解决问题、能设计系统、能理解业务的人,AI不是来抢你饭碗的,它是来给你装上火箭助推器的。说白了,AI淘汰的不是程序员,而是低效的编程方式。你还在一个字母一个字母敲代码,别人已经在用AI生成框架、自动写测试、一键优化性能了。差距不是一点半点。(((顺便吆喝一句,技术大厂,前后端-测试机会,全国一线及双一线城市均有坑位坑位,待遇和稳定性还不错,感兴趣可以试试~所以问题来了:你和AI之间,是竞争关系,还是协作关系?这个问题的答案,决定了你是在2026年被淘汰,还是在2026年起飞。#嘉立创PCB#
AI 9块9抢程序员饭碗?我专门研究了三个月,结论可能和你想的不一样
开源硬件平台
01|什么是 Claude Code一句话:👉 Claude Code = 终端里的 AI 执行助手你可以用一句人话,让它帮你:写代码改文件整理资料分析数据自动完成任务📌 和普通 AI 最大区别:ChatGPT:告诉你怎么做 Claude Code:直接帮你做02|快速安装核心只有一句话:👉 先装 Node → 再装 Claude Code安装 Node.jsnodejs.org选择 LTS 版本验证:node -v npm -v安装 Claude Code npm install -g [@anthropic](https://x.com/@anthropic) -ai/claude-code 验证 claude --version 不废话,技术大厂捞人:前端/后端/测试,全国多地可选,待遇和稳定度都在线。→试试试试不吃亏 03|连接国内大模型一、编辑或新增 settings.json 文件MacOS 在.claude文件夹下创建settings.json, ~/.claude/settings.jsonWindows 为用户目录/.claude/settings.json #新增或修改里面的env字段# 注意替换里面的 `your_deepseek_key` 为您上一步获取到的 API Key { "env": { "ANTHROPIC_AUTH_TOKEN": "your_deepseek_key", "ANTHROPIC_BASE_URL":"https://api.deepseek.com/anthropic", "API_TIMEOUT_MS": "3000000", "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": 1, "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.5-air", "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.7", "ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-4.7" } } 二、再编辑或新增 .claude.json 文件用去跳过MacOS & Linux 为 ~/.claude.jsonWindows 为用户目录/.claude.json #再编辑或新增`.claude.json`文件# 新增 `hasCompletedOnboarding` 参数 { "hasCompletedOnboarding": true } 三、进入ClaudeCode确认首先确认系统默认的模型名称然后输入/status命令查看baseUrl和模型名称,输入命令 /status ——转载自:想不到一个好的ID
Claude Code 初学者必看指南
开源硬件平台
优质硬件创作分享平台
打赏记录
服务时间:周一至周六 9::00-18:00 · 联系地址:中国·深圳(福田区商报路奥林匹克大厦27楼) · 媒体沟通:pr@jlc.com · 集团介绍
移动社区