Using async and axios to make a weather app - javascript

I'm fairly certain it's a detail that I can't remember how to fix, but I've gotten the code to pull the data from the URL, but I can't call the setResults() method. I'm sure there is a way around it but I'm unsure how to do it.
class Test {
constructor() {
this.testResults = document.getElementsByClassName('test-results');
}
async run() {
console.log(new Date().toISOString(), '[Test]', 'Running the test');
// TODO: Make the API call and handle the results
const url = `http://api.openweathermap.org/data/2.5/weather?q=${query}&appid=25e989bd41e3e24ce13173d8126e0fd6&units=imperial`;
//Using the axios libary to call the data and log it.
const getData = async url => {
try {
const response = await axios.get(url);
const data = response.data;
console.log(data);
var results = data;
} catch (error) {
console.log(error);
}
};
getData(url);
}
setError(message) {
// TODO: Format the error
this.testResults.innerHTML = (message || '').toString();
}
setResults(results) {
results = responses()
this.testResults.innerHTML = (results || '').toString();
}
}

The bug that you did not see is probably related to testResults being a HTMLCollection rather than HTMLElement.
So in order to make the setResults method to work properly you need to adjust it.
Here I'm providing a possible solution.
class Test {
testResults;
constructor() {
this.testResults = document.getElementsByClassName('test-results');
}
async run() {
console.log(new Date().toISOString(), '[Test]', 'Running the test');
// TODO: Make the API call and handle the results
const url = `http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=25e989bd41e3e24ce13173d8126e0fd6&units=imperial`;
//Using the axios libary to call the data and log it.
const getData = async url => {
try {
const response = await axios.get(url);
const data = response.data;
this.setResults(data);
} catch (error) {
console.log(error);
}
};
getData(url);
}
setError(message) {
// TODO: Format the error
this.testResults[0].innerHTML = (message || '').toString();
}
setResults(results) {
results = JSON.stringify(results);
for(let resultEl of this.testResults) {
resultEl.innerHTML = (results || '').toString();
}
// this.testResults[0].innerHTML = (results || '').toString();
}
}
let testObj = new Test();
testObj.run();

Related

async await not working in composable function vue 3

