Attempting to retrieve JSON data results in [ Promise { <pending> }, Promise { <pending> } ] - javascript

I am attempting to retrieve the JSON data stored in the following API:
https://api.hatchways.io/assessment/blog/posts
Using node.js and https requests, I constantly receive an array of [ Promise { }, Promise { } ]. Unfortunately, I can only search by one tag at a time, and I have to retrieve a list of posts that has at least one tag from a list of provided tags, before sorting the posts. My code is below:
const express = require('express');
const app = express();
app.get("/api/ping", (req, res) => {
res.status(200).send("{\"success\": true}");
})
app.get("/api/posts", (req, res) => {
const tags = req.query.tags;
const sortBy = req.query.sortBy;
const direction = req.query.direction;
if (!tags) res.status(400).send("Must provide at least one tag.");
let tag_array = tags.split(',');
let posts_array = [];
tag_array.forEach(tag => {
let posts = getPost(tag);
posts_array.push(posts);
})
console.log(posts_array);
})
app.listen(3000, () => console.log("Listening on port 3000..."));
function getPost(tag) {
const https = require('https');
return new Promise( (resolve, reject) => {
const options = {
hostname: 'api.hatchways.io',
path: `/assessment/blog/posts?tag=${tag}`
}
let body = [];
const req = https.request(options, res => {
res.on('data', data => {
body.push(data);
});
res.on('end', () => {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch (error) {
reject(error);
}
resolve(body);
});
});
req.on('error', error => {
reject(error);
});
req.end();
}).then(function(data) { return data; }, function(error) { console.log(error) });
}

the getPost method is returning a promise, just do this:
app.get("/api/posts", async (req, res) => {
const tags = req.query.tags;
const sortBy = req.query.sortBy;
const direction = req.query.direction;
if (!tags) res.status(400).send("Must provide at least one tag.");
let tag_array = tags.split(',');
const promises = [];
tag_array.forEach(tag => {
promises.push(getPost(tag))
});
posts_array = await Promise.all(promises)
console.log(posts_array)
})

Just wait for all promises to resolve using the await keyword.
app.get("/api/posts", async (req, res) => { // Add the async keyword
//... Skipped some code for concision
tag_array.forEach(tag => {
let posts = getPost(tag);
posts_array.push(posts);
})
await Promise.all(posts_array) // Add this to await all promises to resolve
console.log(posts_array);
})

Related

How to mock https.get with Jest

I have a function that calls https.get inside a promise which I want to test with Jest.
The function is like this:
const request = (url) => {
return new Promise((resolve, reject) => {
const chunks = [];
https.get(url, (stream) => {
stream
.on('data', (chunk) => {
if( chunk ) {
chunks.push(JSON.parse(chunk));
}
})
.on('error', (err) => {
reject(err);
})
.on('end', () => {
const data = doSomething(chunks);
resolve(data)
});
});
})
}
I want to test that when the function resolves on "end" and rejects on "error";
Currently I have a test like this but because .on("end") doesn't get called, the promise never resolves.
describe("request", () => {
it("Should resolve", async (done) => {
const response = await request("my-url");
expect(response).toEqual("some-data")
})
})
How can I mock events like .on("end") to be called and ensure the promise resolves?
You can do something like this.
// ./request.test.js
jest.mock('https', () => ({
methodToMock: {}
}));
const Stream = require('stream');
const request = require("./request");
const httpsMock = require("https");
describe("request", () => {
it("Should resolve", async () => {
var streamStream = new Stream()
httpsMock.get = jest.fn().mockImplementation((url, cb) => {
cb(streamStream)
streamStream.emit('data', 'some');
streamStream.emit('data', '-');
streamStream.emit('data', 'data');
streamStream.emit('end'); // this will trigger the promise resolve
})
const response = await request("my-url");
expect(response).toEqual("some-data");
})
})
const https = require("https");
const request = (url) => {
return new Promise((resolve, reject) => {
const chunks = [];
https.get(url, (stream) => {
stream
.on('data', (chunk) => {
if (chunk) {
// chunks.push(JSON.parse(chunk));
chunks.push(chunk);
}
})
.on('error', (err) => {
reject(err);
})
.on('end', () => {
// const data = doSomething(chunks);
const data = chunks.join('');
resolve(data)
});
});
})
}
module.exports = request;
Note that jest.mock('https', ...) need to be called before const request = require("./request"); if you want https to be mocked.

Passing multiple query objects with res.render

