Why I am getting undefined after calling fetchNotes - javascript

After calling fetchNotes from the addNote function it shows me undefined as push method is not defined in the addNote function
const fs = require('fs');
const fetchNotes = ()=>{
fs.readFile('data.json',(err,notes)=>{
if(err){
// return empty array if data.json not found
return [];
}else{
// return Object from data found data.json file
return JSON.parse(notes)
}
});
}
const saveNotes = (notes) =>{
fs.writeFile('data.json',JSON.stringify(notes),()=>{
console.log('Notes is successfully saved');
});
}
const addNote = (title, body)=>{
const note = {
title,
body
}
const notes = fetchNotes();
//Push method not defined
notes.push(note);
saveNotes(notes);
return note;
}
module.exports.addNote = addNote;

It returns undefined because when you are returning in the callback you are not exactly returning from the fetchNotes function itself.
Maybe you can use the readFileSync and don't use callback or maybe you can make it a promise and use async/await
const fetchNotes = () => {
return new Promise((res, rej) => {
fs.readFile('data.json', (err, notes) => {
if (err) {
// return empty array if data.json not found
res([]);
} else {
// return Object from data found data.json file
res(JSON.parse(notes));
}
});
});
}
const addNote = async (title, body) => {
const note = {
title,
body
}
const notes = await fetchNotes();
//Push method not defined
notes.push(note);
saveNotes(notes);
return note;
}
Alternatively, you can use utils.promisify

return JSON.parse(notes) does not store this value inside fetchNotes because it is asynchronous, so you get the content of the file later in time.
To do it asynchronously, you can use async/await :
const fetchNotes = () => {
return new Promise((resolve, reject) => {
fs.readFile('data.json', (err,notes) => resolve(JSON.parse(notes)));
})
}
const addNote = async (title, body) => {
// ...
const notes = await fetchNotes();
notes.push(note);
saveNotes(notes);
return note;
}
You can also do it synchronously :
const fetchNotes = () => JSON.parse( fs.readFileSync('data.json') );
const notes = fetchNotes();

Related

chained await in class chained methods

