You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js应用运行1天后价格百分比异常及本地运行影响问询

Next.js价格百分比计算异常问题求助

问题描述

  • 预期逻辑:代码中priceHistory的month、week、day分别通过setInterval按月、周、日重置;运行1天后,日百分比(pctChange.day)应更新,周(pctChange.week)和月(pctChange.month)百分比应保持一致。
  • 实际异常:运行1天后,周与日百分比一致,月百分比却出现差异,不符合预期。

疑问

本地机器频繁开关机,是否会影响内存中变量的存储,导致此异常?

已尝试操作

添加调试语句多次运行,仍未定位原因。

相关代码

import { NextResponse } from 'next/server';

let holdersCount: number | null = null;
let priceData: { priceUsd: number, marketCap: string ,liquidity: string} = { priceUsd: 0, marketCap: '' ,liquidity: ''};
let priceHistory = {
  month: 0, // Use numbers
  day: 0,
  week: 0,
};

let pctChange = {
  month: '0.00', // Keep as strings for output
  day: '0.00',
  week: '0.00',
};

let isInitialFetch = true;



{/* deleted the fetch holder function for simplicity*/}

const fetchPriceData = async () => {
  try {
    const response = await fetch('https://api.dexscreener.com/latest/dex/tokens/0x9a26F5433671751C3276a065f57e5a02D2817973');
    const data = await response.json();
    priceData = {
      priceUsd: parseFloat(data.pairs[0].priceUsd) || 0, // Ensure it's always a valid number
      marketCap: data.pairs[0].marketCap,
      liquidity: data.pairs[0].liquidity.usd 
    };

    if (isInitialFetch) {
      // Only set the initial prices after the first successful fetch
      priceHistory.month = priceData.priceUsd;
      priceHistory.day = priceData.priceUsd;
      priceHistory.week = priceData.priceUsd;
      isInitialFetch = false; // Disable the flag after setting the initial prices
      console.log('inital state done')
    }

    console.log("Old price:", priceHistory.month);
    console.log("Current price:", priceData.priceUsd);

    // Calculate the percentage change for each timeframe
    const calculatePercentChange = (oldPrice: number, newPrice: number): string => {
      if (oldPrice === 0 || isNaN(oldPrice) || isNaN(newPrice)) {
        return '0'; // Avoid invalid calculations or division by 0
      }

      const percentChange = ((newPrice - oldPrice) / oldPrice) * 100;
      return percentChange > 0 ? `+${percentChange.toFixed(2)}` : percentChange.toFixed(2);
    };

    // Only calculate percentage change if the old prices have been set and are valid
    if (priceHistory.month > 0) {
      pctChange.month = calculatePercentChange(priceHistory.month, priceData.priceUsd);
    }
    if (priceHistory.day > 0) {
      pctChange.day = calculatePercentChange(priceHistory.day, priceData.priceUsd);
    }
    if (priceHistory.week > 0) {
      pctChange.week = calculatePercentChange(priceHistory.week, priceData.priceUsd);
    }

    console.log("Price data updated:", priceData);
    console.log("Percentage change:", pctChange);
  } catch (error) {
    console.error('Error fetching price data:', error);
  }
};

// Fetch holders data every 8 hrs
setInterval(fetchHoldersData, 8 * 60 * 60 * 1000);

// Fetch price data every 8 hrs
setInterval(fetchPriceData, 8 * 60 * 60 * 1000);

// Reset the price history every hour, day, and week
setInterval(() => {
  priceHistory.month = priceData.priceUsd;
},31 * 24 * 60 * 60 * 1000); // Every month

setInterval(() => {
  priceHistory.day = priceData.priceUsd;
}, 24 * 60 * 60 * 1000); // Every day

setInterval(() => {
  priceHistory.week = priceData.priceUsd;
}, 7 * 24 * 60 * 60 * 1000); // Every week

// Initial fetch
fetchHoldersData();
fetchPriceData();

export async function GET() {
  return NextResponse.json({
    holders: holdersCount,
    price: priceData.priceUsd.toFixed(2),
    marketCap: priceData.marketCap,
    liquidity: priceData.liquidity,
    percentChanges: pctChange,
  });
}

运行截图

运行24小时后的返回值截图


问题分析与解决方案

1. 核心问题:内存变量生命周期与定时任务不可靠性

你的代码依赖内存全局变量存储priceHistory和pctChange,但Next.js API路由并非长期运行进程:

  • 本地频繁开关机直接导致进程重启,内存中所有变量会被重置为初始值,isInitialFetch重新变为true,此时priceHistory会被设为重启时的当前价格,而非保留重启前的历史基准值。
  • 即使不重启,setInterval在Node.js/Next.js环境中无法保证精确执行,进程休眠、资源回收都可能导致定时任务延迟或丢失。

