Node.js Express not waiting for Async Await - javascript

I have a program that I'm trying to run, and when getting the cars, it needs to wait for the function to finish before running the result, but it doesn't seem to be working. I'm new to this, so I don't really know what I'm doing all that well.
app.get('/cars', async (req, res) => {
try {
const result = await getCars()
console.log('result: ' + result)
res.send(result)
} catch(error) {
console.log(error)
}
})
That's the code to call the "getCars" function which is below:
const getCars = async () => {
// TODO: Replace this with a call to the database
await fs.readFile(__dirname + '/cars.json', function (err, data) {
if (err) {
throw err
}
let cars = JSON.parse(data)
//Groups the cars to their location, to be sorted and chosen based on arrivalDate
const groupBy = (xs, f) => {
return xs.reduce(
(r, v, i, a, k = f(v)) => ((r[k] || (r[k] = [])).push(v), r),
{}
)
}
const result = groupBy(cars, (c) => c.locationName)
Object.keys(result).forEach((car) => {
console.log(car)
// filters out cars with no arrival dates
let filtered = result[car].filter(obj => Object.keys(obj).includes("arrivalDate"));
//Sort cars by the last update time
filtered.sort(function (a, b) {
let keyA = new Date(a.arrivalDate.toString().split(' ')[0]),
keyB = new Date(b.arrivalDate.toString().split(' ')[0])
// Compare the 2 dates
if (keyA < keyB) return -1
if (keyA > keyB) return 1
return 0
}).reverse()
//Add the top two (latest) of each car to the new array
emailCars = [...emailCars, { [car]: [ result[car][0], result[car][1] ]}]
})
console.log('returning' + emailCars)
return emailCars
})
}
What am I missing here to make sure emailCars is being set by the function and then sent to the user when they go to /cars