context: Two javascript classes in separate files, each integrating a different external service and being called in a express.js router.
See "problematic code" below:
route
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const data = await ( await ( await awsTranscribe.Upload(req.file, bucket)).CreateJob(transcribeParams)).GetJob()
res.send(data)
})
S3 class
class AmazonS3 {
constructor() {
this.Upload = this.Upload
}
async Upload(file, bucket) {
const uploadParams = {
Bucket: bucket,
Body: fs.createReadStream(file.path),
Key: file.filename,
}
this.data = await s3.upload(uploadParams).promise()
return this
}
}
Transcribe class
class Transcribe extends AwsS3 {
constructor() {
super()
this.CreateJob = this.CreateJob
this.GetJob = this.GetJob
}
async CreateJob(params) {
if(this.data?.Location) {
params.Media = { ...params.Media, MediaFileUri: this.data.Location }
}
this.data = await transcribeService.startTranscriptionJob(params).promise()
return this
}
async GetJob(jobName) {
if(this.data?.TranscriptionJob?.TranscriptionJobName) {
jobName = this.data.TranscriptionJob.TranscriptionJobName
}
this.data = await transcribeService.getTranscriptionJob({TranscriptionJobName: jobName}).promise()
return this
}
}
problem: the problem is with the chained awaits in the router file:
await ( await ( await awsTranscribe.Upload...
Yes, it does work, but it would be horrible for another person to maintain this code in the future.
How can i make so it would be just
awsTranscribe.Upload(req.file, bucket).CreateJob(transcribeParams).GetJob() without the .then?
The problem is with the chained awaits in the router file: await ( await ( await awsTranscribe.Upload...
No, that's fine. In particular it would be trivial to refactor it to separate lines:
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const a = await awsTranscribe.Upload(req.file, bucket);
const b = await b.CreateJob(transcribeParams);
const c = await b.GetJob();
res.send(c);
});
Your actual problem is that a, b, and c all refer to the same object awsTranscribe. Your code would also "work" if it was written
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
await awsTranscribe.Upload(req.file, bucket);
await awsTranscribe.CreateJob(transcribeParams);
await awsTranscribe.GetJob();
res.send(awsTranscribe);
});
The horrible thing is that you are passing your data between these methods through the mutable awsTranscribe.data property - even storing different kinds of data in it at different times! One could change the order of method calls and it would completely break in non-obvious and hard-to-debug ways.
Also it seems that multiple requests share the same awsTranscribe instance. This will not work with concurrent requests. Anything is possible from just "not working" to responding with the job data from a different user (request)! You absolutely need to fix that, then look at ugly syntax later.
What you really should do is get rid of the classes. There's no reason to use stateful objects here, this is plain procedural code. Write simple functions, taking parameters and returning values:
export async function uploadFile(file, bucket) {
const uploadParams = {
Bucket: bucket,
Body: fs.createReadStream(file.path),
Key: file.filename,
};
const data = s3.upload(uploadParams).promise();
return data.Location;
}
export async function createTranscriptionJob(location, params) {
params = {
...params,
Media: {
...params.Media,
MediaFileUri: location,
},
};
const data = await transcribeService.startTranscriptionJob(params).promise();
return data.TranscriptionJob;
}
async function getTranscriptionJob(job) {
const jobName = job.TranscriptionJobName;
return transcribeService.getTranscriptionJob({TranscriptionJobName: jobName}).promise();
}
Then you can import and call them as
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const location = await uploadFile(req.file, bucket);
const job = await createTranscriptionJob(location, transcribeParams);
const data = await getTranscriptionJob(job);
res.send(c);
});
I got interested in whether it was possible to take an object with several async methods and somehow make them automatically chainable. Well, you can:
function chain(obj, methodsArray) {
if (!methodsArray || !methodsArray.length) {
throw new Error("methodsArray argument must be array of chainable method names");
}
const methods = new Set(methodsArray);
let lastPromise = Promise.resolve();
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
if (prop === "_promise") {
return function() {
return lastPromise;
}
}
const val = Reflect.get(target, prop, receiver);
if (typeof val !== "function" || !methods.has(prop)) {
// no chaining if it's not a function
// or it's not listed as a chainable method
return val;
} else {
// return a stub function
return function(...args) {
// chain a function call
lastPromise = lastPromise.then(() => {
return val.apply(obj, args);
//return Reflect.apply(val, obj, ...args);
});
return proxy;
}
}
}
});
return proxy;
}
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
const delta = Date.now() - log.start;
const deltaPad = (delta + "").padStart(6, "0");
console.log(`${deltaPad}: `, ...args)
}
class Transcribe {
constructor() {
this.greeting = "Hello";
}
async createJob(params) {
log(`createJob: ${this.greeting}`);
return delay(200);
}
async getJob(jobName) {
log(`getJob: ${this.greeting}`);
return delay(100);
}
}
const t = new Transcribe();
const obj = chain(t, ["getJob", "createJob"]);
log("begin");
obj.createJob().getJob()._promise().then(() => {
log("end");
});
There's a placeholder for your Transcribe class that has two asynchronous methods that return a promise.
Then, there's a chain() function that returns a proxy to an object that makes a set of passed in method names be chainable which allows you to then do something like this:
const t = new Transcribe();
// make chainable proxy
const obj = chain(t, ["getJob", "createJob"]);
obj.createJob().getJob()
or
await obj.createJob().getJob()._promise()
I wouldn't necessarily say this is production-ready code, but it is an interesting feasibility demonstration and (for me) a chance to learn more about a Javascript proxy object.
Here's a different approach that (instead of the proxy object) adds method stubs to a promise to make things chainable:
function chain(orig, methodsArray) {
let masterP = Promise.resolve();
function addMethods(dest) {
for (const m of methodsArray) {
dest[m] = function(...args) {
// chain onto master promise to force sequencing
masterP = masterP.then(result => {
return orig[m].apply(orig, ...args);
});
// add methods to the latest promise befor returning it
addMethods(masterP);
return masterP;
}
}
}
// add method to our returned promise
addMethods(masterP);
return masterP;
}
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
const delta = Date.now() - log.start;
const deltaPad = (delta + "").padStart(6, "0");
console.log(`${deltaPad}: `, ...args)
}
class Transcribe {
constructor() {
this.greeting = "Hello";
this.cntr = 0;
}
async createJob(params) {
log(`createJob: ${this.greeting}`);
++this.cntr;
return delay(200);
}
async getJob(jobName) {
log(`getJob: ${this.greeting}`);
++this.cntr;
return delay(100);
}
}
const t = new Transcribe();
log("begin");
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
log("end");
});
Since this returns an actual promise (with additional methods attached), you can directly use .then() or await with it without the separate ._promise() that the first implementation required.
So, you can now do something like this:
const t = new Transcribe();
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
});
or:
const t = new Transcribe();
await chain(t, ["getJob", "createJob"]).createJob().getJob();
log(`cntr = ${t.cntr}`);
And, here's a third version where it creates a thenable object (a pseudo-promise) with the added methods on it (if it bothers you to add methods to an existing promise):
function chain(orig, methodsArray) {
if (!methodsArray || !methodsArray.length) {
throw new Error("methodsArray argument must be array of chainable method names");
}
let masterP = Promise.resolve();
function makeThenable() {
let obj = {};
for (const m of methodsArray) {
obj[m] = function(...args) {
// chain onto master promise to force sequencing
masterP = masterP.then(result => {
return orig[m].apply(orig, ...args);
});
return makeThenable();
}
}
obj.then = function(onFulfill, onReject) {
return masterP.then(onFulfill, onReject);
}
obj.catch = function(onReject) {
return masterP.catch(onReject);
}
obj.finally = function(onFinally) {
return masterP.finally(onFinally);
}
return obj;
}
return makeThenable();
}
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
const delta = Date.now() - log.start;
const deltaPad = (delta + "").padStart(6, "0");
console.log(`${deltaPad}: `, ...args)
}
class Transcribe {
constructor() {
this.greeting = "Hello";
this.cntr = 0;
}
async createJob(params) {
log(`createJob: ${this.greeting}`);
++this.cntr;
return delay(200);
}
async getJob(jobName) {
log(`getJob: ${this.greeting}`);
++this.cntr;
return delay(100);
}
}
const t = new Transcribe();
log("begin");
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
log("end");
});

