普通视图

发现新文章,点击刷新页面。
今天 — 2025年8月25日掘金 前端
昨天 — 2025年8月24日掘金 前端

写给自己的 LangChain 开发教程(二):格式化数据 & 提取 & 分类

作者 朱程
2025年8月24日 17:05
1. 格式化数据 在实际应用中,我们的应用的前端和后端之间的数据交换是一个确切参数的 json 数据,我们可能会期望大模型将不可控的用户输入,转换成我们定义好的某个规整结构进行输出,这是一个很常见的需

Vue状态管理工具pinia的使用以及Vue组件通讯

作者 lichenyang453
2025年8月24日 16:24

         状态管理工具redux以及resso和zustand都用过了,这里pinia和resso是一样的,存储的状态数据直接写,方法也是直接写就可以了,会自动进行类型推断。

1.pinia的使用

        首先入口文件引入pinia之后创建pinia对象用use安装pinia和路由一样

import { createApp } from "vue";
import App from './App.vue'
//引入pinia
import { createPinia } from "pinia";
const app =createApp(App)
//创建pinia
const pinia=createPinia()
//安装pinia
app.use(pinia)
app.mount('#app')

        之后创建store文件夹,不同的数据以及数据方法单独创建文件。

编辑

        然后用defineStore初始化store容器。

        编辑

        actions写方法,state写数据。

        之后就可以在组件里面引入useCountStore函数调用生成countStore对象就可以引用里面的方法和数据了。

<template>
    <div class="count">
        <h2>当前求和{{ sum }}</h2>
        <select name="number" id="" v-model="n">
            <option value="1">1</option>
            <option value="2">2</option>
        </select>
        <button @click="add"></button>
        <button @click="increase"></button>
    </div>
</template>
<script setup lang="ts" name="Count">
    import { ref } from 'vue';
    import {useCountStore} from '@/store/count'
    import { storeToRefs } from 'pinia';
    const countStore=useCountStore()
    console.log(countStore.sum);
    let {sum} = storeToRefs(countStore)
    let n =ref(1)
    const add=()=>{
        //第一种修改
        //countStore.sum=countStore.sum+Number(n.value)
        //第二种修改 可以一次修改多种数据
        // countStore.$patch({
        //     sum:+Number(n.value)
        // })
        //第三种修改方式actions
        countStore.add(n.value)
    }
      const increase=()=>{
       countStore.sum=countStore.sum-Number(n.value) 
    }
    //store的监听事件
    //subscribe回调函数接收两个参数 mutate修改的数据 state就是store存储的数据
    countStore.$subscribe((mutate,state)=>{
        console.log('state改变了',mutate,state);
    })
</script>
<style scoped>
    .count{
        background-color: aqua;
    }

    select{
        width: 50px;
    }
    button{
        height: 100px;
        width: 100px;
        background-color: aquamarine;
        color: #000;
    }
</style>

        还有一个$subscribe可以监听store种的数据变化,mutate第一个参数是变化的数据,state就是存储的所以数据对象。

2.Vue组件通讯的方式、

        包括之前的vue博客,并不是零基础的,是在写过完整的前端项目之后去学的,所以原理大致都是通的,只是方法用法不同。

        这里通讯方式其实和React是相通的,这里只是总结。

        编辑

        这里和react一致。父子和子夫通过props操作。

编辑

编辑

        分享数据的组件on绑定事件,然后把数据放到回调函数中,然后触发事件的时候把参数传递之后,绑定事件的回调函数就会接收参数。

        记得组件卸载的时候解绑。和pubsub一致

编辑

        v-model双向绑定,无非就是把数据绑定到input的value上,然后@input事件发生的时候把value绑定到数据上,和react受控组件一个道理。

        编辑

        父子传递props子没有接收就会放到attrs属性中,可以借此用props传递给孙组件。

        provide提供数据子组件inject拿。子获取父的时候需要父暴露数据,父获取子的实例对象也需要子暴露数据,不然数据是隐藏的。

        