In my project I have a function for downloading the files. When click the button the function onDownload will be called:
import {useOnDownload} from "../../use/useOnDownload"
setup() {
...
const loading = ref(null)
onDownload = (id) => {
loading.value = id
await useOnDownload(id)
loading.value = null
}
return {loading, onDownload}
}
I refactored the code for api in a file useOnDownload.js call because the same code is used in another components as well.
export async function useOnDownload(id) {
// make api call to server with axios
}
What I did wrong? I need to wait for the function useOnDownload ... in order the loader to work.
Here is how to make async composable functions with async await syntax
export default function useOnDownload() {
const isLoading = ref(true);
const onDownload = async () => {
isLoading.value = true;
try {
const { data } = await axios.post('/api/download', {id: id},
{responseType: 'blob'})
// handle the response
} catch (error) {
console.log(error);
} finally {
isLoading.value = false;
}
};
// invoke the function
onDownload();
return { // return your reactive data here };
}
import useOnDownload from "../../use/useOnDownload"
// no await in setup script or function
const { reactiveDataReturned } = useOnDownload();
Read more here
onDownload must be async in order to use await within it
I managed to solved another way without async and await...
I passed the reference object loader to the function parameter (as optional) and handle from there...
export function useOnDownload(id, loader) {
if(loader !== undefined) {
loader.value = id
}
axios.post('/api/download', {id: id}, {
responseType: 'blob'
}).then(response => {
// handle the response
...
if(loader !== undefined) {
loader.value = null
}
}).catch(error => {
// handle the error
...
if(loader !== undefined) {
loader.value = null
}
})
}
You are using the await keyword in your onDownlaod function, but the function is not asynchronous. Here's how you should update it.
// next line important
onDownload = async(id) => {
loading.value = id
await useOnDownload(id)
loading.value = null
}

How to improve the efficiency of an algorithm that does multiple fetch calls in terms of security and complexity?

The exercise is Write a JavaScript package that is able to:
fetch an array of URLs which contain JSON data
return their contents in a promise
I have already made a solution to this exercise, but I would like some input on how to improve this solution in terms of efficiency, security and complexity. I have also included unit tests for this solution.
index.js
const { InvalidParameterError, EmptyArrayError, InvalidURLError, FetchError, APIError } = require("./errors");
const fetch = require('node-fetch');
const validUrl = require('valid-url');
const links = [
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/ftse-fsi.json',
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-hkd.json',
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-usd.json'
]
/**
* iterates through the array and fetches data from each url, includes edge cases
* #params {urls} array of URLs
* #return {Promise} promise which is an array of the data fetched from the URL in JSON format
*/
function requestMultipleUrls (urls) {
if (!Array.isArray(urls)){
console.log('Parameter passed is not in array format', urls)
return new InvalidParameterError('urls should be inside in an array')
}
if (urls.length === 0) {
console.log('Array cannot be empty', urls)
return new EmptyArrayError('Array of urls cannot be empty')
}
const promises = urls.map(async (url) => {
if ((!validUrl.isUri(url))) {
console.log('URL is in incorrect format', url)
return new InvalidURLError('Input URL is not in the correct format')
}
try {
const data = await fetch(url);
const { status } = data
if (status !== 200) {
console.log(`Your fetch call returned a status code of ${status}, check if the URL you entered is correct`)
return new APIError('Something went wrong with the URL you are trying to fetch', status)
} else {
return mapData(data)
}
} catch(error) {
console.log(error)
return new FetchError('Something went wrong during the fetch call')
}
})
return Promise.all(promises)
}
/**
* Deconstructs the data being passed to form an object that is eventually returned
* #params {data} response that is recieved from the fetch call in requestMultipleUrls
* #return {final} object that is formatted
*/
const mapData = async (data) => {
const {url, status} = data
const final = {
url,
status,
body: await data.json()
}
return final
}
requestMultipleUrls(links).then((data) => console.log(data))
module.exports = {
requestMultipleUrls
}
errors.js
class InvalidParameterError {
constructor (message) {
this.status = 400;
this.message = message;
}
}
class EmptyArrayError {
constructor(message) {
this.status = 400;
this.message = message;
}
}
class InvalidURLError {
constructor(message) {
this.status = 400;
this.message = message
}
}
class APIError {
constructor(message, status){
this.status = status,
this.message = message
}
}
class FetchError {
constructor(message) {
this.status = 502;
ths.message = message
}
}
module.exports = {
InvalidParameterError,
EmptyArrayError,
InvalidURLError,
FetchError,
APIError
}
index.spec.js
const { expect } = require('chai');
const { requestMultipleUrls } = require('../index');
const gbpUsd = require('./gbp-usd.json');
const gbpHkd = require('./gbp-hkd.json')
describe('requestMultipleUrl intended functionality', () => {
it('function should return an accumulated API response', async () => {
const dummyUrls = [
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-usd.json',
'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-hkd.json'
]
const promise = await requestMultipleUrls(dummyUrls);
expect(promise).to.deep.equal([gbpUsd, gbpHkd]);
})
})
describe('requestMultipleUrl edge cases', () => {
it('should throw an error if the urls in the array do not match URI standards', async () => {
const dummyUrls = [
'lfkjklwfklm',
'ekefjkf;lfl;'
]
try {
await requestMultipleUrls(dummyUrls)
} catch (error) {
expect(error.message).to.eql('Input URL is not in the correct format')
}
})
it('should throw an error if URL array is empty', async () => {
const dummyUrls = []
try {
await requestMultipleUrls(dummyUrls)
} catch (error) {
expect(error.message).to.eql('Array of urls cannot be empty')
}
})
it('should throw an error if the parameter being passed is not an array', async () => {
const dummyUrls = 'https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-hkd.json'
try {
await requestMultipleUrls(dummyUrls)
} catch (error) {
expect(error.message).to.eql('urls should be inside in an array')
}
})
it('should throw an error if status code is not 200', async () => {
const dummyUrls = ['https://ft-tech-test-example.s3-eu-west-1.amazonaws.com/gbp-jpy.json']
try {
await requestMultipleUrls(dummyUrls)
} catch(error){
expect(error.message).to.eql('Something went wrong with the URL you are trying to fetch')
}
})
})

Async/Await in for loop NodeJS Not blocking the loop execuation

I know that old school for loop works in the traditional way - that it waits for the await to finish getting results.
But in my use case, I need to read a file from local/s3 and process it line by line, and for each line I need to call an External API.
Generally I use await inside the loop because all are running inside a lambda and I don't want to use all memory for running it parallelly.
Here I am reading the file using a stream.on() method, and in order to use await inside that, I need to add async in read method, like so:
stream.on('data',async () =>{
while(data=stream.read()!==null){
console.log('line');
const requests = getRequests(); // sync code,no pblms
for(let i=0;i<requests.length;i++){
const result = await apiCall(request[i);
console.log('result from api')
const finalResult = await anotherapiCall(result.data);
}
}
});
This is working but order in which the lines are processed is not guaranteed. I need all in a sync manner. Any help?
Complete Code
async function processSOIFileLocal (options, params) {
console.log('Process SOI file');
const readStream = byline.createStream(fs.createReadStream(key));
readStream.setEncoding('utf8');
const pattern = /^UHL\s|^UTL\s/;
const regExp = new RegExp(pattern);
readStream.on('readable', () => {
let line;
while (null !== (line = readStream.read())) {
if (!regExp.test(line.toString())) {
totalRecordsCount++;
dataObject = soiParser(line);
const { id } = dataObject;
const XMLRequests = createLoSTRequestXML(
options,
{ mapping: event.mapping, row: dataObject }
);
console.log('Read line');
console.log(id);
try {
for (let i = 0;i < XMLRequests.length;i++) {
totalRequestsCount++;
console.log('Sending request');
const response = await sendLoSTRequest(
options,
{ data: XMLRequests[i],
url: LOST_URL }
);
console.log("got response");
const responseObj = await xml2js.
parseStringPromise(response.data);
if (Object.keys(responseObj).indexOf('errors') !== -1) {
fs.writeFileSync(`${ERR_DIR}/${generateKey()}-${id}.xml`, response.data);
failedRequestsCount++;
} else {
successRequestsCount++;
console.log('Response from the Lost Server');
console.log(response[i].data);
}
}
} catch (err) {
console.log(err);
}
}
}
})
.on('end', () => {
console.log('file processed');
console.log(`
************************************************
Total Records Processed:${totalRecordsCount}
Total Requests Sent: ${totalRequestsCount}
Success Requests: ${successRequestsCount}
Failed Requests: ${failedRequestsCount}
************************************************
`);
});
}
async function sendLoSTRequest (options, params) {
const { axios } = options;
const { url, data } = params;
if (url) {
return axios.post(url, data);
// eslint-disable-next-line no-else-return
} else {
console.log('URL is not found');
return null;
}
}
Code needs to flow like so:
read a line in a sync way
process the line and transform the line into an array of two members
for every member call API and do stuff
once line is complete, look for another line, all done in order
UPDATE: Got a workaround..but it fires stream.end() without waiting stream to finish read
async function processSOIFileLocal (options, params) {
console.log('Process SOI file');
const { ERR_DIR, fs, xml2js, LOST_URL, byline, event } = options;
const { key } = params;
const responseObject = {};
let totalRecordsCount = 0;
let totalRequestsCount = 0;
let failedRequestsCount = 0;
let successRequestsCount = 0;
let dataObject = {};
const queue = (() => {
let q = Promise.resolve();
return fn => (q = q.then(fn));
})();
const readStream = byline.createStream(fs.createReadStream(key));
readStream.setEncoding('utf8');
const pattern = /^UHL\s|^UTL\s/;
const regExp = new RegExp(pattern);
readStream.on('readable', () => {
let line;
while (null !== (line = readStream.read())) {
if (!regExp.test(line.toString())) {
totalRecordsCount++;
dataObject = soiParser(line);
const { id } = dataObject;
const XMLRequests = createLoSTRequestXML(
options,
{ mapping: event.mapping, row: dataObject }
);
// eslint-disable-next-line no-loop-func
queue(async () => {
try {
for (let i = 0;i < XMLRequests.length;i++) {
console.log('Sending request');
console.log(id);
totalRequestsCount++;
const response = await sendLoSTRequest(
options,
{ data: XMLRequests[i],
url: LOST_URL }
);
console.log('got response');
const responseObj = await xml2js.
parseStringPromise(response.data);
if (Object.keys(responseObj).indexOf('errors') !== -1) {
// console.log('Response have the error:');
// await handleError(options, { err: responseObj, id });
failedRequestsCount++;
fs.writeFileSync(`${ERR_DIR}/${generateKey()}-${id}.xml`, response.data);
} else {
console.log('Response from the Lost Server');
console.log(response[i].data);
successRequestsCount++;
}
}
} catch (err) {
console.log(err);
}
});
}
}
})
.on('end', () => {
console.log('file processed');
console.log(`
************************************************
Total Records Processed:${totalRecordsCount}
Total Requests Sent: ${totalRequestsCount}
Success Requests: ${successRequestsCount}
Failed Requests: ${failedRequestsCount}
************************************************
`);
Object.assign(responseObject, {
failedRequestsCount,
successRequestsCount,
totalRecordsCount,
totalRequestsCount
});
});
}
Thank You
The sample code at the top of your question could be rewritten like
const queue = (() => {
let q = Promise.resolve();
return (fn) => (q = q.then(fn));
})();
stream.on('data', async() => {
while (data = stream.read() !== null) {
console.log('line');
const requests = getRequests(); // sync code,no pblms
queue(async () => {
for (let i = 0; i < requests.length; i++) {
const result = await apiCall(request[i]);
console.log('result from api');
const finalResult = await anotherapiCall(result.data);
}
});
}
});
Hopefully that will be useful for the complete code
If anyone want a solution for synchronisely process the file, ie, linebyline read and execute some Async call, it's recommended to use inbuilt stream transform. There we can create a transform function and return a callback when finishes.
That's will help of any one face this issues.
Through2 is a small npm library that also can be used for the same.

How can I manage to make diffrent request after first request with failed status

I try to fetch some object, but the problem is that I need to check first if there ist any object on cache endpoint, if not I should do normal fetching from regular endpoint.
So far I only managed to do fetching from:
Normal endpoint and set everything on state,
Cache endpoint and set everything on state
Any attempts to mix both methods ended in failure :(
How can I mix this two methods?
const getCache = async () => {
try {
const apiCall = await fetch(fetchCacheEndpoint)
const data = await apiCall.json()
return data
} catch (e) {
console.log(e);
}
}
const pageOne = getCache().then((result) => {
const convertedOBj = result.doSomeSeriousStuff()
this.setState({
desiredObj: convertedOBj
})
})
I expected to do something like this
const getCache = async () => {
try {
const apiCall = await fetch(fetchCacheEndpoint)
const data = await apiCall.json()
return data
} catch (e) {
console.log(e);
}
}
let convertedOBj
const pageOne = getCache().then((result) => {
if ((result === undefined) || (!result) || (result && !result.length > 0)) {
const makeRegularFetch = async () => {
const regularFetch = await fetch(regularEndPoint)
const data = await regularFetch.json()
}
const pageTwo = makeRegularFetch ().then((result) => {
convertedOBj = result.doSomeSeriousStuff()
this.setState({
desiredObj: convertedOBj
})
})
} else {
convertedOBj = result.doSomeSeriousStuff()
this.setState({
desiredObj: convertedOBj
})
}
})
After first fetch (getCache) is failed there is another fetch (makeRegularFetch) to second endpoint which is always fine, but only in the case when first(getCache) return empty object or just any kind of error
How can I handle this kind of action?
From what I can see in your second part of your code, you never execute your pageOne function.
Try pageOne() after your definition.
However I made a fiddle on stackblitz for your case: https://stackblitz.com/edit/js-eufm8h
If you don't understand something, feel free to ask.

Node.js - Not running DB promises before returning response

I have this request handler on my node server. It has three MongoDB queries, and I want all the results to be returned, before the response is sent.
api.get('/getStats/:productID', (req,res)=>{
let data = {};
let dailySales = [];
let avgProduct = "";
let customers = [];
Sales.find({productID: productID}).then(
sales => {
dailySales = sales;
}
);
Products.find({}).then(
products => {
// Calculate Avg product here
avgProduct = result;
}
);
Customers.find({}).then(
customers => {
customers = customers;
}
);
data = {
dailySales,
avgProduct,
customers
};
res.json(data);
});
But running this returns
data: {
dailySales: [],
avgProduct: "",
customers: []
}
i.e. The Mongo response is returning before the data is run. Please how to I fix. Thank You
wait for all the promises to resolve before sending the actual response
const sales = Sales.find({productID: productID});
const allProducts = Products.find({});
const allCustomers = Customers.find({});
Promise.all([sales, allProducts, allCustomers])
.then(data => res.json(data));
you can try using the Promise.all where you can pass the MongoDB queries as parameter to it ,the promise will be resolved when all the queries return the result in the array
Try using the in-built util.promisify function along with
async-await to get data correctly!
const promisify = require('utils').promisify;
const salesFindOnePromise = promisify(Sales.find);
const productsFindAllPromise = promisify(Products.find);
const customersFindAllPromise = promisify(Customers.find);
findDailySalesByIdAsync = async (productID) => {
try {
return await salesFindOnePromise({ productID: productID });
} catch(err) {
throw new Error('Could not fetch the appropriate sales with productID');
}
}
findProductsAsync = async () => {
try {
return await productsFindAllPromise({});
} catch (err) {
throw new Error('Could not fetch sales!');
}
}
findCustomersAsync = async () => {
try {
return await customersFindAllPromise({});
} catch (err) {
throw new Error('Could not fetch customers!');
}
}
api.get('/getStats/:productID', async (req,res)=>{
try {
const dailySales = await findDailySalesByIdAsync(productID);
const avgProduct = await findProductsAsync();
const customers = await findCustomersAsync();
const data = {
dailySales,
avgProduct,
customers
};
return res.status(200).send(data);
} catch(err) {
console.err(`Failed because: {err}`);
throw new Error('Could not fetch data because of some error!');
}
});

Categories

Resources