I want to pass multiple query objects with res.render() inside my route.js. I have a select.js which contains the SQL statements and delivers the objects to my route.js. This works fine until I want to pass multiple query objects with res.render().
Any ideas on how I can pass multiple objects at once?
snippet route.js (I need to pass get_PriceData here as well)
I already query get_KategorieData but I have no clue how to handle multiple queries in one route.
router.get('/edit', (req, res, next) => {
var speisenEintragenData = {};
db.get_KategorieData()
.then(({ data: kategorie_data }) => {
res.render('speiseEintragen', { kategorie_data }); //maybe putting res.render() after the db.get?
})
.catch((error) => {
console.log(error);
});
});
select.js
const db = require('./config');
//KATEGORIEN LADEN
const get_KategorieData=()=>{
var sql = 'SELECT * FROM Kategorie';
return new Promise((resolve,reject) => {
db.query(sql, function (err, data, fields) {
if (err) reject(err);
resolve({data});
});
})
}
//PREISE LADEN
const get_PriceData=()=>{
var sql = 'SELECT * FROM preise';
return new Promise((resolve,reject) => {
db.query(sql, function (err, data, fields) {
if (err) reject(err);
resolve({data});
});
})
}
module.exports={
get_KategorieData,
get_PriceData
}
There are two ways to go about this. One is to stick with promises and other is to use async/await.
Using promise
Create a new function to query database. This is if the module you are using does not support async/await and requires a callback.
const query = ( sql ) => {
return new Promise(( resolve, reject) => {
db.query(sql, function (err, data, fields) {
if (err) reject(err);
resolve(data);
});
})
}
// and then you can write an async/await function to call n queries like
const get_data = async () => {
const sql1 = '...';
const a = await query( sql1 );
const sql2 = '...';
const b = await query( sql2 );
....
....
....
const sqln = '...';
const n = await query( sqln );
return { a ,b,... ,n};
}
Or with async/await you can directly call db.query and use the response
const get_data = async () => {
const sql1 = '...';
const res_1 = await db.query(sql1);
const sql2 = '...';
const res_2 = await db.query(sql2);
return { a: res_1 ,b: res_2 };
}
router.js can rewritten as
router.get('/edit', async (req, res, next) => {
const {a:rename_a,b:rename_b and so on}=await db.get_data();
res.render('view', { rename_a,rename_b and so on })
});

I want to show all my tables from my database to an API

