parsing data from an api call into a text file using axios - javascript

I am parsing data from an API call into a text file. However, I wanted to use async-await and break the call below call into 3 separate functions.
#!/usr/bin/env node
const yargs = require("yargs");
const axios = require("axios");
const fs = require("fs");
const options = yargs
.usage("Usage: -n <name>")
.option("n", {
alias: "name",
describe: "Your name",
type: "string",
demandOption: true,
})
.option("s", { alias: "search", describe: "Search Term", type: "string" })
.argv;
const greetings = `Hello ${options.name}!`;
console.log(greetings);
console.log("Here's a random joke for you: ");
const url = options.search
? `https://icanhazdadjoke.com/search?term${escape(options.search)}`
: " https://icanhazdadjoke.com/";
axios.get(url, { headers: { Accept: "application/json" } }).then((res) => {
if (options.search) {
res.data.results.forEach((j) => {
fs.appendFile("jokes.txt", "\n" + j.jokes, (err) => {});
});
if (res.data.results.length === 0) {
console.log("no joke found 😭");
}
} else {
fs.appendFile("jokes.txt", res.data.joke, (err) => {
if (err) throw err;
console.log("File Updated");
});
}
});
So the above code works absolutely fine and generates the file perfectly, however when I tried to break it into the following below functions, I just get undefined in the text file, I am not sure why this is happening.
const getJoke = async (url) => {
try {
const joke = await axios.get(url, {
headers: { Accept: "application/json" },
});
return joke;
} catch (error) {
console.error(error);
}
};
const parseJokes = (res) => {
if (options.search) {
res.data.results.forEach((j) => {
return `\n ${j.joke}`;
});
if (res.data.results.length === 0) {
console.log("no joke found 😭");
}
} else {
return res.data.joke;
}
};
const addJokeToFile = async () => {
const result = await getJoke(url)
.then((res) => {
parseJokes(res);
})
.catch((err) => {
console.error(`ERROR: ${err}`);
});
fs.appendFile("jokes.txt", result, (err) => {
console.error(err);
});
};

In the second (functional approach) addJokeToFile method, you are waiting for the promise to be resolved using both ways, await and .then, following modification to the code, might help you get through:
const addJokeToFile = async () => {
getJoke(url)
.then((res) => {
// Aside, we should also return some value from parseJokes function for "no joke found 😭" case, or return null and put a check here and only append to file when jokeString is not null.
const jokeString = parseJokes(res);
fs.appendFile("jokes.txt", jokeString, (err) => {
console.error(err);
});
})
.catch((err) => {
console.error(`ERROR: ${err}`);
});
};

Try using appendFile from 'fs/promises' so that you can stick with the async/await style. Since getJoke returns a promise I would expect result to be a Promise<string | undefined> depending on if any errors show up earlier in the chain.
const { appendFile } = require('fs/promises');
const addJokeToFile = async () => {
try {
const result = await getJoke(url);
const parsed = parseJokes(result);
await appendFile('jokes.txt', parsed);
} catch (err) {
console.error(err);
}
};

Related

Why is this asynchronous function being called twice

I am trying to create a user with email and password using firebase, but when I call the function that creates it, it is called twice and I get an error because I am trying to register the email that is already in use.
I noticed that the console.log('CALLED') is called once, I don't understand why RegisterWithEmail is called twice. My auth flow only creates the userDocument in the confirmation phase, for this reason userSnap.length equals zero in the second call and tries to create again.
How can I call this function once?
FILE: emailconfirm.page.tsx
registerEmail = async data => {
const { setRegStatus, createDoc } = this.props;
console.log('CALLED')
await RegisterWithEmail(data).then(res => {
console.log('Final response ', res)
if(res === 'EMAIL_VERIFIED') {
createDoc()
setRegStatus({ status: 'created', data: res })
}
else if(res === 'SOMETHING_WENT_WRONG'){
setRegStatus({ status: 'error', data: res })
}
}).catch(err => {
console.log('Error ', err)
setRegStatus({ status: 'error', data: err })
})
}
FILE: firebase.utils.tsx
export const RegisterWithEmail = async user => {
console.log("Called Once...");
if(!user) return 'SOMETHING_WENT_WRONG';
else {
const snap = await firestore.collection('users').where('email', '==', user.email).get();
const docs = snap.docs.map((doc) => doc.data());
if (docs.length !== 0) return 'EMAIL_HAS_ALREADY_BEEN_TAKEN';
try {
console.log("Trying to register email...");
return await auth.createUserWithEmailAndPassword(user.email, user.password).then(async usr => {
await usr.user.updateProfile({
displayName: user.name
}) // SETTING NAME
const sendVerifyEmail = usr.user.sendEmailVerification().then(() => setTimer(usr.user, 5))
return await sendVerifyEmail.then(msg => {
console.log('Finishing...', msg)
if(msg.txt !== 'waiting') {
if(msg.error) {
throw msg.txt
}
else return msg.txt
}
}).catch(() => {
throw 'EMAIL_NOT_SENT'
})
}).catch(() => {
throw 'USER_NOT_CREATED'
})
} catch (err) {
throw 'USER_ALREADY_REGISTERED'
}
}
}
Developer console:
You shouldn't be mixing and matching .then()s in async functions for your own sanity's sake.
Something like
export const RegisterWithEmail = async (user) => {
if (!user) return false;
const snap = await firestore.collection("users").where("email", "==", user.email).get();
const docs = snap.docs.map((doc) => doc.data());
if (docs.length !== 0) return false;
console.log("Trying to register email...");
try {
const resp = await auth.createUserWithEmailAndPassword(user.email, user.password);
// then ...
return true;
} catch (err) {
// catch ...
}
};
might work better for you.
I need more code to be sure, but I think you should add await
registerEmail = async data => {
console.log('CALLED')
await RegisterWithEmail(data)
}

