Javascript - Chain multiple Fetch promises - javascript

I have this method who performs 3 window.fetch
const API_URL = 'http://localhost:3000/'
, API = {
'getArtistLyric': artist => {
return fetch(`${API_URL}artist?name=${artist}`)
.then(res => res.json())
.then(res => {
const artistID = JSON.parse(res).message.body.artist_list[0].artist.artist_id;
console.log('Artist ID is:', artistID);
fetch(`${API_URL}artist/albums/?artist_id=${artistID}`)
.then(resp => resp.json())
.then(resp => {
const trackID = JSON.parse(resp).message.body.album_list[0].album.album_id;
console.log('Random Track ID is:', trackID);
fetch(`${API_URL}artist/album/songsnippet?track_id=${trackID}`)
.then(response => response.json())
.then(response => {
const lyricSnippet = JSON.parse(response).message;
console.log('Track Id lyric snippet is:', lyricSnippet);
})
.catch(err => {
console.error(err);
});
})
.catch(err => {
console.error(err);
});
})
.catch(err => {
console.error(err);
});
}
}
Now i want to call it like this
API.getArtistLyric('Prodigy').then(res).catch(err);
What's the best practice here?

If you want to make a chain requests it's better to use async/await :
async func(){
let response = await /* some request */
let res = await /* another request */
...
return results;
}
Here you can use try/catch syntax and wrap specific request :
try {
let response = await...
} catch ( exception) {
...
}
Also you can wrap a couple of requests.

(async() => {
const API_URL = 'http://localhost:3000/';
const API = {
getArtistLyric: async(artist) => {
try {
const res = await fetch(`${API_URL}artist?name=${artist}`);
const artistID = JSON.parse(res.json()).message.body.artist_list[0].artist.artist_id;
console.log('Artist ID is:', artistID);
const resp = await fetch(`${API_URL}artist/albums/?artist_id=${artistID}`);
const trackID = JSON.parse(resp.json()).message.body.album_list[0].album.album_id;
console.log('Random Track ID is:', trackID);
const response = await fetch(`${API_URL}artist/album/songsnippet?track_id=${trackID}`);
const lyricSnippet = JSON.parse(response.json()).message;
console.log('Track Id lyric snippet is:', lyricSnippet);
return lyricSnippet;
} catch (e) {
console.error(e);
}
}
}
try {
const art = await API.getArtistLyric('Prodigy');
console.log(art);
} catch (e ){
console.error(e);
}
})()

Check out this link regarding chaining promises: https://javascript.info/promise-chaining
Here's the core idea:
It looks like this:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1 return result * 2;
}).then(function(result) { // (***)
alert(result); // 2 return result * 2;
}).then(function(result) {
alert(result); // 4 return result * 2;
});
The idea is that the result is passed through the chain of .then
handlers.
Here the flow is:
The initial promise resolves in 1 second (*), Then the .then handler
is called (**). The value that
it returns is passed to the next .then handler (***) …and so on.

Related

Use a promise result from a fetch-api in an if statement of another function - JS

So I have this fetch-api
let test = () => fetch(usd_api).then(response => response.json())
.then(data => data.exchange_rates.dash_usd);
Console log
let console_test = () => {console.log(test())}
How can use this number [[PromiseResult]]: 134.445... in the following function where the number 150 is.
function main(){
fetch(txs_api).then(response => response.json()).then(function(data) {
var amount = data.txs[0].vout[1].value;
if(amount == 150){
// SUCCESS!
$('#modal').modal('hide');
console.log('requests stopped');
clearInterval(interval);
}
})
}
let test = () => fetch(usd_api)
.then(response => response.json())
.then(data => data.exchange_rates.dash_usd);
You want too keep using the same promise, if you want to log the result of your promise you need to wait for it to be completed:
// with .then()
test().then(console.log);
// or with await if you can:
console.log(await test());
I figured it out. Don't know if it's the best way to do it but it's working.
function main(){
fetch(usd_api).then(response => response.json()).then(function(data) {
var dash_price = data.exchange_rates.dash_usd;
fetch(txs_api).then(response => response.json()).then(function(data) {
var amount = data.txs[0].vout[1].value;
if(amount == dash_price){
// SUCCESS!
$('#modal').modal('hide');
console.log('requests stopped');
clearInterval(interval);
}
})
})
}
Do not return value.
Because it returns to below function
function(data) {
return data.exchange_rates.dash_usd
}
If you want to return your value to a variable you have to use Promise like below.
let test = new Promise((resolve, reject) => {
fetch(usd_api).then(response => response.json())
.then(function (data) {
resolve(data.exchange_rates.dash_usd)
});
})
async function demo(){
let console_test = await test
console.log(console_test )
}
demo()
Note : Do not forget to use async and await

Executing a statement after a fetch request

