'Nodejs Async/Await not working on a file stream function
Currently, I am trying to parse a csv file with {Lat, Lng, Mag}. Also, I am creating Polygons to use the magnitude. If the point {Lat, Lng} is in the Polygon, then the Magnitude will be added to the Polygon Magnitude. The function works as described, but the data is computed very slowly and the response does not contain the magnitude calculated. Below is my code:
const express = require('express');
const router = express.Router();
const csv = require('csv-parser')
const fs = require('fs');
const { exit } = require('process');
const { resolveObjectURL } = require('buffer');
/*
Things to parse:
1.) offense count
2.) latitude
3.) longitude
*/
function getPolygons() {
let Count = 16
let ParentPolygon = [
{lat: 30.118713, lng: -95.812735 }, // Top Left
{lat: 30.118713, lng: -95.034621 }, // Top Right
{lat: 29.49764, lng: -95.034621}, // Bottom Right
{lat: 29.49764, lng: -95.812735 } // Bottom Left
]
length = Math.abs(30.11873 - 29.49764)
width = Math.abs(-95.812735 + 95.034621)
length_cut = length/Math.sqrt(Count)
width_cut = width/Math.sqrt(Count)
let result = []
let point = ParentPolygon[0] // Start at top left of Parent Polygon
for(let j = 0; j < Math.sqrt(Count); j++){
let FindNewChild = true;
let FirstChildBottomLeft = {lat: 0, lng: 0}
for(let i = 0; i < Math.sqrt(Count); i++ ){
let ChildPolygon = [
{lat: point.lat, lng: point.lng }, //Top left
{lat: point.lat, lng: point.lng + width_cut}, //Top Right
{lat: point.lat - length_cut, lng: point.lng + width_cut}, //Bottom Right
{lat: point.lat - length_cut, lng: point.lng} //Bottom Left
]
if(FindNewChild){
FirstChildBottomLeft = ChildPolygon[3]
FindNewChild = false;
}
if(i === Math.sqrt(Count) - 1){
point = FirstChildBottomLeft
FindNewChild = true;
} else
{
point = ChildPolygon[1]
}
result.push({mag: 0, square: ChildPolygon})
}
}
return result;
}
/*
{
{
magnitude: 0,
square: [
{lat: 30, lng: 45},
{lat: 30, lng: 55},
{lat: 40, lng: 45},
{lat: 40, lng: 55}
]
},
{
magnitude: 0,
square: {
{lat: 30, lng: 45},
{lat: 30, lng: 55},
{lat: 40, lng: 45},
{lat: 40, lng: 55}
}
},
{
magnitude: 0,
square: {
{lat: 30, lng: 45},
{lat: 30, lng: 55},
{lat: 40, lng: 45},
{lat: 40, lng: 55}
}
}
}
*/
async function getParsedData() {
let results = await getPolygons();
let file = '../server/datafiles/NIBRSPublicViewJan-Mar22.csv' // hard coded file & file directory
await fs.createReadStream(file)
.pipe(csv({mapValues: ({value}) => parseFloat(value)}))
.on('data', (data) => {
if(data.lat && data.lng && data.mag) { // check if a latitude and longitude exist
let point = ({lng: parseFloat(data.lat), lat: parseFloat(data.lng), mag: parseFloat(data.mag)})
results.forEach(polygon => {
let TopLeft = polygon.square[0]
let BottomRight = polygon.square[2]
//Something is wrong with this statement
if(point.lat < TopLeft.lat && point.lng > TopLeft.lng){
if(point.lat > BottomRight.lat && point.lng < BottomRight.lng){
//console.log("Top Left:" + TopLeft.lat + ", " + TopLeft.lng, "Point: "+ point.lat + ", " + point.lng)
//console.log("Bottom Right:" + BottomRight.lat + ", " + BottomRight.lng, "Point: "+ point.lat + ", " + point.lng)
//console.log("point Magnitude: ", point.mag, "Polygon mag: ", polygon.mag)
polygon.mag += point.mag
}
}
})
}
})
.on('end', () => {
console.log("What should go to Front-end", results); // check to see if the results are correct
});
return results;
}
router.get('/', async (req, res) => {
getParsedData()
.then(results => {
console.log("To Front-end" , results)
res.json(results)
})
})
module.exports = router;
EDIT: The result should look like this:
[
{ mag: 0, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 279, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 880, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 429, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 8, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 5428, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 8605, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 58, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 1224, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 19229, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 17830, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 756, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 0, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 2260, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 2873, square: [ [Object], [Object], [Object], [Object] ] },
{ mag: 1923, square: [ [Object], [Object], [Object], [Object] ] }
]
Instead I am getting a result with all magnitudes set to 0
Solution 1:[1]
There are a number of issues here, but the main issue is that await fs.createReadStream(...) does not do anything useful. await only does something useful when you're awaiting a promise and fs.createReadStream() returns a stream, not a promise.
So, nothing in getParsedData() actually communicates back to the caller when the stream is done.
Since streams in nodejs are event based, they don't directly support promises. You could either make getParsedData() be callback-based so you pass in a callback and call that callback from the end event when the stream is done or you can wrap it in a promise (my preference) and you can manually resolve or reject the promise when the stream finishes.
Here's how you can make getParsedData() return a promise that resolves/rejects when the stream is done:
function getParsedData() {
return new Promise((resolve, reject) => {
const results = getPolygons();
const file = '../server/datafiles/NIBRSPublicViewJan-Mar22.csv' // hard coded file & file directory
fs.createReadStream(file)
.pipe(csv({ mapValues: ({ value }) => parseFloat(value) }))
.on('data', (data) => {
if (data.lat && data.lng && data.mag) { // check if a latitude and longitude exist
let point = ({ lng: parseFloat(data.lat), lat: parseFloat(data.lng), mag: parseFloat(data.mag) })
results.forEach(polygon => {
const TopLeft = polygon.square[0]
const BottomRight = polygon.square[2]
//Something is wrong with this statement
if (point.lat < TopLeft.lat && point.lng > TopLeft.lng) {
if (point.lat > BottomRight.lat && point.lng < BottomRight.lng) {
//console.log("Top Left:" + TopLeft.lat + ", " + TopLeft.lng, "Point: "+ point.lat + ", " + point.lng)
//console.log("Bottom Right:" + BottomRight.lat + ", " + BottomRight.lng, "Point: "+ point.lat + ", " + point.lng)
//console.log("point Magnitude: ", point.mag, "Polygon mag: ", polygon.mag)
polygon.mag += point.mag
}
}
})
}
}).on('end', () => {
console.log("What should go to Front-end", results); // check to see if the results are correct
resolve(results);
}).on('error', reject);
});
}
router.get('/', async (req, res) => {
getParsedData().then(results => {
console.log("To Front-end", results)
res.json(results)
}).catch(err => {
console.log(err);
res.sendStatus(500);
});
});
Changes to note:
- We've added a promise inside of
getParsedData()that lets us resolve or reject that promise based on the stream events. - Inside of the
endevent handler, we resolve the promise and resolved it with the desired results. - Inside of the
errorevent handler, we reject the promise. - There is no point in using
awaitas inawait getPolygons()becausegetPolygons()is purely synchronous. - There's no reason for getParsedData()
to beasyncas we're just returning a manually constructed promise and not usingawait` in it. - A number of local variables can be
constinstead ofletas they are not modified. - An
errorhandler is added on the stream to detect errors. - In your route handler, you need to also have a
.catch()handler so your route sends a response if there's an error.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | jfriend00 |