I believe that problem is with the very first line of the getCars() function...
await fs.readFile(__dirname + '/cars.json', function (err, data) {
}
You cannot await a function that returns results in a callback. Either use "sync" version of readFile and remove await/async from getCars(), or use promisfied version of readFile:
try {
const data = await fs.promises.readFile(filepath, 'utf8');
} catch (e) {
// handle errors herer
}

The only issue that I see is you are mixing async/await with callback notion together. Just replace all the callbacks with async/await and wrap them around try/catch.
const getCars = async () => {
// TODO: Replace this with a call to the database
try {
const data = await fs.readFile(__dirname + '/cars.json')
let cars = JSON.parse(data)
//Groups the cars to their location, to be sorted and chosen based on arrivalDate
const groupBy = (xs, f) => {
return xs.reduce(
(r, v, i, a, k = f(v)) => ((r[k] || (r[k] = [])).push(v), r),
{}
)
}
const result = groupBy(cars, (c) => c.locationName)
Object.keys(result).forEach((car) => {
console.log(car)
// filters out cars with no arrival dates
let filtered = result[car].filter(obj => Object.keys(obj).includes("arrivalDate"));
//Sort cars by the last update time
filtered.sort(function (a, b) {
let keyA = new Date(a.arrivalDate.toString().split(' ')[0]),
keyB = new Date(b.arrivalDate.toString().split(' ')[0])
// Compare the 2 dates
if (keyA < keyB) return -1
if (keyA > keyB) return 1
return 0
}).reverse()
//Add the top two (latest) of each car to the new array
emailCars = [...emailCars, { [car]: [ result[car][0], result[car][1] ]}]
})
console.log('returning' + emailCars)
return emailCars
} catch (e) {
console.log(e);
}
}
If you have any difficulty understanding this concept, please check this link.
if I do that, it returns all the sorted data, but I only need the
first 2 of each sub-array in the object (thats what the emailCars
does, gets just the first 2 objects in each array). Or should I do
that in the /get function instead?
Any utility function should be correctly organised in order to maintain the correct structure of your application. The routes should not contain so much logic and the logic should be placed under controller or anything like that.
You need to devise your own logic and if you face any issues, you are welcome to ask on this platform.

You should first understand sync/asynchronous concept. Here is my solution.
import express from 'express'
import { readFile } from 'fs'
import { dirname } from 'path'
import { fileURLToPath } from 'url'
import { promisify } from 'util'
const app = express()
const readFileAsync = promisify(readFile)
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const getCars = async () => {
const file = await readFileAsync(`${__dirname}/cars.json`, 'utf8')
const cars = JSON.parse(file)
// DO YOUR LOGIC HERE
return cars
}
app.get('/', async (req, res) => {
try {
const cars = await getCars()
res.json(cars)
} catch (error) {
console.log(error)
}
})
app.listen(7777, () => {
console.log('Example app listening on port 7777!')
})

Related

I am facing problem chaining the async code in Javascript

I am trying to execute allCountryData and return a promise its working fine but after allCountryData is done executing I want to perform a operation on that returned data / or allCountryDataArray and store the highest values in arrayOfHighestCases
Note I can't chain the other login in allCountryData.
Please help let me know if you need any more details
export const allCountryDataArray = [];
export const arrayOfHighestCases = [];
const allCountryData = async () => {
sendHTTP()
.then((res) => {
return res.response;
})
.then((res) => {
allCountryDataArray.push(...res);
return allCountryDataArray;
});
return await allCountryDataArray;
// Highest Cases
};
The code is below is not working
const highestCasesData = async () => {
// const allCountryDataArrayy = await allCountryData();
// allCountryData()
// .then((data) => {
// console.log(arrayOfHighestCases[0]);
// })
// .then((res) => {
const np = new Promise((res, rej) => {
res(allCountryData());
});
return np.then((res) => {
console.log(res);
const arrayofHigh = allCountryDataArray.sort((a, b) => {
if (a.cases.total < b.cases.total) {
return 1;
} else if (a.cases.total > b.cases.total) {
return -1;
} else {
return 0;
}
});
console.log(arrayofHigh);
const slicedArray = arrayofHigh.slice(0, 6);
for (const eachHighCase of slicedArray) {
arrayOfHighestCases.push(eachHighCase);
}
console.log(arrayOfHighestCases);
return arrayOfHighestCases;
});
// });
};
highestCasesData();
Filling global arrays with async data is a way into timing conflicts. Bugs where the data ain't there, except when you look it is there and yet another question here on my SO about "Why can't my code access data? When I check in the console everything looks fine, but my code ain't working."
If you want to store something, store Promises of these arrays or memoize the functions.
const allCountryData = async () => {
const res = await sendHTTP();
return res.response;
};
const highestCasesData = async () => {
const allCountryDataArray = await allCountryData();
return allCountryDataArray
.slice() // make a copy, don't mutate the original array
.sort((a, b) => b.cases.total - a.cases.total) // sort it by total cases DESC
.slice(0, 6); // take the first 6 items with the highest total cases
}
This is working please let me know if I can make some more improvements
const allCountryData = async () => {
return sendHTTP()
.then((res) => {
return res.response;
})
.then((res) => {
allCountryDataArray.push(...res);
return allCountryDataArray;
});
// Highest Cases
};
const highestCasesData = async () => {
return allCountryData().then((res) => {
console.log(res);
const arrayofHigh = allCountryDataArray.sort((a, b) => {
if (a.cases.total < b.cases.total) {
return 1;
} else if (a.cases.total > b.cases.total) {
return -1;
} else {
return 0;
}
});
console.log(arrayofHigh);
const slicedArray = arrayofHigh.slice(0, 6);
for (const eachHighCase of slicedArray) {
arrayOfHighestCases.push(eachHighCase);
}
console.log(arrayOfHighestCases);
return arrayOfHighestCases;
});
};
highestCasesData();

How to send res.json() in Express JS from a for loop

I am trying to send a json response when an API call is triggered from front-end, I'm not able to send the res.json() when I am getting the data from a for loop. Where I am writing a query to search in multiple Tables. I am using RethinkDB.
I want res.json() to send data after the query, But I don't understand what mistake I am doing. :(
Thanks in advence
zeasts
Here is the following code and Fiddle Link too.
const express = require("express");
const router = express.Router();
const moment = require('moment');
const r = require('rethinkdb');
const tableNameDB = ['assets', 'alerts', 'destinations']
router.post('/', (req, res, next) => {
let resData = []
let searchValue = req.body.searchValue,
tableName = req.body.tableName;
newCallForSearch(res, searchValue, resData)
})
function newCallForSearch (res, searchValue, resData){
let anArray = ['captain']
for(var i = 0; i<tableNameDB.length; i++){
let tabName = tableNameDB[i]
r.table(tableNameDB[i]).filter(function(doc) {
return doc.coerceTo('string').match(searchValue);
}).run(rethink_conn, (err, cur) => {
// console.log(cur)
if (err) {
return 0
} else {
cur.toArray((err, result) => {
if (err) {
return 0
} else if (result) {
let Results = []
Results = Object.values(result).slice(0,10)
var newResults = Results.map(function() {
resData = Object.assign({'tableName': tabName},{'data' : result});
anArray.push(resData)
})
}
})
}
})
}
res.status(200);
res.json(anArray);
}
module.exports = router;
RethinkDb is a functional database and so using it in a functional way will yield the least resistance. We can accomplish more by writing less code.
You can use Promise.all to run many subqueries and pass the result to res.send -
const search = (table = "", query = "", limit = 10) =>
r.table(table)
.filter(doc => doc.coerceTo("string").match(query))
.toArray()
.slice(0, limit)
.do(data => ({ table, data }))
const tables =
['assets', 'alerts', 'destinations']
const subqueries =
tables.map(t => search(t, "foo").run(rethink_conn)) // <-- search each table
Promise.all(subqueries) // <-- run all subqueries
.then(result => { // <-- handle success
res.status(200)
res.json(result)
})
.catch(e => { // <-- handle failure
res.status(500)
res.send(e.message)
})
Or use .union to produce a single query -
const search = (table = "", query = "", limit = 10) =>
r.table(table)
.filter(doc => doc.coerceTo("string").match(query))
.toArray()
.slice(0, limit)
.do(data => ({ table, data }))
const searchAll = (tables = [], query = "", limit = 10) =>
tables.reduce
( (r, t) => r.union(search(t, query, limit)) // <-- union
, r.expr([]) // <-- if no tables are supplied, return empty result
)
const tables =
['assets', 'alerts', 'destinations']
searchAll(tables, "foo") // <-- single rethink expr
.run(rethink_conn) // <-- run returns a promise
.then(result => { // <-- handle success
res.status(200)
res.json(result)
})
.catch(e => { // <-- handle failure
res.status(500)
res.send(e.message)
})
I should remark on the proposed use of filter in your original post -
.filter(doc => doc.coerceTo("string").match(query))
This is quick but it is also sloppy. It matches query against any of docs values, but also the doc's keys. And if doc is a complex nested document, it matches them too. User beware.

Multiple if's refactoring

I have this function, with two ifs where I want to find the user depending on which alphanumeric code I receive. How can I refactor this one with sanctuary-js?
//const code = '0011223344';
const code = 'aabbc';
const isNumberCode = code => !!/^[0-9]{10}$/.exec(code);
const isLiteralCode = code => !!/^[A-Za-z]{5}$/.exec(code);
const findUser = (criteria) => {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('user object');
}, 300);
});
}
async function handler(code) {
if (isNumberCode(code)) {
const user = await findUser({id: code});
return user;
}
if (isLiteralCode(code)) {
const user = await findUser({identifier: code});
return user;
}
return 'not found';
}
async function run() {
const user = await handler(code);
console.log(user)
}
run();
I can't understand how I should handle three different types: number code, literal code and not found code.
-- UPDATE
Here my functional solution (I may think so):
const Code = x => ({
chain: f => f(x),
fold: (e, a, f) => e(x)
});
const ErrorCode = x => ({
fold: (e, a, f) => e(x),
findLabel: f => ErrorCode(x)
});
const PromiseToCode = promise => ({
fold: (e, a, f) => promise.then(x => x.fold(e, a, f))
});
const NumberCode = x => ({
fold: (e, a, f) => a(x),
findLabel: f => PromiseToCode(f(x, {activationCode: x}, NumberCode))
});
const LiteralCode = x => ({
fold: (e, a, f) => f(x),
findLabel: f => PromiseToCode(f(x, {finderCode: x}, LiteralCode))
});
const checkTypeOfCode = code => {
if (isNumberCode(code)) {
return NumberCode(code);
}
if (isLiteralCode(code)) {
return LiteralCode(code);
}
return ErrorCode(code);
};
const find = async (code, criteria, type) => {
const user = findUser();
if (!user) {
return ErrorCode(code);
}
return type(user);
};
const handler2 = (code) =>
Code(code)
.chain(checkTypeOfCode)
.findLabel(find)
.fold(
e => 'not found',
a => 'user object find by id',
l => 'user object find by identifier'
)
handler2(code).then(console.log);
But I don't know if it's good code. Also I'm asking about sanctuary-js because I think that all this object not good way to programming.
Since you are looking for a more functional restructuring, you can try this:
Divide your code into smaller, more independent sections:
findUser: This function is responsible to give either UserObject or Not found.
Create a function getCriteria, that will have all the logic as to isNumberCode or isLiteralCode etc. This will return a criteria object or undefined.
handler should be responsible to get criteria, and based on that return findUser's response. Any cleanup code can be kept here but this is a hub function which calls various functions and return an output. It should have bare minimum business logic.
//const code = '0011223344';
const code = 'aabbc';
const isNumberCode = code => !!/^[0-9]{10}$/.exec(code);
const isLiteralCode = code => !!/^[A-Za-z]{5}$/.exec(code);
const findUser = (criteria) => {
return new Promise(function(resolve, reject) {
if (!criteria) resolve('not found')
setTimeout(function() {
resolve('user object');
}, 300);
});
}
function getCriteria(code) {
if (isNumberCode(code)) {
return { id: code };
}
if (isLiteralCode(code)) {
return { identifier: code }
}
}
async function handler(code) {
const user = await findUser(getCriteria(code))
return user;
}
async function run() {
const user = await handler(code);
console.log(user)
}
run();
You can create enum for multiple type of input and use switch statement as below.
// Enum for search-parameters
var ParameterTypes =
{
NUMBER :1 ,
LITERAL:2 ,
OTHER : 3
}
function getParameterType()
{
//responsible to get search-parameter
return isNumberCode(code) ? ParameterTypes.NUMBER :
( isLiteralCode(code) ? ParameterTypes.LITERAL : ParameterTypes.OTHER);
}
async function handler(code)
{
//responsible to search user
var user;
switch(getParameterType())
{
case ParameterTypes.NUMBER :
user = await findUser({id: code});
//console.log('number');
break;
case ParameterTypes.LITERAL :
user = await findUser({identifier: code});
//console.log('literal');
break;
case ParameterTypes.OTHER :
user = 'not found';
//console.log('other');
break;
}
return user;
}

