1

I am using node.js on Windows, with express module to generate an HTML where I would like to return data from a server processed function getfiledata() (i.e. I do not want to expose my js or txt file publicly).

I have been trying to use fetch() to return the value from getfiledata().

PROBLEM: I have not been able to get the data from getfiledata() returned to fetch().


HTML

<!DOCTYPE html>
<html>

<script type="text/javascript">
function fetcher() {
  fetch('/compute', {method: "POST"})
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(JSON.stringify(myJson));
  });
}
</script>

<input type="button" value="Submit" onClick="fetcher()">

</body>
</html>

^^ contains my fetch() function

server

var express = require("express");
var app     = express();
var compute = require("./compute")

app.post('/compute',compute.getfiledata);

compute.js

var fs = require('fs');

module.exports={
  getfiledata: function() {
    console.log("we got this far");
    fs.readFile("mytextfile.txt", function (err, data) {
        if (err) throw err;
        console.log("data: " + data);
        return data;
    })
  }
}

^^ contains my server side function

Note: from compute.js the console successfully logs:

we got this far

data: this is the data in the text file

but doesn't log from: console.log(JSON.stringify(myJson)) in the HTML

I suspect this is due to the fact I have not set up a "promise", but am not sure, and would appreciate some guidance on what the next steps would be.

Community
  • 1
  • 1
Wronski
  • 1,506
  • 3
  • 18
  • 37
  • 1
    This has nothing to do with `fetch`. Check the `express` documentation on how to serve JSON - you need to actually *write a HTTP response*. And yes, you cannot `return` from the `readFile` callback. – Bergi Oct 06 '18 at 12:46
  • 1
    Half of this issue (returning result from `getfiledata()`) is covered in great detail here: [How do I return the response from an asynchronous request](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call/14220323#14220323). – jfriend00 Oct 06 '18 at 17:39
  • Thank you, @jfriend00. I think for the time being I will try to handle this sync as per the Answer below. I have left a note with reference to the page you linked to update my code in the future to async if performance becomes an issue. – Wronski Oct 06 '18 at 21:29
  • @Bergi, I may convert my code to sync computation for now, and look to "write a HTTP response" in the future. Thank you. – Wronski Oct 06 '18 at 21:30

2 Answers2

1

I think you're on the way. I'd suggest making a few little changes, and you're all the way there.

I'd suggest using fs.readFileSync, since this is a really small file (I presume!!), so there's no major performance hit. We could use fs.readFile, however we'd need to plug in a callback and in this case I think doing all this synchronously is fine.

To summarize the changes:

  • We need to call getFileData() on compute since its a function.
  • We'll use readFileSync to read your text file (since it's quick).
  • We'll call res.json to encode the response as json.
  • We'll use the express static middleware to serve index.html.

To test this out, make sure all files are in the same directory.

Hit the command below to serve:

node server.js

And then go to http://localhost/ to see the web page.

server.js

var express = require("express");
var compute = require("./compute");
var app     = express();

app.use(express.static("./"));

app.post('/compute', (req, res, next) => {
    var result = compute.getfiledata();
    res.status(200).json({ textData: result } );
});

app.listen(80);

compute.js

var fs = require('fs');

module.exports = {
    getfiledata: function() {
      console.log("we got this far");
      return fs.readFileSync("mytextfile.txt", "utf8");
    }
}

index.html

<!DOCTYPE html>
<html>

<script type="text/javascript">
function fetcher() {
  fetch('/compute', {method: "POST"})
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(JSON.stringify(myJson));
    document.getElementById("output").innerHTML = "<b>Result: </b>" + myJson.textData;
  });
}
</script>
<input type="button" value="Submit" onClick="fetcher()">
<br><br>
<div id="output">
</div>
</body>
</html>

mytextfile.txt

Why, then, ’tis none to you, for there is nothing either good or bad, but thinking makes it so.

Terry Lennox
  • 29,471
  • 5
  • 28
  • 40
  • 1
    Thank you. The code works and I believe it will satisfy for now. I expect that in the future I will need to utilize async processing as that txt file may grow or there may be js computation on the contents of the txt file before it is served back to the browser. Take care! – Wronski Oct 06 '18 at 21:32
  • 1
    It should do, I wouldn't expect to see issues below maybe 100kb, but it's easily made async! – Terry Lennox Oct 06 '18 at 22:53
  • 1
    Recommending synchronous I/O in a server route handler is a bad idea. The OP is developing a server. They should learn the "right" way to use asynchronous I/O. – jfriend00 Oct 06 '18 at 23:43
  • I agree, however if we pay a 1ms penalty for synch. io, it will be a while before we hit any major issues. – Terry Lennox Oct 06 '18 at 23:55
0

You have a couple problems here. First, you can't directly return asynchronous data from getfiledata(). See How do I return the response from an asynchronous call? for a full description of that issue. You have to either use a callback to communicate back the asynchronous results (just like fs.readFile() does) or you can return a promise that fulfills to the asynchronous value.

Second, a route handler in Express such as app.post() has to use res.send() or res.write() or something like that to send the response back to the caller. Just returning a value from the route handler doesn't do anything.

Third, you've seen other suggestions to use synchronous I/O. Please, please don't do that. You should NEVER use synchronous I/O in any server anywhere except in startup code because it absolutely ruins your ability for your server to handle multiple requests at once. Instead, synchronous I/O forces requests to be processed serially (with the whole server process waiting and doing nothing while the OS is fetching things from the disk) rather than letting the server use all available CPU cycles to process other requests while waiting for disk I/O.

Keeping these in mind, your solution is pretty simple. I suggest using promises since that's the future of Javascript and node.js.

Your compute.js:

const util = require('util');
const readFile = util.promisify(require('fs').readFile);

module.exports= {
  getfiledata: function() {
    return readFile("mytextfile.txt");
  }
}

And, your server.js:

const express = require("express");
const compute = require("./compute");
const app     = express();

app.use(express.static("./"));

app.post('/compute', (req, res) => {
    compute.getfiledata().then(textData => {
        res.json({textData});
    }).catch(err => {
        console.log(err);
        res.sendStatus(500);
    });
});

app.listen(80);

In node.js version 10, there is an experimental API for promises built-in for the fs module so you don't even have to manually promisify it like I did above. Or you can use any one of several 3rd party libraries that make it really easy to promisify the whole fs library at once so you have promisified versions of all the functions in the module.

jfriend00
  • 683,504
  • 96
  • 985
  • 979