Using promises in Axios requests - javascript

I am trying to work out the best way to achieve something. When I land on a Profile page, the Profile component loads the data for that profile. This is assigned to this.profile. Within this data is a path to a file, where I want to process some data using this file. To me, the below approach seems slightly risky.
created() {
let vm = this;
let url = `/api/profile/${this.$route.params.id}`;
axios.get(url).then(response => {
this.profile = response.data;
d3.json(response.data.fileName)
.then(function (data) {
//do some stuff
}).catch(function (error) {
// handle error
});
});
}
Instead of that, I want to ensure that I first have the data from the axios call. So I am thinking I need a promise? I was thinking something more along the lines off
created() {
let vm = this;
let url = `/api/profile/${this.$route.params.id}`;
axios.get(url).then(response => {
this.profile = response.data;
}).then() {
d3.json(response.data.fileName)
.then(function (data) {
//do some stuff
}).catch(function (error) {
// handle error
});
};
}
But the above is incorrect, it is mainly to show what I am trying to achieve. I was wondering how I can maybe use deferred and promises to only execute the d3 stuff once the axios call is made.
Thanks

You can solve this by chaining promises, assuming that d3.json returns a promise:
created() {
let vm = this;
let url = `/api/profile/${this.$route.params.id}`;
axios.get(url)
.then(response => {
this.profile = response.data
return d3.json(response.data.fileName)
}).then(data => {
//do some stuff
}).catch(err => {
//log error
})
}

That's where async/await comes in handy. A you don't need to save this to a variable and B you have cleaner, more readable code.
async created() {
const url = `/api/profile/${this.$route.params.id}`;
const { data } = await axios.get(url); // Optional destructuring for less clutter
this.profile = data;
const d3Data = await d3.json(data.fileName);
//do whatever you want
}

async created() {
let vm = this;
let url = `/api/profile/${this.$route.params.id}`;
try {
const {data} = await axios.get(url)
const d3Data = await d3.json(data.fileName)
} catch(err) {
//error
}
}

Related

Store fetch data in variable to access it later

I'm facing a probably super easy to solve problem regarding fetching.
I'd like to fetch some json datas and store it in a variable to access it later.
The problem is that I always ends up getting undefined in my variable. What's the way to do to deal with that kind of data storing ?
Here's my code.
const fetchCities = () => {
fetch('cities.json')
.then(response => response.json())
.then(data => {
return data;
});
}
let cities = fetchCities();
console.log(cities)
Already looked up for answers but couldn't find a way to do. Thanks !
You could do this very simply with async/await like this:
const fetchCities = async () => {
let cities = await fetch('cities.json');
return cities.json();
};
let cities = await fetchCities();
console.log(cities);
Sending a fetch request takes time, so the console.log works before the data arrives.
The best way to deal with fetch is using async functions and await like so:
const fetchCities = ()=>{
return fetch('cities.json');
}
async function main(){
try {
const res = await fetchCities();
const data = await res.json();
// handle the data here, this will work only after the data arrival
console.log(data);
} catch (err) {
console.log(err);
}
}
main();
Note: await can only be used in async functions, that's the main purpose of the main function.
Or if you want to use .then:
const fetchCities = ()=>{
return fetch('cities.json');
}
function main(){
fetchCities()
.then(res => res.json())
.then(data => {
// handle the data here, all you code should be here
})
.catch (err => console.log(err));
}
main();

Confused by async behavior

I'm using a try/catch to make some fetch requests, then I am extracting the title from the HTML and adding it to my object 'sleekResponse'
When I try to parse the body and add it to that object I'm having issues with the return value not including the title that I extracted from the HTML
I know this has something to do with asynchronicity, or my shallow understanding of Promises, but I can't tell why the return value is different from the value it's logging just before it's sent.
async function fetchUrl(url) {
console.log(url);
try {
const myInit = {
mode: 'cors'
}
let sleekResponse = {};
await fetch(url, myInit).then(function (response) {
sleekResponse.redirected = response.redirected;
sleekResponse.status = response.status;
return response;
})
.then((response) => titleWait(response))
.then((res) => sleekResponse.title = res)
function titleWait(response) {
Promise.resolve(response.text()).then((res) => {
a = res.split('<title>');
b = a[1].split('</title>')
sleekResponse.title = b[0];
return sleekResponse;
})
console.log(sleekResponse);
return sleekResponse;
}
console.log(sleekResponse); // This logs the correct value
return sleekResponse; // when it's returned it doesn't show the title that was added
} catch (err) {
return `${err}`;
}
}
I've tried so many things I don't remember everything that I tried. I know that I'm missing something that might be obvious, but I still don't understand why the console.log value is different from the value returned one line later.
The main issue is that titleWait doesn't return its own promise:
function titleWait(response) {
return Promise.resolve(response.text()).then((res) => {
a = res.split('<title>');
b = a[1].split('</title>')
sleekResponse.title = b[0];
return sleekResponse;
});
}
It's still a very convoluted way to write it. This is better:
async function titleWait(response) {
const res = await response.text());
const a = res.split('<title>');
const b = a[1].split('</title>')
sleekResponse.title = b[0];
return sleekResponse;
}
i hope i can a litel help
this basic fetch();
const response = await fetch('your url')
const data = await response.json();
console.log(data);