Promise all with inner function to be executed

I want to retrieve different HTML body at once and as soon as all results are available work with that content.
My callback solution which works looks like this (probably only relevant to read if the idea is not clear, otherwise skip ;)):
const request = require('request')
const argLength = process.argv.length
const result_array = []
let counter = 0
function executeRequest () {
for (start = 2; start <= argLength - 1; start++) {
const copy = start
function callback (error, res, body) {
const startCopy = copy
if (error) {
console.log('error')
return
}
result_array[startCopy - 2] = body.toString().length
counter++
if (counter === argLength - 2) {
console.log(result_array)
}
}
request(process.argv[start], callback)
}
}
executeRequest()
Now I try to make it running with Promises like this:
const httpRequest = require('request')
const argumentLength = process.argv.length
function fillURLArray () {
resultArray = []
for (start = 2; start < argumentLength; start++) {
resultArray[start - 2] = process.argv[start]
}
return resultArray
}
const urls = fillURLArray()
let counter = 0
function readHttp () {
const resultArray = []
Promise.all(urls.map(url => httpRequest(url, (error, res, body) => {
console.log(body.toString())
resultArray[counter++] = body.toString()
}))).then(value => {
console.log('promise counter: ' + counter++)
console.log(resultArray)
console.log('called')
})
}
readHttp()
I tried already several attempts with different promise chains but every time I get either not a result or just an empty array. So obviously the .then() function is called before the array is actually filled (at least I guess so since console.log(body.toString()) appears to print the content some time later)
Any idea how to solve this with promises?
Thank you
request is not returning promise object so have created a method that return promise object on which you do Promise.all.
function requestPromise(url){
return new Promise((resovle,reject) => {
httpRequest(url, (error, res, body) => {
if(err){
reject(err);
}
resolve(body.toString());
});
});
}
function readHttp () {
const resultArray = []
Promise.all(urls.map(url => requestPromise(url))).then(values => {
console.log("counter => ",values.length);
resultArray = resultArray.concat(values);
console.log("values=> ",values);
console.log("resultArray=> ",resultArray);
});
}
httpRequest does not return a promise so you have to make one yourself, also your resultArray is not necessary:
const makeRequest = url => new Promise((resolve, reject) => httpRequest(url, (error, res) => error ? reject(error) : resolve(res)));
Promise.all(urls.map(makeRequest))
.then(result => {
console.log(result.map(res => res.body.toString()));
console.log('called');
});