I have 3 tables (services, practitioners, network) in my postgres database and I want them all to show in my API, but I got this error
(node:21300) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
and this is the only output I could get
json response
here is my code.
const handler = (req, res, db, tableName) => {
try{
db.select('*').from(tableName)
.then(data => {
if(data.length){
res.status(200).json({tableName: data});
}
})
}catch(err){
res.status(400).json(err)
}
}
const content = (req, res, db) => {
handler(req, res, db, 'services')
handler(req, res, db, 'practitioners')
}
module.exports = { content };
edit:
here's what I did from Nabil Farhan's answer, and it's working just what I wanted. screenCapture
const getData = async (db, tableName) => {
return new Promise((resolve, reject) => {
db.select('*').from(tableName)
.then(data => {
resolve({[tableName]:data})
})
});
}
const contentHandler = async (req, res, db) => {
// get the argument from get request
let allTable = [];
const table_a = await getData(db, 'services');
const table_b = await getData(db, 'practitioners');
allTable.push(table_a);
allTable.push(table_b);
res.status(200).json({data: allTable});
}
module.exports = { contentHandler };
I recommend you to use promise like this:
async function getData(tableName) {
return new Promise(function (resolve, reject) {
db.select("*")
.from(tableName)
.then((data) => {
resolve(data);
});
});
}
async function run(){
var allTable = [];
const firstTable = await getData('testingDB');
const secondTable = await getData('user');
allTable.push(firstTable);
allTable.push(secondTable);
res.status(200).json({data: allTable});
}
run().then(() => {
console.log("Finished!");
})
I see a couple potential issues here.
Unhandled Promise rejection:
Add a catch block after then (you can rethrow to put it in the existing catch block).
.then(data => {
if(data.length){
let responseData = {}
responseData[tableName] = data
res.status(200).json(responseData)
}
})
.catch(err) {throw err}
tableName is also not going to be interpreted as variable, but as a literal property name, so I changed it to get what I think you were going for.
For the header error, you are setting the response twice, once for "services", then again for "practitioners". One possible solution is to remove the res.status... line from handler and use Promise.all in content and moving the response setting there:
const content = (req, res, db) => {
Promise.all(
handler(req, res, db, 'services')
handler(req, res, db, 'practitioners')
)
.then(allData => {
//use reduce/Object.assign to combine the individual data objects
allData.reduce((finalResponseData, data) => {
if(!data.length) return finalResponseData
Object.assign(finalResponseData, data)
})
.then(finalResponseData => res.status(200).json(finalResponseData)
.catch(err) {res.status(400).json(err)}
}

Node.js - Mock result of a promise

I want to mock the result of a function within a node module so that i can run assertions.
Considering the following node module:
const doPostRequest = require('./doPostRequest.js').doPostRequest;
const normalizeSucessResult = require('./normalizer.js').normalizeSucessResult;
const normalizeErrorResult = require('./normalizer.js').normalizeErrorResult;
exports.doPost = (params, postData) => {
return doPostRequest(params, postData).then((res) => {
const normalizedSuccessResult = normalizeSucessResult(res);
return normalizedSuccessResult;
}).catch((err) => {
const normalizedErrorResult = normalizeErrorResult(err);
return normalizedErrorResult;
})
}
The function doPostRequest returns a promise. How can i fake the return value of this promise so that i can assert if normalizeSucessResult has been called?
So for i have tried:
const normalizeSucessResult = require('./normalizer.js');
const doPostRequest = require('./doPostRequests.js');
const doPost = require('./doPost.js');
it('runs a happy flow scenario', async () => {
let normalizeSucessResultStub = sinon.stub(normalizeSucessResult, 'normalizeSucessResult');
let postData = { body: 'Lorum ipsum' };
let params = { host: 'someUrl', port: 433, method: 'POST', path: '/' };
sinon.stub(doPostRequest, 'doPostRequest').resolves("some response data"); //Fake response from doPostRequest
return doPost.doPost(params, postData).then((res) => { //res should be equal to some response data
expect(normalizeSucessResultStub).to.have.been.calledOnce;
expect(normalizeSucessResultStub).to.have.been.with("some response data");
});
});
The doPostRequest module looks like this:
const https = require('https')
module.exports.doPostRequest = function (params, postData) {
return new Promise((resolve, reject) => {
const req = https.request(params, (res) => {
let body = []
res.on('data', (chunk) => {
body.push(chunk)
})
res.on('end', () => {
try {
body = JSON.parse(Buffer.concat(body).toString())
} catch (e) {
reject(e)
}
resolve(body)
})
})
req.on('error', (err) => {
reject(err)
})
if (postData) {
req.write(JSON.stringify(postData))
}
req.end()
})
}
You can use Promise.resolve to return a promise with any given value.
Promise.resolve(“hello world”);
For stub your func you need to do like this
sinon.stub({doPostRequest}, 'doPostRequest').resolves("some response data")
Okay, i figured it out. The function doPostRequest was loaded using require, on the top of the file using const doPostRequest = require('./doPostRequest.js').doPostRequest;
In order to mock the data that comes back from a function that is loaded using require i had to use a node module called mock-require. There are more modules that can take care of this (proxyquire is a populair one) but i picked mock-require (i did not have a specific reason for choosing mock-require).
For anyone else that is stuck with a similar problem, try mock-require to mock the respose from files that are loaded using require.

How to wait until base64 encoding is finished

I'm trying to put image file encoded into base64, put it in an object(responseData), and res.json(responseData).
The problem is that response is sent before base64 encoding is finished.
How can I delay sending response until encoding is finished.
P.S I'm not sure if sending an image in a json file is the right way.
router.get("/", async (req, res) => {
let responseData;
await Story.find().then(storiesData => {
responseData = [...storiesData];
storiesData.map((storyData, storyIndex) => {
responseData[storyIndex].imageFiles = [];
gfs.files
.find({ _id: { $in: storyData.image } })
.toArray((err, files) => {
files.map((file, imageIndex) => {
let data = [];
var readstream = gfs.createReadStream({
_id: file._id
});
readstream.on("data", chunk => {
data.push(chunk);
});
readstream.on("end", () => {
imageData = Buffer.concat(data);
responseData[storyIndex].imageFiles[imageIndex] = new Buffer(
imageData
).toString("base64");
});
});
});
});
return new Promise((resolve, reject) => {
resolve();
});
});
res.json(responseData);
});
I think something like this might work
router.get("/", async (req, res) => {
let responseData, storiesData;
storiesData = await Story.find();
responseData = [...storiesData];
const mapResults = storiesData.map((storyData, storyIndex) => {
return new Promise((resolve, reject) => {
responseData[storyIndex].imageFiles = [];
gfs.files
.find({_id: {$in: storyData.image}})
.toArray((err, files) => {
files.map((file, imageIndex) => {
let data = [];
var readstream = gfs.createReadStream({
_id: file._id
});
readstream.on("data", chunk => {
data.push(chunk);
});
readstream.on("end", () => {
imageData = Buffer.concat(data);
responseData[storyIndex].imageFiles[imageIndex] = new Buffer(
imageData
).toString("base64");
resolve(responseData[storyIndex])
});
});
});
})
})
const results = await Promise.all(mapResults);
res.json(results);
});
The map is returning an array of promises, so after each one is done, it sends the responseData, check if final result is like you wanted if not adjust to your needs but the overall idea is there.

Categories