2. 异常原因拆解

运行1天后的差异本质是进程重启导致priceHistory.month被重新初始化,而week/day的重置逻辑未按预期触发:

  • 假设重启应用后,isInitialFetch重置为true,新的priceHistory.month会被设为重启时的当前价格,而非原始初始价格。
  • priceHistory.day会从重启时间开始计算24小时后重置,priceHistory.week则需要7天,这就导致month的基准价格与week/day不一致,最终百分比计算出现差异。

3. 解决方案:持久化存储替代内存变量

必须将priceHistory的基准值持久化到外部存储(如文件、数据库、Redis),而非依赖内存:

示例修改思路(文件存储)
import fs from 'fs/promises';
import path from 'path';

const HISTORY_FILE_PATH = path.join(process.cwd(), 'price-history.json');

// 读取历史基准价格
async function loadPriceHistory() {
  try {
    const data = await fs.readFile(HISTORY_FILE_PATH, 'utf8');
    return JSON.parse(data);
  } catch (err) {
    return { month: 0, day: 0, week: 0, timestamps: { month: 0, day: 0, week: 0 } };
  }
}

// 保存历史基准价格
async function savePriceHistory(history) {
  await fs.writeFile(HISTORY_FILE_PATH, JSON.stringify(history), 'utf8');
}

const fetchPriceData = async () => {
  try {
    const response = await fetch('https://api.dexscreener.com/latest/dex/tokens/0x9a26F5433671751C3276a065f57e5a02D2817973');
    const data = await response.json();
    priceData = {
      priceUsd: parseFloat(data.pairs[0].priceUsd) || 0,
      marketCap: data.pairs[0].marketCap,
      liquidity: data.pairs[0].liquidity.usd 
    };

    let priceHistory = await loadPriceHistory();
    const now = Date.now();

    // 初始化逻辑:无基准值时设置初始价格和时间戳
    if (priceHistory.month === 0) {
      priceHistory.month = priceData.priceUsd;
      priceHistory.day = priceData.priceUsd;
      priceHistory.week = priceData.priceUsd;
      priceHistory.timestamps = {
        month: now,
        day: now,
        week: now
      };
      await savePriceHistory(priceHistory);
      console.log('Initial state saved');
    }

    // 基于时间戳判断是否需要重置基准(替代setInterval)
    const MONTH_MS = 31 * 24 * 60 * 60 * 1000;
    const DAY_MS = 24 * 60 * 60 * 1000;
    const WEEK_MS = 7 * 24 * 60 * 60 * 1000;

    if (now - priceHistory.timestamps.month >= MONTH_MS) {
      priceHistory.month = priceData.priceUsd;
      priceHistory.timestamps.month = now;
      await savePriceHistory(priceHistory);
    }
    if (now - priceHistory.timestamps.day >= DAY_MS) {
      priceHistory.day = priceData.priceUsd;
      priceHistory.timestamps.day = now;
      await savePriceHistory(priceHistory);
    }
    if (now - priceHistory.timestamps.week >= WEEK_MS) {
      priceHistory.week = priceData.priceUsd;
      priceHistory.timestamps.week = now;
      await savePriceHistory(priceHistory);
    }

    // 百分比计算逻辑不变
    const calculatePercentChange = (oldPrice: number, newPrice: number): string => {
      if (oldPrice === 0 || isNaN(oldPrice) || isNaN(newPrice)) {
        return '0';
      }
      const percentChange = ((newPrice - oldPrice) / oldPrice) * 100;
      return percentChange > 0 ? `+${percentChange.toFixed(2)}` : percentChange.toFixed(2);
    };

    pctChange.month = calculatePercentChange(priceHistory.month, priceData.priceUsd);
    pctChange.day = calculatePercentChange(priceHistory.day, priceData.priceUsd);
    pctChange.week = calculatePercentChange(priceHistory.week, priceData.priceUsd);

    console.log("Price data updated:", priceData);
    console.log("Percentage change:", pctChange);
  } catch (error) {
    console.error('Error fetching price data:', error);
  }
};

4. 额外优化建议

  • 用时间戳判断替代setInterval:避免定时任务的不可靠性,每次获取价格时主动检查是否需要重置基准。
  • 生产环境建议使用Redis或数据库存储,比文件存储更可靠且支持并发访问。

内容的提问来源于stack exchange,提问作者vectorizedaynon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 12:09:53