Wednesday, February 2, 2022

[SOLVED] Nodejs & Express: properly resolve a promise

Issue

I have a simple node.js app that checks the current raspberry pi temperature using systeminformation package and also checks the public ip, since I'm exposing my pi using dynamic dns. It gets shown in the browser via a express get request. I also use PM2 for process management and keeping the app.js always up.

I want to log a timestamp, the IP and temperature to log.txt each time there is a get request to "/". The code below does the trick ALMOST. It displays temperature and IP in the browser, and while it logs the correct timestamp and IP to "log.txt", the temperature (stored in variable "temp") appears always "undefined".

const si = require('systeminformation');
const pip4 = require('public-ip');
const express = require('express');
const app = express();
const port = 3389;
const fs = require('fs');
let temp;

app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

app.get("/", (req, res, next) => {
  
  //get cpu temperature and store it in ${temp}
  si.cpuTemperature()
      .then(data => {temp = data; })
      .catch(error => console.error(error));
  
  //get public IP and store it in ${ip}    
  (async () => {
    let ip = (await pip4.v4());
  
  const text = `${Date()} from IP: ${ip} and core temp: ${temp}` + '\r\n';

  //append timestamp and IP info to log.txt
  fs.appendFile('./log/log.txt', text, (err) => {
    if (err) throw err;
    console.log('Successfully logged request.');
  });
  
  //display cpu temperature and IP in the browser
  setTimeout(() => {
    res.json({
      temp,
      ip
    });
  }, 250);

  })();

});

The http response works and displays the temperature and IP:

{"temp":{"main":37.485,"cores":[],"max":37.485},"ip":"x.x.x.x"}

But the logged temp entry in log.txt is always undefined:

Wed Aug 26 2020 13:07:30 GMT+0200 (Central European Summer Time) from IP: x.x.x.x and core temp: undefined

I understand it's due to an unresolved promise, but I can't seem to find a way to resolve it properly...


Solution

Make your handler fully async; don't bother with mixing and matching .then():

const si = require("systeminformation");
const pip4 = require("public-ip");
const express = require("express");
const app = express();
const port = 3389;
const fs = require("fs");

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

app.get("/", async (req, res) => {
  const temp = await si.cpuTemperature();
  const ip = await pip4.v4();
  const text = `${Date()} from IP: ${ip} and core temp: ${temp}\r\n`;
  fs.appendFile("./log/log.txt", text, (err) => {
    if (err) console.error(`Logging: ${err}`);
  });
  res.json({ temp, ip });
});

You can make it a little faster by doing the IP and CPU temperature queries in parallel:

app.get("/", async (req, res) => {
  const [temp, ip] = await Promise.all([si.cpuTemperature(), pip4.v4()]);
  const text = `${Date()} from IP: ${ip} and core temp: ${temp}\r\n`;
  fs.appendFile('./log/log.txt', text, (err) => {
    if (err) console.error(`Logging: ${err}`);
  });
  res.json({temp, ip});
});

Both of these are missing error handling, though.

EDIT: One more variation that also logs as JSON, to express (heh) all objects correctly:

app.get("/", async (req, res) => {
  const [temp, ip] = await Promise.all([si.cpuTemperature(), pip4.v4()]);
  const logData = JSON.stringify({date: Date(), ip, temp}) + '\r\n';
  fs.appendFile('./log/log.txt', logData, (err) => {
    if (err) console.error(`Logging: ${err}`);
  });
  res.json({temp, ip});
});


Answered By - AKX
Answer Checked By - Clifford M. (WPSolving Volunteer)