Promise wont return valid value

I have this test I made just to check an API, but then i tryied to add an URL from a second fetch using as parameter a value obtained in the first fetch and then return a value to add in the first fecth. The idea is to add the image URL to the link. thanks in advance.
function script() {
const url = 'https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20'
const result = fetch(url)
.then( (res)=>{
if(res.ok) {
return res.json()
} else {
console.log("Error!!")
}
}).then( data => {
console.log(data)
const main = document.getElementById('main');
main.innerHTML=`<p><a href='${data.next}'>Next</a></p>`;
for(let i=0; i<data.results.length;i++){
main.innerHTML=main.innerHTML+`<p><a href=${getImageURL(data.results[i].url)}>${data.results[i].name}</a></p>`;
}
})
}
async function getImageURL(imgUrl) {
const resultImg = await fetch(imgUrl)
.then( (res)=> {
return res.json()
})
.then (data => {
console.log(data.sprites.other.dream_world.front_default);
})
return resultImg.sprites.other.dream_world.front_default;
}
In general, don't mix .then/.catch handlers with async/await. There's usually no need, and it can trip you up like this.
The problem is that your fulfillment handler (the .then callback) doesn't return anything, so the promise it creates is fulfilled with undefined.
You could return data, but really just don't use .then/.catch at all:
async function getImageURL(imgUrl) {
const res = await fetch(imgUrl);
if (!res.ok) {
throw new Error(`HTTP error ${res.status}`);
}
const resultImg = await res.json();
return resultImg.sprites.other.dream_world.front_default;
}
[Note I added a check of res.ok. This is (IMHO) a footgun in the fetch API, it doesn't reject its promise on HTTP errors (like 404 or 500), only on network errors. You have to check explicitly for HTTP errors. (I wrote it up on my anemic old blog here.)]
There's also a problem where you use getImageURL:
// Incorrent
for (let i = 0; i < data.results.length; i++) {
main.innerHTML=main.innerHTML+`<p><a href=${getImageURL(data.results[i].url)}>${data.results[i].name}</a></p>`;
}
The problen here is that getImageURL, like all async functions, returns a promise. You're trying to use it as those it returned the fulfillment value you're expecting, but it can't — it doesn't have that value yet.
Instead, you need to wait for the promise(s) youre creating in that loop to be fulfilled. Since that loop is in synchronous code (not an async function), we'd go back to .then/.catch, and since we want to wait for a group of things to finish that can be done in parallel, we'd do that with Promise.all:
// ...
const main = document.getElementById('main');
const html = `<p><a href='${data.next}'>Next</a></p>`;
Promise.all(data.results.map(async ({url, name}) => {
const realUrl = await getImageURL(url);
return `<p><a href=${realUrl}>${name}</a></p>`;
}))
.then(paragraphs => {
html += paragraphs.join("");
main.innerHTML = html;
})
.catch(error => {
// ...handle/report error...
});
For one, your
.then (data => {
console.log(//...
at the end of the promise chain returns undefined. Just remove it, and if you want to console.log it, do console.log(resultImg) in the next statement/next line, after await.
This the final version that accomplish my goal. Just want to leave this just in case someone finds it usefull. Thanks for those who answer!
function script() {
const url = 'https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20'
const result = fetch(url)
.then( (res)=>{
if(res.ok) {
return res.json()
} else {
console.log("Error!!")
}
}).then( data => {
console.log(data)
const main = document.getElementById('main');
main.innerHTML=`<p><a href='${data.next}'>Proxima Página</a></p>`;
Promise.all(data.results.map(async ({url, name}) => {
const realUrl = await getImageURL(url);
return `<div><a href=${realUrl}>${name}</a></div>`;
}))
.then(paragraphs => {
main.innerHTML=main.innerHTML+paragraphs;
})
.catch(error => {
console.log(error);
});
})
}
async function getImageURL(imgUrl) {
const res = await fetch(imgUrl);
if(!res.ok) {
throw new Error(`HTTP Error ${res.status}`)
}
const resultImg = await res.json();
return resultImg.sprites.other.dream_world.front_default
}

Save fetched JSON data to sessionStorage

I just figured out how to write an async/await function to fetch data from an API, and it's working, but it's hitting the API like crazy. So now I'm trying to save the fetched data to sessionStorage and only fetch from the API if the data isn't in the sessionStorage.
Here's my code:
const fetchMeetingData = async () => {
console.log('api hit')
try {
const response = await fetch(https://sheet.best...)
const data = await response.json()
validate(data) // Clean data to remove null key values
return data
} catch (e) {
console.log('Fetch error with getMeetingData()')
}
}
const filterMeetings = async (filters) => {
meetings = await fetchMeetingData()
meetings.forEach((meeting) => {
meeting.time2 = moment(meeting.time, ["h:mm A"]).format("HHmm")
})
let today = moment().format("dddd").toString()
let hour = moment().format('HHmm').toString()
let filteredMeetings = meetings.filter(function (matches) {
if (document.querySelector('#select-day').selectedIndex === 0 && filters.searchText === '') {
return matches.day === today &&
moment(matches.time, ["h:mm A"]).format("HHmm") > hour
} else {
return true
}
})
Here's what I've tried:
const fetchMeetingData = async () => {
console.log('api hit')
try {
const response = await fetch(https://sheet.best...)
const data = await response.json()
validate(data) // Clean data to remove null key values
sessionStorage.setItem('meetingData', JSON.stringify(data)) // added this line
return data
} catch (e) {
console.log('Whoa! Fetch error with getMeetingData()')
}
}
I'm not really sure where to go from here, or if this is even the correct approach. My noob instinct was to do something like this, which didn't work.
savedMeetingData = sessionStorage.getItem('meetingData')
const getSavedMeetingData = async () => {
if (savedMeetingData) {
meetings = savedMeetingData
return meetings
} else {
fetchMeetingData()
meetings = await data
return meetings
}
const filterMeetings = async (filters) => {
meetings = await getSavedMeetingData() // replaces call to fetchMeetingData
meetings.forEach((meeting) => {
meeting.time2 = moment(meeting.time, ["h:mm A"]).format("HHmm")
})
I'm not sure if that's exactly the code I was trying but it's close. The problem was the API was still getting hit, even though the data was stored successfully to sessionStorage.
I'd really appreciate some help and/or suggestions on how to clarify this question.
SOLUTION:
Based on answer from #Christian
// StackOverflow Q/A
async function getMeetingData() {
const preLoadedData = sessionStorage.getItem('meetingData')
if(!preLoadedData) {
try {
const response = await fetch('https://sheet.best...')
const data = await response.json()
validate(data)
sessionStorage.setItem('meetingData', JSON.stringify(data))
console.log('api hit')
return data
} catch (e) {
console.log('Whoa! Fetch error with getMeetingData()')
}
} else {
console.log('no api hit!!!')
return JSON.parse(preLoadedData)
}
}
async function getSavedMeetingData() {
const meetings = await getMeetingData()
return meetings
}
const filterMeetings = async (filters) => {
meetings = await getSavedMeetingData()
meetings.forEach((meeting) => {
meeting.time2 = moment(meeting.time, ["h:mm A"]).format("HHmm")
})
If you could be more explicit on what exactly did not work it would be great :) (did not save data in sessionStorage?, could not retrieve it?, etc...). Anyway, maybe you could try something like this and see if it helps:
async function getSavedMeetingData() {
const meetingData = await getMeetingData();
}
async function getMeetingData() {
const preloadedData = sessionStorage.getItem('meetingData');
if (!preloadedData) {
try {
const response = await fetch('https://myapiurl.com/');
const data = validate(response.json());
sessionStorage.setItem('meetingData', JSON.stringify(data));
return data;
} catch (e) {
console.log('Whoa! Fetch error with getMeetingData()');
}
} else {
return JSON.parse(preloadedData);
}
}
One more reminder (just in case), keep in mind you are saving this to sessionStorage, so if you close the tab do not expect to have the information saved, in that case you should use localStorage.

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.

Categories