I have the following structure of code:
var cdict = new Map();
fetch("randomurl")
.then(res => res.json())
.then(data => {
for (const obj of data.result) {
// insert stuff into Map cdict
}
counter(0).then(res => {
console.log(cdict);
})
// ^ here after calling of counter i need to do stuff
});
const cbegin = 0;
const ccount = 10;
function counter(cnt) {
if (cnt < ccount) {
setTimeout( () => {
cnt++;
fetch(someurl)
.then(res => res.json())
.then(data => {
for (const obj of data.result) {
// do stuff
}
})
.catch(err => {
console.log(err);
});
counter(cnt);
}, 1000);
}
}
Here after execution of counter(0) call and all its fetch requests, I wish to execute a line console.log(cdict);
How can this be achieved? And is this proper way to call fetch requests with delay of 1 second?
Don't mix setTimeout event queue callbacks with promise-based code -
const sleep = time =>
new Promise(resolve => setTimeout(resolve, time))
async function main()
{ console.log("please wait 5 seconds...")
await sleep(5000)
console.log("thanks for waiting")
return "done"
}
main().then(console.log, console.error)
Don't write .then(res => res.json()) every time you need some JSON. Write it once and reuse it -
const fetchJSON(url, options = {}) =>
fetch(url, options).then(res => res.json())
async function main()
{ const data = await fetchJSON("path/to/data.json")
console.log("data received", data)
return ...
}
main().then(console.log, console.error)
Don't attempt to declare variables outside of Promises and modify them later. You cannot return the result of an asynchronous call. Asynchronous data needs to stay contained within the promise or you will be chasing hard-to-find bugs in your code -
async function main(urls)
{ const result = []
for (const u of urls) // for each url,
{ result.push(await getJSON(u)) // await fetch and append to result
sleep(1000) // wait 1 second
}
return result
}
const myUrls =
[ "foo/path/data.json"
, "another/something.json"
, "and/more/here.json"
]
main(urls)
.then(result => /* counter logic */)
.then(console.log, console.error)
Continue abstracting as you see fit -
// give reusable functions a name; use parameters for configurable behaviour
async function fetchAll(urls, delay = 100)
{ const result = []
for (const u of urls)
{ result.push(await getJSON(u))
sleep(delay)
}
return result
}
async function counter(urls)
{ const results = await fetchAll(urls) // use our generic fetchAll
const cdict = /* counter logic... */
return cdict
}
const myUrls =
[ "foo/path/data.json"
, "another/something.json"
, "and/more/here.json"
]
counter(urls).then(console.log, console.error)
As you can see async and await prevent nesting that occurs with the use of setTimeout or .then callbacks. If you use them correctly, your code remains flat and you can think about your code in a synchronous way.

What is the best way to handle paginated API in Javascript?

I am trying to consume an API which returns structure below.
{
data: [{...},{...},{...},{...},...],
nextUrl: "url_goes_here";
}
The pages end when the nextUrl is null. I want to collect all the elements in data into one array while going through the paginated responses. I tried the following code segment.
const getUserData = async (url) => {
const result = await fetch(url)
.then(res => res.json())
.then(res => {
dataList= [...dataList, ...res.data];
console.log(res.data)
if (res.nextUrl !== null) {
getUserData(res.nextUrl);
} else {
console.log("else ", dataList);
}
})
.catch(err => {});
return result;
}
The console.log can print the result. but I want to get all the data to get into a variable that can be used for further processing later.
Your approach using recursion isn't bad at all, but you're not returning the chunk of data (there's no value returned by your second .then handler). (You're also falling into the fetch pitfall of not checking ok. It's a flaw IMHO in the fetch API design.)
There's no need for recursion though. I'd be tempted to just use a loop:
const getUserData = async (url) => {
const result = [];
while (url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
const {data, nextUrl} = await response.json();
result.push(...data);
url = nextUrl;
}
return result;
};
But if you want to use recursion:
const getUserData = async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
const {data, nextUrl} = await response.json();
if (nextUrl) {
data.push(...await getUserData(nextUrl));
}
return data;
};
or
const getUserData = async (url, result = null) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
const {data, nextUrl} = await response.json();
if (!result) {
result = data;
} else {
result.push(...data);
}
if (nextUrl) {
result = await getUserData(nextUrl, result);
}
return result;
};

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);

Assign return value of function to variable

I kind of got the same question as has been asked here.
I've made a function:
const rewardStayingViewersOrNewcomers = () => {
fetch('https://tmi.twitch.tv/group/user/instak/chatters')
.then(parseJSON)
.then(r => {
let chatters = r.chatters;
viewerKeys = Object.keys(chatters); // [mods, viewers,...]
let ChattersPerRole = viewerKeys.map(role => {
return chatters[role].map(username => ({
username, role
}));
});
return flattenDeep(ChattersPerRole);
}).catch(err => {
console.log(`Error in fetch: ${err}`);
});
};
Why can't I assign that return value to my variable? The log of that variable returns undefined...
let viewersPerRole = rewardStayingViewersOrNewcomers();
setTimeout(() => console.log(viewersPerRole), 7000);
Bonus question, how could I easily wait for viewersPerRole to be filled with the data I'm waiting for because of the fetch? (so I don't have to use setTimeout())?
First of all, try returning something from the main function. There's no return in front on the fetch(...). Code should look like:
const rewardStayingViewersOrNewcomers = () => {
return fetch('https://tmi.twitch.tv/group/user/instak/chatters')
.then(parseJSON)
.then(r => {
let chatters = r.chatters;
viewerKeys = Object.keys(chatters); // [mods, viewers,...]
let ChattersPerRole = viewerKeys.map(role => {
return chatters[role].map(username => ({
username, role
}));
});
return Promise.resolve(flattenDeep(ChattersPerRole));
}).catch(err => {
console.log(`Error in fetch: ${err}`);
});
};
// now that you're returning a promise
rewardStayingViewersOrNewcomers()
.then(viewersPerRole => console.log(viewersPerRole))
If you're using Babeljs to transpile with stage3 enabled, have a look at async / await.

Categories

Resources