未处理的拒绝(类型错误):ships.reduce 不是函数

IT技术 javascript node.js reactjs
2021-05-15 18:30:04

我使用特定的 API 构建了一个船可视化器。API 返回一个 json 响应,我将它注入到一个表中。

问题:有时在白天我注意到应用程序会停止工作并抛出以下实例:

Unhandled Rejection (TypeError): ships.reduce is not a function

下面是错误的打印屏幕的完整性:

呃

在我使用的代码下方:

const ShipTracker = ({ ships, setActiveShip }) => {
  console.log("These are the ships: ", { ships });

  return (
    <div className="ship-tracker">
      <Table className="flags-table" responsive hover>
        <thead>
          <tr>
            <th>#</th>
            <th>MMSI</th>
            <th>TIMESTAMP</th>
            <th>LATITUDE</th>
            <th>LONGITUDE</th>
            <th>COURSE</th>
            <th>SPEED</th>
            <th>HEADING</th>
            <th>NAVSTAT</th>
            <th>IMO</th>
            <th>NAME</th>
            <th>CALLSIGN</th>
          </tr>
        </thead>
        <tbody>
          {ships.map((ship, index) => {
            // <-- Error Here
            const {
              MMSI,
              TIMESTAMP,
              LATITUDE,
              LONGITUDE,
              COURSE,
              SPEED,
              HEADING,
              NAVSTAT,
              IMO,
              NAME,
              CALLSIGN
            } = ship.AIS;

            const cells = [
              MMSI,
              TIMESTAMP,
              LATITUDE,
              LONGITUDE,
              COURSE,
              SPEED,
              HEADING,
              NAVSTAT,
              IMO,
              NAME,
              CALLSIGN
            ];

            return (
              <tr
                onClick={() =>
                  setActiveShip(
                    ship.AIS.NAME,
                    ship.AIS.LATITUDE,
                    ship.AIS.LONGITUDE
                  )
                }
                key={index}
              >
                <th scope="row">{index}</th>
                {cells.map(cell => (
                  <td key={ship.AIS.MMSI}>{cell}</td>
                ))}
              </tr>
            );
          })}
        </tbody>
      </Table>
    </div>
  );
};

谷歌地图.js

class BoatMap extends Component {
  constructor(props) {
    super(props);
    this.state = {
      ships: [],
      filteredShips: [],
      type: "All",
      shipTypes: [],
      activeShipTypes: []
    };
    this.updateRequest = this.updateRequest.bind(this);
    this.countDownInterval = null;
    this.updateInterval = null;
    this.map = null;
    this.maps = null;
    this.previousTimeStamp = null;
  }

  async updateRequest() {
    const url = "http://localhost:3001/hello";
    const fetchingData = await fetch(url);
    const ships = await fetchingData.json();
    console.log("fetched ships", ships);

    if (JSON.stringify(ships) !== "{}") {
      if (this.previousTimeStamp === null) {
        this.previousTimeStamp = ships.reduce(function(obj, ship) {
          obj[ship.AIS.NAME] = ship.AIS.TIMESTAMP;
          return obj;
        }, {});
      }

      this.setState({
        ships: ships,
        filteredShips: ships
      });

      this.props.callbackFromParent(ships);

      for (let ship of ships) {
        if (this.previousTimeStamp !== null) {
          if (this.previousTimeStamp[ship.AIS.NAME] === ship.AIS.TIMESTAMP) {
            this.previousTimeStamp[ship.AIS.NAME] = ship.AIS.TIMESTAMP;
            console.log("Same timestamp: ", ship.AIS.NAME, ship.AIS.TIMESTAMP);
            continue;
          } else {
            this.previousTimeStamp[ship.AIS.NAME] = ship.AIS.TIMESTAMP;
          }
        }

        let _ship = {
          // ship data ...
        };
        const requestOptions = {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(_ship)
        };
        await fetch(
          "http://localhost:3001/users/vessles/map/latlng",
          requestOptions
        );
        // console.log('Post', Date());
      }
    }
  }

  render() {
    const noHoverOnShip = this.state.hoverOnActiveShip === null;
    return (
      <div className="google-map">
        <GoogleMapReact
          bootstrapURLKeys={{ key: "key" }}
          center={{
            lat: this.props.activeShip ? this.props.activeShip.latitude : 37.99,
            lng: this.props.activeShip
              ? this.props.activeShip.longitude
              : -97.31
          }}
          zoom={5.5}
          onGoogleApiLoaded={({ map, maps }) => {
            this.map = map;
            this.maps = maps;
            // we need this setState to force the first mapcontrol render
            this.setState({ mapControlShouldRender: true, mapLoaded: true });
          }}
        >
          {this.state.mapLoaded && (
            <div>
              <Polyline
                map={this.map}
                maps={this.maps}
                markers={this.state.trajectoryData}
                lineColor={this.state.trajectoryColor}
              />
            </div>
          )}

          {Array.isArray(this.state.filteredShips) ? (
            this.state.filteredShips.map(ship => (
              <Ship
                ship={ship}
                key={ship.AIS.MMSI}
                lat={ship.AIS.LATITUDE}
                lng={ship.AIS.LONGITUDE}
                logoMap={this.state.logoMap}
                logoClick={this.handleMarkerClick}
                logoHoverOn={this.handleMarkerHoverOnShip}
                logoHoverOff={this.handleMarkerHoverOffInfoWin}
              />
            ))
          ) : (
            <div />
          )}
        </GoogleMapReact>
      </div>
    );
  }
}

