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

Node.js调用币安API实现本地订单簿时报Cannot read property '0'错误

错误根因

  1. 删除逻辑循环对象写错:sort_array函数中,当array2[x][1]为0需要删除对应价格档位时,错误遍历了array2的索引i,却去访问array1[i]。当array2长度大于array1时,array1[i]为undefined,访问[0]属性就触发报错。
  2. 数组遍历过程中元素被提前删除:即便循环对象写对,用for in遍历数组的同时调用splice删除元素,会导致数组长度动态变化,后续遍历到的索引对应的元素可能已经被移除,变为undefined。
  3. 初始化时序错乱:程序运行初期订单簿快照还未请求完成,asks/bids可能还未初始化,或者状态切换逻辑有误,导致未完成初始化的数组被传入sort_array函数。

修复方案

1. 重写sort_array函数

function sort_array(array1, array2, type = 'asks') {
  // 先校验传入数组合法性,避免非法输入报错
  if (!Array.isArray(array1) || !Array.isArray(array2)) return array1 || [];

  for (const updateItem of array2) {
    const [price, quantity] = updateItem;
    // 数量为0则删除对应价格档位,兼容字符串/数字两种格式
    if (quantity === '0' || Number(quantity) === 0) {
      // 倒序遍历删除,不会影响未遍历元素的索引
      for (let i = array1.length - 1; i >= 0; i--) {
        // 可选链避免元素不存在时报错
        if (array1[i]?.[0] === price) {
          array1.splice(i, 1);
          break;
        }
      }
    } else {
      let existed = false;
      for (let i = 0; i < array1.length; i++) {
        if (array1[i]?.[0] === price) {
          array1[i] = updateItem;
          existed = true;
          break;
        }
      }
      if (!existed) {
        array1.push(updateItem);
      }
    }
  }
  // 补全订单簿排序逻辑:卖盘按价格升序,买盘按价格降序
  array1.sort((a, b) => {
    const priceA = Number(a[0]);
    const priceB = Number(b[0]);
    return type === 'asks' ? priceA - priceB : priceB - priceA;
  });
  return array1;
}

2. 修复update_orderbook状态流转逻辑

exports.update_orderbook = function(data) {
  if (data.e !== "depthUpdate") return;

  // 未就绪时先缓存深度消息,不要拼接为字符串
  if (this.ready < 2) {
    this.depthBuffer = this.depthBuffer || [];
    this.depthBuffer.push(data);
    // 第一次收到深度消息时触发快照请求,仅触发一次
    if (this.ready === 0) {
      this.ready = 1;
      console.log("Stage 1: 开始请求订单簿快照");
      // 等待快照异步请求返回,完成初始化后再切换状态处理增量
      this.get_orderbook_history().then(snapshot => {
        this.lastUpdateId = snapshot.lastUpdateId;
        this.asks = snapshot.asks;
        this.bids = snapshot.bids;
        this.ready = 2;
        console.log("Stage 2: 快照初始化完成,开始处理缓存的深度消息");
        // 按顺序处理所有缓存的增量消息
        this.depthBuffer.forEach(cachedData => {
          this.processDepthUpdate(cachedData);
        });
        this.depthBuffer = [];
      });
    }
    return;
  }

  // 就绪后直接处理新的深度消息
  this.processDepthUpdate(data);
}

// 抽离增量更新逻辑,方便缓存消息和实时消息复用
exports.processDepthUpdate = function(data) {
  if (data.U <= this.lastUpdateId + 1 && data.u >= this.lastUpdateId + 1) {
    this.asks = sort_array(this.asks, data.a, 'asks');
    this.bids = sort_array(this.bids, data.b, 'bids');
    this.lastUpdateId = data.u;
    console.log("Stage 3: 订单簿更新完成");
  }
}

3. 额外注意事项

  • WebSocket收到的原始消息需要先解析为JSON对象,再传入update_orderbook,不要直接传入原始字符串
  • 币安接口返回的价格、数量均为字符串格式,做数值判断时要注意类型兼容

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 19:54:03