如何更好的封装一个接口轮询?

2025年8月24日 15:59

相信大家做pc端开发的时候都会遇到一个需求,比如扫码登录的时候,通过手机端扫码之后一般都需要通过轮询查询登录状态,其实这是个很常见的需求,但是没做过的小伙伴可能就会不经意间写出一个bug。就是比如1秒钟轮询一次的时候,如果上次的接口还没成功响应那么下一次的接口就又发送出去了。正确做法应该是等待上个响应后再发出下一次请求。

其实解决bug的关键就是如何等待上次请求响应之后才发出下一次的请求,等待具体应该怎么实现呢?可以参考下我的思路。

具体如下:
type PollingEvent = 'success' | 'error' | 'stop';

interface PollingOptions {
 interval?: number; // 轮询间隔时间
 retries?: number; // 接口失败后重试次数
 immediate?: boolean; // 是否立即调用一次接口
}

type EventCallback<T> = (data?: T | Error) => void;

class PollingService<T = any> {
 private requestFn: () => Promise<T>;
 private interval: number;
 private retries: number;
 private immediate: boolean;

 private timer: ReturnType<typeof setTimeout> | null = null;
 private isPolling: boolean = false;
 private currentRetry: number = 0;
 private listeners = new Map<PollingEvent, Set<EventCallback<T>>>();

 constructor(requestFn: () => Promise<T>, options: PollingOptions = {}) {
   this.requestFn = requestFn;
   this.interval = options.interval || 1500;
   this.retries = options.retries || 0;
   this.immediate = options.immediate ?? true;
 }

 on(event: PollingEvent, callback: EventCallback<T>): () => void {
   if (!this.listeners.has(event)) {
     this.listeners.set(event, new Set());
   }
   const listeners = this.listeners.get(event)!;
   listeners.add(callback);

   return () => listeners.delete(callback);
 }

 start(): void {
   if (this.isPolling) return;
   this.isPolling = true;
   this.currentRetry = 0;

   if (this.immediate) {
     this.execute();
   } else {
     this.scheduleNext();
   }
 }

 stop(): void {
   this.isPolling = false;
   this.clearTimer();
   this.emit('stop');
 }

 private async execute(): Promise<void> {
   if (!this.isPolling) return;
   
   // 其实关键就在这里了,我们借助 async/await来实现
   try {
     const data = await this.requestFn();
     this.currentRetry = 0;
     this.emit('success', data);
   } catch (error) {
     this.handleError(error as Error);
     return;
   }

   this.scheduleNext();
 }

 private handleError(error: Error): void {
   this.emit('error', error);

   if (this.currentRetry < this.retries) {
     this.currentRetry++;
     this.execute(); // 立即重试
     return;
   }

   this.stop();
 }

 private scheduleNext(): void {
   if (!this.isPolling) return;

   this.clearTimer();
   this.timer = setTimeout(() => {
     this.execute();
   }, this.interval);
 }

 private clearTimer(): void {
   if (this.timer) {
     clearTimeout(this.timer);
     this.timer = null;
   }
 }

 private emit(event: PollingEvent, data?: T | Error): void {
   const listeners = this.listeners.get(event);
   if (listeners) {
     listeners.forEach((callback) => callback(data));
   }
 }
}

export { PollingService };

完成上述代码之后,在页面中直接引入使用即可。

使用如下:
import React, { useEffect, useRef } from 'react';
import { PollingService } from 'xxx/PollingService';