module.exports returning undefined

I`m trying to make a translation function with IBM Watson API in "services/ibmWatson/index.js". I receive the response correctly, but when I return the response to the "IncidentController.js" it receives as undefined.
const LanguageTranslatorV3 = require('ibm-watson/language-translator/v3');
const { IamAuthenticator } = require('ibm-watson/auth');
module.exports = {
async translate(phrase, language) {
const languageTranslator = new LanguageTranslatorV3({
authenticator: new IamAuthenticator({ apikey: '<my_API_key>' }),
url: 'https://gateway.watsonplatform.net/language-translator/api/',
version: '2020-03-28',
});
await languageTranslator.translate(
{
text: phrase,
source: 'pt',
target: language
})
.then(response => {
if(response.status=200){
console.log(response.result.translations);
return(response.result.translations);
}
return (["error"]);
})
.catch(err => {
console.log('error: ', err);
return (["error"]);
});
}
}
In the above code the console.log(response.result.translations) returns correctly:
[ { translation: 'Dog run over.' },
{ translation: 'Castration' },
{ translation: 'Ticks' },
{ translation: 'Tuberculosis' } ]
In the in IncidentController.js:
const Watson = require('../../src/services/ibmWatson');
const db_connection = require('../database/connection');
module.exports = {
async index(request, response) {
const incidents = await db_connection('incidents').join('ongs', 'ongs.id', '=', 'incidents.ong_id')
.select([
'incidents.*',
'ongs.name',
'ongs.uf']
);
const titles = [];
incidents.forEach((incident) => { titles.push(incident.title) });
const translated_titles = await Watson.translate(titles, "en");
console.log(translated_titles);
return response.json(incidents);
}
}
In the above code the console.log(response.result.translations) returns undefined.
What is wrong with it?
You are returning response.result.translations to the response callback from then().
Since that callback cannot be accessed by your IncidentController, it returns undefined.
This is one way to solve this problem:
// services/ibmWatson/index.js
translate(phrase, language) {
return new Promise((resolve, reject) => {
try {
const response = await languageTranslator.translate({ /* options */ });
resolve(response); // Return the translations
} catch(error) {
reject(error); // If there's an error, return the error
}
});
}
// IncidentController.js
async index() {
// ...
const translatedTitles = await Watson.translate(titles, "en");
console.log(translatedTitles); // Should be working now
}
I hope I could help you or at least lead you in the right direction.

how to return value from a promise function

I have a function which checks whether a device is online or not. Below is the code.
const ping = require('ping');
export function findDevices(device) {
try {
const hosts = [device];
let result = null;
hosts.forEach((host) => {
ping.promise.probe(host)
.then((res) => {
console.log(res.alive)
result = res.alive;
return {
Status: result
}
});
});
} catch (err) {
logger.error(err, '[ config - findDevices() ]');
console.error(err);
return {
Status: "Failed"
}
}
}
I am calling this function in a redux action like this:
export function start(device) {
return dispatch => {
const status = Connectionstatus.findDevices(device);
return dispatch({
type: actionTypes.CONNECTIONSTATUS,
payload: {
ConnectionStatus: status
}
})
};
}
I am expective the status variable to be either true or false. But i am getting as undefined even though i am returning the value inside then of the promise function. i have tried awaiting this call and still its not working. Any help would be much appreciated. Thanks.
If that's the case you can do like this
const getStatus = async () => {
try {
const hosts = [device];
const promises = [];
hosts.forEach((host) => {
promises.push(ping.promise.probe(host));
});
const result = await Promise.all(promises);
const status = result.map((r) => { return r.alive; });
return status;
} catch (err) {
logger.error(err, '[ config - findDevices() ]');
return { status: 'Failed' };
}
};
Not 100% sure what all the vars are, but have you considered using async/await to simplify things a bit like this?
const getStatus122 = async device => {
return await Promise.all([device].map(ping.promise.probe))
.then(({ alive }) => alive)
.then(Status => ({ Status }))
.catch(error => {
logger.error(error, '[ config - findDevices() ]');
return { Status: 'Failed' };
})
}
More on that here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
With Promises you should inspect the promised result either in when or catch callback functions. With async/await the code may look a bit simpler. Here is the version with explicit promises.
const ping = require('ping');
const Connectionstatus = {
findDevices: (device) => {
return ping.promise.probe(device).then((res) => {
const result = res.alive;
console.log(result);
return {
Status: result,
};
}).catch((err) => {
logger.error(err, '[ config - findDevices() ]');
console.error(err);
return {
Status: "failed"
}
});
}
}
export function start(device) {
return dispatch => {
Connectionstatus.
findDevices(device).
then((status) => {
dispatch({
type: actionTypes.CONNECTIONSTATUS,
payload: {
ConnectionStatus: status
}
})
});
};
}
You may see that error handling moved to the catch callback function while the dispatch is done in the then callback function. And this is the answer to your question.

Capturing errors with Async/Await

I have a part of my code that makes several API calls to different endpoints and I want to know if any of those calls fail so I can display an appropriate error message. Right now, if an error happens in one() it will stop all other calls from happening, but that's not what I want; If an error occurs, I want it to populate the errors object and have the program continue on.
async function gatherData() {
let errors = { one: null, two: null, three: null };
const responseOne = await one(errors);
const responseTwo = await two(errors);
const responseThree = await three(errors);
if (!_.isNil(errors.one) || !_.isNil(errors.two) || !_.isNil(errors.three)) {
// an error exists, do something with it
} else {
// data is good, do things with responses
}
}
gatherData();
async function one(errors) {
await axios
.get("https://jsonplaceholder.typicode.com/comment")
.then(res => {
return res;
})
.catch(err => {
errors.one = err;
return err;
});
}
async function two(errors) {
await axios
.get("https://jsonplaceholder.typicode.com/comments")
.then(res => {
return res;
})
.catch(err => {
errors.two = err;
return err;
});
}
async function three(errors) {
await axios
.get("https://jsonplaceholder.typicode.com/comments")
.then(res => {
return res;
})
.catch(err => {
errors.three = err;
return err;
});
}
If you pass the errors to the async functions, so pass the errors object as parameter
const responseOne = await one(errors);
const responseTwo = await two(errors);
const responseThree = await three(errors);

Testing Chained Promises (Jasmine, React, Karma)

I have run into several situations on my present project where I have a chain of promises that I'm not sure how to deal with.
Here is the relevant code block:
return this.axios.get(path, requestOpts)
.then((response) => {console.log('did authorize: ', response); return response})
.then((response) => {
if (response.data.ok) {
window.localStorage.setItem(path, JSON.stringify(response.data));
console.log("Setting localStorage item ", path, response.data);
return response.data.payloadUrl;
} else {
console.error("Non-ok response for ", path, response.data);
const resp: DisplayTokenResponse = response.data;
//TODO: reject promise?
if (resp.status === "AUTHENTICATION_REQUIRED") {
this.axiosService.goToLoginPage(window.location + '');
}
Promise.reject(response.data.message);
}
});
My test (so far) looks like this:
describe('.authorize()', () => {
let axiosSpy: jasmine.Spy;
beforeEach((done) => {
spyOn(svc, 'keyPath').and.returnValue(path);
spyOn(svc, 'storedToken').and.returnValue(stored);
let response = {
data: {
ok: true,
message: 'test-response',
payloadUrl: 'http://payload-url.com'
}
}
spyOn(svc.axios, 'get').and.callFake(
(path:string, reqOpts:AxiosRequestConfig) => {
return new Promise(() => {
response
});
}, (e) => {
console.log(`failed`);
});
});
describe('should authorize user', () => {
it('when supplied a STRING', () => {
clientId = clientId_string;
});
it('when supplied a NUMBER', () => {
clientId = clientId_number;
});
afterEach((done) => {
svc.authorize(clientId, locationId, screenId).then((result) => {
console.log(`result ${result}`);
done();
}, (e) => {
console.log(`failed with error ${e}`);
done();
});
});
});
});
I can test one-level-down promises, but how to I set up my tests to be able to handle situations like this?
Finally got it figured out. I believe it stemmed from a confusion between creating Promise instances versus their resolvers.
The new beforeEach block looks like this:
beforeEach(() => {
spyOn(svc, 'keyPath').and.returnValue(path);
spyOn(svc, 'storedToken').and.returnValue(stored);
let axiosPromise = new Promise((resolve, reject) => {
var responseData = {
data: {
ok: true,
message: 'test-response',
payloadUrl: 'http://payload-url.com'
}
};
resolve(responseData);
});
spyOn(svc.axios, 'get').and.callFake(
()=>{
return axiosPromise;
}
);
});
My tests now pass.

Categories

Resources