Chaining Functions Does not Work Synchronously

I query the database as follows:
const testMe = async () => {
const index = await _fetchNextIndex()
if (index === undefined) {
throw new Error('Next index could not fetched')
}
return 'Success'
}
const _fetchNextIndex = async () => {
return db.query("SELECT nextval(pg_get_serial_sequence('testme_table', 'id'))", (err, result) => {
if (err) {
throw new Error('Index could not generated')
}
return result.rows[0]
})
}
db is defined as:
const {Pool} = require('pg')
const pool = new Pool()
module.exports = {
query: async (text, params, callback) => {
return await pool.query(text, params, callback)
}
}
My code doesn't w work synchronously. index is undefined and error is thrown. However, when I debug the application I see that this line is called after a time later:
return result.rows[0]
What I miss?
EDIT 1: I've added async and await into query function.
EDIT 2: I've also removed the callback.
const {Pool} = require('pg')
const pool = new Pool()
module.exports = {
query: async (text, params) => {
return await pool.query(text, params)
}
}

Problem accessing object property created using Promise

I am not able to access the returned object property, Please tell me why its returning undefined when data is object and giving correct value.
This is function created to sendHTTPRequest based on data.
import { countryCap } from "./capitalizingFunc.js";
export const sendHTTPRequest = (country) => {
const capitalisedCountry = countryCap(country);
return fetch(
`https://covid-19-coronavirus-statistics.p.rapidapi.com/v1/total?country=${capitalisedCountry}`,
{
method: "GET",
headers: {
"x-rapidapi-key": "3b0f2e00ebmsh95246403d9540c9p1506d4jsn3c44ce26f745",
"x-rapidapi-host": "covid-19-coronavirus-statistics.p.rapidapi.com",
},
}
)
.then((response) => {
const newResponce = response.json();
return newResponce;
})
.catch((err) => {
console.error(err);
});
};
This is constructor class
export class casesDataFetcher {
constructor(countryName) {
sendHTTPRequest(countryName)
.then((response) => {
return response.data;
})
.then((data) => {
this.country = data.location;
this.cases = data.confirmed;
this.recovered = data.recovered;
this.deaths = data.deaths;
console.log(this);
return this;
});
}
}
This is execution function
import { casesDataFetcher } from "./casesDataFetcher.js";
export const screenDataShower = (country) => {
const dataStorage = [];
const globalInfected = document.querySelector(".infected h2");
const globalActive = document.querySelector(".active h2");
const globalDeaths = document.querySelector(".deaths h2");
const globalRecovered = document.querySelector(".recovered h2");
const globalCountries = document.querySelector(".countries h2");
let promise = new Promise(function (resolve, reject) {
const recordedData = new casesDataFetcher(country);
console.log(recordedData);
resolve(recordedData);
});
return promise.then((data) => {
console.log(typeof data);
console.log(typeof data);
console.log(data.cases); // NOT WORKING GIVING UNDEFINED
globalInfected.textContent = `${nn.cases}`;
globalActive.textContent = data.cases - data.recovered - data.deaths;
globalDeaths.textContent = data.deaths;
globalRecovered.textContent = data.recovered;
globalCountries.textContent = 219;
});
};
I also tried to convert the data to JSON again but still I was not able to access the property of returned data in screenDataShower
you're calling sendHTTPRequest inside casesDataFetcher's constructor, from your code there's no guarantee data is resolved when you access it
extract sendHTTPRequest into a new function and wrap into a promise
export class casesDataFetcher {
constructor(countryName) {
this.countryName = countryName
}
fetch = () => {
return new Promise((res, rej) => {
sendHTTPRequest(this.countryName)
.then((response) => {
return response.data;
})
.then((data) => {
this.country = data.location;
this.cases = data.confirmed;
this.recovered = data.recovered;
this.deaths = data.deaths;
console.log(this);
res(this);
});
})
}
}
make screenDataShower function async then you can await data from fetch function in casesDataFetcher, this way it can guarantee data is there when you access it
import { casesDataFetcher } from "./casesDataFetcher.js";
export const screenDataShower = async (country) => {
const dataStorage = [];
const globalInfected = document.querySelector(".infected h2");
const globalActive = document.querySelector(".active h2");
const globalDeaths = document.querySelector(".deaths h2");
const globalRecovered = document.querySelector(".recovered h2");
const globalCountries = document.querySelector(".countries h2");
const _casesDataFetcher = new casesDataFetcher(country)
const data = await _casesDataFetcher.fetch()
console.log(typeof data);
console.log(typeof data);
console.log(data.cases); // NOT WORKING GIVING UNDEFINED
globalInfected.textContent = `${nn.cases}`;
globalActive.textContent = data.cases - data.recovered - data.deaths;
globalDeaths.textContent = data.deaths;
globalRecovered.textContent = data.recovered;
globalCountries.textContent = 219;
};
The problem is that the json method of your response returns a promise instead of plain JSON. So you should change the call of the json method in your sendHTTPRequest function to something like:
.then((response) => {
const newResponse = response.json().then((jsonResponse) => jsonResponse);
return newResponse;
})