const Demo = () => {
    const pollingRef = useRef<any>(null);
    
    // 轮询的方法
    const requesFun = async () => {
        const res = await axios.post('xxx')
        // todo sth
    }
    
    useEffect(() => {
        pollingRef.current = new PollingService()
        // 启动轮询
        pollingRef.current.start()
        
        // !!!组件卸载一定要关闭轮询
        return () => {
            pollingRef?.current?.stop()
        }
    }, [)
    <div>测试页面</div>
}
export default Demo;

震惊!多核性能反降11%?node接口压力测试出乎意料!

2025年8月24日 15:50

背景

最近在优化一个Node.js后端服务时,我决定对多进程模式进行一次真实压力测试。大家都知道Node.js是单线程模型,通常我们会用PM2的Cluster模式来启动多进程,试图榨干多核CPU的性能。理论上,这应该能让服务性能起飞,但事实真的如此吗?

测试设计

但我决定不做假设,而是用真实数据说话。我设计了一个压力测试对比:

  • 单进程模式:一个 Node.js 实例运行在主核上
  • 多进程模式:两个进程分别运行在两个核心上(通过 PM2 启动)

测试环境为:

  • 机器:2核2G服务器
  • Node.js 版本:24+
  • 测试工具:autocannon
  • 测试条件:10秒内发送100个并发请求

测试结果

单核心接口压力测试开始,10秒钟请求100次接口

autocannon -c 100 -d 10 http://your-server-ip:3000/api/endpoint

image.png

结果让人惊喜:平均延迟354ms哈哈哈,QPS达到278.11,数据传输量543 kB/s。表现....

多进程压测:搓手期待性能起飞,接下来配置PM2多进程模式,创建ecosystem.config.js:

module.exports = { 
apps: [
{ name: "my-node-api", 
script: "/var/www/my-node-api/index.js",
instances: 2, // 明确指定2个实例(或者max) 
exec_mode: "cluster", //cluster-多线程模式 fork-单线程模式
env: {
      NODE_ENV: "production", PORT: 3000
    },
error_file: "/var/www/my-node-api/logs/err.log",
out_file: "/var/www/my-node-api/logs/out.log", 
time: true, 
max_memory_restart: "400M"
  }]
}

开启多核心后继续压力测试,结果意想不到...

image.png

满怀期待启动服务,结果...让人大跌眼镜!

性能不升反降?数据说话

对比测试数据后,我发现了一个反直觉的结果:

1. 延迟性能下降 ⚠️

指标 单进程 多进程 变化
平均延迟 354ms 394ms +11.4%
中位数延迟 348ms 390ms +12.1%
最大延迟 496ms 517ms +4.2%

2. 吞吐量下降 ⚠️

指标 单进程 多进程 变化
请求/秒 278.11 249.9 -10.2%
数据传输量 543 kB/s 487 kB/s -10.3%

3. 稳定性略有改善 ✅

  • 延迟波动(标准差):从 43.15ms 降至 41.49ms,改善 3.8%
  • 吞吐波动(标准差):大幅减少,改善 25.4%

多进程模式为什么反而更差了?

没有想到在开启多核心的之后的压力测试并没有得到提升,反而接口延迟又变高了,请求数据量也从单核的5.48M变成了双核4.87M,我反复对比了下。 理论上多进程应该更好,但实际测试结果却相反。我分析主要有以下几个原因:

  1. 进程间需要通信:多个进程之间需要协调工作,这部分额外工作单进程不需要做
  2. 切换成本高:系统在不同进程间切换比在单个进程内切换开销更大
  3. 内存无法共享:每个进程都有自己的内存空间,无法共享数据
  4. 负载均衡本身也需要资源:分配请求的工作也需要消耗计算资源

那为什么稳定性反而提升了?

虽然性能下降了,但稳定性的确略有提升。这是因为:

  • 压力分散到多个进程,单个进程负担减轻
  • 一个进程出问题不会影响整个服务
  • 错误被控制在单个进程内

结论与建议

多进程模式不是万能的!特别是在:

  • 访问量不大时
  • 业务逻辑不复杂时
  • 主要是处理I/O操作时(如常见的API服务)

单进程模式可能效果更好。

什么时候应该用多进程?

  • 访问量非常大时
  • 需要处理大量计算任务时(如图片处理、数据加密等)
  • 对稳定性要求特别高时

这次测试说明:

没有最好的方案,只有最适合的方案~

JavaScript - 观察者模式的实现与应用场景

作者 AlenLi
2025年8月23日 17:29

1. 什么是观察者模式

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系。当主题带有的状态被改变时,所有观察者都会收到通知并自动更新。

主题(Subject):Subject也可以称为被观察者,它维护一个Observer列表,实现新增、删除、通知Observer更新的方法。

观察者(Observer):Observer是接收主题通知的对象,需要实现一个更新方法,当收到Subject的通知时,调用该方法进行更新。

具体主题(Concrete Subject):ConcreteSubject继承了Subject,是Subject的具体实现类,我们在其中定义主题状态,当状态发生某些变化时通知Observer更新。

具体观察者(Concrete Observer):ConcreteObserver是Observer的具体实现类。它定义了在收到通知时需要执行的具体操作。

需要注意的是,在观察者模式的依赖关系中,并不是观察者主动拉取主题消息,而是被动的接收主题通知并产生相应变化

2. 观察者模式举例

以拍卖会上拍卖者与竞价者的一对多的依赖关系举例:当价格更新时,所有的竞价者都会收到通知。

3. 例子的代码实现

定义Subject类与Observer类

/**
 * 主题(Subject)中维护一个观察者列表,实现新增、删除观察者、与通知观察者更新的方法
 */
class Subject {
  constructor() {
   this.observerList = [];
  }
  
  /**
   * 新增观察者方法
   * @param observer
   */
  addObserver(observer) {
   this.observerList.push(observer);
  }
  
  /**
   * 移除观察者方法
   * @param observer
   */
  removeObserver(observer) {
   const findBidderIndex = this.observerList.findIndex(observerItem => {
    return observerItem.id === observer.id;
   })
   if(findBidderIndex > -1) {
    this.observerList.splice(findBidderIndex, 1);
   }
  }
  
  /**
   * 通知观察者进行更新
   * @param data
   */
  notify(data) {
   this.observerList.forEach(observerItem => {
    observerItem.update(data);
   })
  }
}

/**
 * 观察者(Observer)中应定义一个更新方法
 */
class Observer {
  
  /**
   * 更新方法
   * @param data
   */
  update(data) {
  }
}

定义Subject与Observer的具体类

/**
 * 定义一个拍卖师类,作为具体主题(ConcreteSubject)
 */
class Auctioneer extends Subject {
  constructor() {
   super();
   this.state = '30';
  }
  
  setState(state){
   this.state = state;
   this.notify(this.state);
  }
  
  getState(){
   return this.state;
  }
}

/**
 * 定义一个竞拍者类,作为具体观察者(ConcreteObserver),实现update方法
 */
class Bidder extends Observer {
  constructor(id) {
   super();
   this.id = id;
  }
  
  update(data) {
   console.log('竞拍者 ' + this.id + ' 收到通知:' + data);
  }
}

创建实例、修改状态

//实例化具体主题
const auctioneer = new Auctioneer();

//实例化具体观察者
const bidder1 = new Bidder(1);
const binder2 = new Bidder(2);
const binder3 = new Bidder(3);

//将具体观察者推入具体观察者列表
auctioneer.addObserver(bidder1);
auctioneer.addObserver(binder2);
auctioneer.addObserver(binder3);
auctioneer.removeObserver(binder2);

//修改状态
auctioneer.setState(20);

打印结果

竞拍者 1 收到通知:20
竞拍者 3 收到通知:20

4.观察者模式的适用场景

当目标对象与其它对象产生一对多关系时,当目标对象改变,希望其它对象也发生相应变化

比如在订单页面使用v-for生成出来几个Tab组件,通过滑动切换。这些Tab页标识着该用户所有订单的状态。

当用户操作的某一笔订单的状态发生改变时,我们希望每个与该笔订单相关的所有Tab组件都发生变化,都重新走一遍查询接口。

这时就可以定义这一组一对多的依赖,将相关标签页的key推入Store中定义的列表作为状态。使用观察者模式使那些存于列表中的Tab刷新。

❌
❌