Multiple queries end with unpredictably sorted result

There are few hundred documents in my database. Schema is very simple:
var firmsSchema = mongoose.Schema({
name: String,
sections: [String],
});
I want to query documents and iterate over:
{{#each sections}}
{{sectionName}}
{{#each firms}}
{{firmName}}
{{/each}}
{{/each}}
Simple:
const SECTIONS = ['name_one', 'name_two', 'name_three'];
const UNSORTED_SECTION_NAME = 'unsorted';
router.get('/', function(req, res, next) {
var showFirms = function showSection (i, acc) {
if (i < 0) return;
let query = SECTIONS[i] ? {sections: SECTIONS[i]} : {sections: {$nin: SECTIONS}};
let key = SECTIONS[i] || UNSORTED_SECTION_NAME;
Firms.find(query).
then((result) => {
acc.push({
section: key,
firms: result,
});
if (i === SECTIONS.length) {
acc = acc.sort((a, b) => (a.section > b.section));
res.render('template', {
sections: acc,
});
}
}).
then(showSection (i - 1, acc));
}
showFirms(SECTIONS.length, []);
};
Works fine. Except it returns acc randomly and unpredictably sorted. I mean 'name_two' section can follow 'name_one' or vise versa.
I thought .sort() at the end of promises chain would be a silver bullet here and solve all asynchronous problems, but it didn't.
Of course i can sort acc with handlebars helper after i pass it to my template, but it is so ridiculously strange i can't sort it right after all queries have been done in my showFirms function.
Can you give me some advise please?
Look at this remake of your code. Instead of getting the data one by one, we gotta get them on the same time (asynchronously) and then treat the return.
If you have any questions I am here, this code is untested so give me a feedback. This is an example of how you can change your code.
const showFirms = function showSection() {
return new Promise((resolve, reject) => {
// Get the keys for the queries
const keys = SECTIONS.map(x => x || UNSORTED_SECTION_NAME);
// For each sections we gonna call a find request
const promises = SECTIONS.map((x, xi) => {
const query = x ? {
sections: x,
} : {
sections: {
$nin: SECTIONS,
},
};
const key = keys[xi];
return Firms.find(query);
});
// Resolve all promises
Promise.all(promises)
.then((rets) => {
// Use the finds results to build an acc array
const accs = rets.map((x, xi) => ({
section: keys[xi],
firms: x,
}));
// Change the sort -> ;) #comments
const sortedAccs = accs.sort((a, b) => (a.section > b.section));
resolve(sortedAccs);
})
.catch(reject);
});
};
How to use it
showFirms()
.then(accs => res.render('template', {
sections: accs,
}))
.catch(err => console.log(`I have an error ${err.toString()}`));
Based on Grégory NEUTS excellent solution. I simplified some things and made the "other sections case" work. I even dropped out sort functionality. Eventual result returns in order of sections as they were declared in the initial SECTIONS array, so now i can just reorder it to control output.
const showFirms = function () {
return new Promise ((resolve, reject) => {
const extendedSections = SECTIONS.slice();
extendedSections.push(UNSORTED_SECTION_NAME);
const promises = extendedSections.map((section) => {
const unsortedCase = section === UNSORTED_SECTION_NAME;
const query = unsortedCase ? {sections: {$nin: SECTIONS}} : {sections: section};
return Firms.find(query);
})
Promise.all(promises)
.then((allResponces) => {
const sectionsData = allResponces.map((response, i) => ({
section: extendedSections[i],
firms: response,
}));
resolve(sectionsData);
})
.catch(reject);
});
};

Categories