I get Promise { <pending> } as returned value and also calling in async scope gives me undefined immediately

Im trying to return a value from a Promise in async-await form and use it in another function in another file, but I do have problem because my Promise doesnt return any value.
When im trying to console.log('website') it returns me undefined immediately (it's like the value is not being fetched at all from API services). I dont know what im doing wrong, I really love to learn about Promises and Async-Await but each time im trying to work with them im getting more confused.
const dns = require('dns')
const iplocation = require("iplocation").default;
const emojiFlags = require('emoji-flags');
const getServerIPAddress = async (server) => {
return new Promise((resolve, reject) => {
dns.lookup(server, (err, address) => {
if (err) throw reject(err);
resolve(address);
});
});
};
const getServerLocation = async (server) => {
const ip = await getServerIPAddress(server)
iplocation(ip).then((res) => {
const country = emojiFlags.countryCode(res.countryCode)
const result = `Location: ${country.emoji} ${country.name}`
return result
})
.catch(err => {
return `Location: Unknown`
});
}
(async function() {
console.log(await getServerLocation('www.google.com'))
})()
module.exports = {
getServerLocation
}
It is really important for me to get result from this function first, then use its value in another function. I wish you could give me tips on how to do tasks asynchronously.
You're clearly using async so it's not apparent why you're using then as well. If you use then then you must return the promise as well in order to preserve the promise chain:
const getServerLocation = async (server) => {
const ip = await getServerIPAddress(server)
return iplocation(ip).then((res) => {
const country = emojiFlags.countryCode(res.countryCode)
const result = `Location: ${country.emoji} ${country.name}`
return result
})
.catch(err => {
return `Location: Unknown`
});
}
Otherwise just async this:
const getServerLocation = async (server) => {
const ip = await getServerIPAddress(server)
let res = await iplocation(ip);
const country = emojiFlags.countryCode(res.countryCode)
const result = `Location: ${country.emoji} ${country.name}`
return result
}
const getServerLocation = async (server) => {
const ip = await getServerIPAddress(server)
//you need to return
return iplocation(ip).then((res) => {
const country = emojiFlags.countryCode(res.countryCode)
const result = `Location: ${country.emoji} ${country.name}`
return result
})
.catch(err => {
return `Location: Unknown`
});
}

Node JS async/await with multiple fs.writeFile using through2 (Gulp/Vinyl)

I'm using through2 to generate multiple files from a Gulp stream. I'm using NodeJS 10.6.0 so thought I'd make full use of async/await, but am not fully understanding the mechanics yet. Currently the through2 done() callback is being fired before all files have been written.
Here's what I have (simplified) - note that I'm not returning the stream at the end as there is no need to.
async function createDirectory(pathDir) {
return new Promise((resolve, reject) => {
mkdirp(pathDir, (err) => {
if (err) reject(err);
else resolve();
});
});
}
async function writeFile(outputFilePath, outputFileContent) {
return new Promise((resolve, reject) => {
fs.writeFile(outputFilePath, outputFileContent, (err) => {
if (err) reject(err);
else resolve();
});
});
}
async function doWriteFile(outputFolderPath, outputFilePath, outputContent) {
await createDirectory(outputFolderPath);
await writeFile(outputFilePath, outputContent, outputContent);
}
async function doGenerateVariant(data, variantArr) {
for (const variant of variantArr) {
/* Do a load of stuff */
const variantOutputFolderPath = blah;
const variantOutputFilePath = blah;
const variantOutputContent = blah;
await doWriteFile(variantOutputFolderPath, variantOutputFilePath, variantOutputContent);
}
}
const generateVariant = () => {
return through.obj((file, enc, done) => {
const data = JSON.parse(file.contents.toString());
*/ Do a load of stuff */
const { variant } = data;
const variantArr = Object.values(variant);
doGenerateVariant(data, variantArr);
return done();
});
};
This doesn't work as done() gets returned before all files have been written. I'm guessing I'm missing a return or two but nothing I do seems to be working.
If I pass done() into doGenerateVariant and call it after doWriteFile everything works as expected but I know this isn't correct.
You need to wait for doGenerateVariant to do its job before calling done. Remember async function always returns a Promise. So you could do it this way
const generateVariant = () => {
return through.obj((file, enc, done) => {
const data = JSON.parse(file.contents.toString());
*/ Do a load of stuff */
const { variant } = data;
const variantArr = Object.values(variant);
doGenerateVariant(data, variantArr).then(() => done());
});
};
or using async/await
const generateVariant = () => {
return through.obj(async (file, enc, done) => {
const data = JSON.parse(file.contents.toString());
*/ Do a load of stuff */
const { variant } = data;
const variantArr = Object.values(variant);
await doGenerateVariant(data, variantArr);
done();
});
};

Categories