export default class GoogleMap extends React.Component {
  state = {
    ships: [],
    activeShipTypes: [],
    activeCompanies: [],
    activeShip: null,
    shipFromDatabase: []
  };

  setActiveShip = (name, latitude, longitude) => {
    this.setState({
      activeShip: {
        name,
        latitude,
        longitude
      }
    });
  };

  setShipDatabase = ships => {
    this.setState({ shipFromDatabase: ships });
  };

  // passing data from children to parent
  callbackFromParent = ships => {
    this.setState({ ships });
  };

  render() {
    return (
      <MapContainer>
        {/* This is the Google Map Tracking Page */}
        <pre>{JSON.stringify(this.state.activeShip, null, 2)}</pre>
        <BoatMap
          setActiveShip={this.setActiveShip}
          activeShip={this.state.activeShip}
          handleDropdownChange={this.handleDropdownChange}
          callbackFromParent={this.callbackFromParent}
          shipFromDatabase={this.state.shipFromDatabase}
          renderMyDropDown={this.state.renderMyDropDown}
          // activeWindow={this.setActiveWindow}
        />
        <ShipTracker
          ships={this.state.ships}
          setActiveShip={this.setActiveShip}
          onMarkerClick={this.handleMarkerClick}
        />
      </MapContainer>
    );
  }
}

到目前为止我做了什么:

1)我也遇到了这个来源来帮助我解决问题,但没有运气。

2)此外,我咨询了这个其他来源,也是这一个,但他们都没有帮我找出这个问题可能是什么。

3)我深入研究了这个问题,也找到了这个来源

4) 我也读过这一篇但是,这些都没有帮助我解决问题。

5)我也发现这个来源非常有用,但仍然没有解决方案。

感谢您指出解决这个问题的正确方向。

3个回答

您可以解决的一种方法是,如果您的fetch请求内部出现问题,则默认为空数组updateRequest

async updateRequest() {
  const url = "http://localhost:3001/hello";
  const defaultValue = [];
  const ships = await fetchShips(url, defaultValue);

  // safe to use `Array` methods on an empty `array`
  if (this.previousTimeStamp === null) {
    this.previousTimestamp = ships.reduce(...);
  }
}

function fetchShips(url, defaultValue) {
  return fetch(url)
    .then(response => {
      if (!response.ok) {
        throw Error(response.statusText);
      }
      return response.json();
    })
    .then(data => {
      if (Array.isArray(data)) {
        return data;
      }
      // return the default value (empty array)
      // so that your application doesn't crash 
      return defaultValue;
    })
    .catch(error => {
      console.error(error.message);

      // catch other errors and return the default
      // value (empty array), so that your application doesn't crash
      return defaultValue;
    });
}

但是,您应该适当地处理错误并显示一条消息,指出出现问题以获得更好的用户体验,而不是根本不显示任何内容。

async updateRequest() {
  const url = "http://localhost:3001/hello";
  const defaultValue = [];
  const ships = await fetchShips(url, defaultValue).catch(e => e);

  if (ships instanceof Error) {
    // handle errors appropriately
    return;
  }
  // otherwise continue, with an empty array still a
  // possibility, but won't break the app
  if (this.previousTimeStamp === null) {
    this.previousTimestamp = ships.reduce(...);
  }
}

function fetchShips(url, defaultValue) {
  return fetch(url)
    .then(response => {
      if (!response.ok) {
        throw Error(response.statusText);
      }
      return response.json();
    })
    .then(data => {
      if (Array.isArray(data)) {
        return data;
      }
      // return the default value (empty array)
      // so that your application doesn't crash 
      return defaultValue;
    });
}

这不会解决为什么你的shipsprops并不总是一个数组,但它会帮助你保护你的代码免受这个未处理的异常的影响

<tbody>
   {Array.isArray(ships) && ships.map((ship, index) => {
      const {
        MMSI,
        // rest of your code here

这可能是因为ships在调用 reduce 时不是数组,也许是 null?

如果ships是父组件的状态被传递,那么它可能最初是空的然后得到更新,所以第一次渲染组件时它的调用减少为空?

如果您使用的 JavaScript 版本支持空传播,您可以使用

ships?.reduce 这样第一次渲染时,如果它是空的,它就不会尝试在空上调用 reduce 函数,但是当它渲染时,一切都应该没问题,这是一种非常常见的模式。

如果您的 JavaScript 版本不支持空传播,您可以使用

ships && ships.length > 0 && ships.reduce(...

所以你也应该改变

{ships.map((ship, index) => { // <-- Error Here


{
   ships?.map((ship, index) => 
   ...

   // or

  ships && ships.length > 0 && ships.map((ship, index) => 
  ....
}