How to avoid .then() hell in javascript? [duplicate] - javascript

This question already has answers here:
Avoiding callback hell in promises using nodejs
(2 answers)
Closed 3 years ago.
I'm having a hard time to sequence my API calls. so I used then() chaining to sequence them in order. All the API and the refresh token are Promises/Async. It's working but is there a cleaner/fancier/shorter way to this without using async/await because my parent function is not async. I don't fully understand the behavior of .then() and async/await
Here is the code inside the parent function:
refreshToken().then(token => {
let request = {} //format request
return axios.post(`${youtubeUrl}/upload/youtube/v3/videos?access_token=${token}&part=contentDetails`, request) //upload video
})
.then(uploadResponse => {
let uploadResponse = {}; //format uploadResponse
refreshToken().then(token => { //refresh the token again
return axios.put(`${youtubeUrl}?access_token=${token}`, uploadResponse) //update existing video
})
.then(updateResponse => {
let updateResponse = {}; //format updateResponse
axios.post(`${BasePath}/v1/videos`, updateResponse, headers)
.then(postResponse => {
if (postResponse.data.response === 'success') {
return dispatch(receivePostsData(postResponse.data))
} else if (postResponse.data.response === 'failed') return dispatch(receivePostsData(postResponse.data))
})
})
})
.catch(error => {
return dispatch(receivePostsData(error))
})

With aysnc await you can convert your callback hell to this:
important notes are:
async keyword before the function allows to use await
To handle exceptions you need to use try catch block.
async function uploadToYoutube() {
try {
let token = await refreshToken();
let request = {}
const youtubeUploadResponse = await axios.post(`${youtubeUrl}/upload/youtube/v3/videos?access_token=${token}&part=contentDetails`, request);
let uploadResponse = {};
token = await refreshToken();
const youtubeUpdateResponse = await axios.put(`${youtubeUrl}?access_token=${token}`, uploadResponse);
let updateResponse = {};
let postResponse = await axios.post(`${BasePath}/v1/videos`, updateResponse, headers);
if (postResponse.data.response === 'success') {
return dispatch(receivePostsData(postResponse.data))
} else if (postResponse.data.response === 'failed') {
//??? why do you here act like a success?
return dispatch(receivePostsData(postResponse.data))
}
} catch (error) {
//??? why do you here act like a success?
return dispatch(receivePostsData(error))
}
}

If you are using ES6 and above you can use async await

Related

Combine async function result in separate function in more efficient way

I have two async functions, that separately work on their own - they each return data if I call them singularly, but I can't get a result from the second when I try and combine them. Basically I'm pinging an API and if the first function returns a result, use that data. If it returns empty, run the second function and use that data. I'm getting the data through a node proxy (again these work fine separately).
Function 1 that checks for a live event:
async function getLive(){
const response = await fetch("video-live");
const getLiveData = await response.json();
return getLiveData;
}
Function 2 that should run if Function 1 returns empty:
async function getArchived() {
const response = await fetch("video-archived");
const getArchivedData = await response.json();
return getArchivedData;
}
The final function that applies the logic:
function showVideo(getLiveData, getArchivedData) {
if( (getLiveData) == "" ) {
console.log('live'+getLiveData);
} else {
console.log('no live'+getArchivedData);
}
}
Then I call them like this:
getLive().then(showVideo);
The above returns 'no live' but not any data from the getArchivedData. How do I combine these in an elegant, efficient way? Can I combine both getLive and getArchived into one function with .then()?
Per #MichaelM's code:
async function getLive(){
const response = await fetch("video-live");
const getLiveData = await response.json();
return getLiveData;
}
async function getArchived() {
const response = await fetch("video-archived");
const getArchivedData = await response.json();
return getArchivedData;
}
async function showVideo() {
const liveData = await getLive();
if(liveData == "") {
console.log('live' + getLiveData);
} else {
const archivedData = await getArchived();
console.log('no live' + archivedData);
}
}
"Uncaught (in promise) ReferenceError: getLiveData is not defined"
Try rewriting your showVideo() function to use getLive() and getArchived() directly, instead of passing the results into showVideo(). Like this:
async function showVideo() {
const liveData = await getLive();
if(liveData == "") {
console.log('live' + liveData);
} else {
const archivedData = await getArchived();
console.log('no live' + archivedData);
}
}
Then you just call showVideo() without the .then() statement.

fetch constantly returns Promise {<pending>} [duplicate]

This question already has answers here:
How can I access the value of a promise?
(14 answers)
Async function returning promise, instead of value
(3 answers)
Closed 9 months ago.
I decided to create a special service aka ES6 class with needed functions to get data from API and then in index.js I could create an instance of the class and work with it. Unfortunately, when I try it, it always returns
Promise {<pending>}
and I don't really know what to do.
nytService.js:
export default class NYTService {
urlBase = "https://api.nytimes.com/svc/topstories/v2/";
category = "world";
apikey = *my api key* ;
async getNews(category = this.category) {
let url = `${this.urlBase}${category}.json?api-key=${this.apikey}`;
let res = await fetch(url)
let data = await res.json();
return data;
}
}
index.js:
import NYTService from "../services/nytService.js";
let nytService = new NYTService();
async function getNewsFinally() {
let res = await nytService.getNews();
return res;
}
console.log(getNewsFinally());
I did tried different things with the getNewsFinally function, did various .then chains, nothing helped
See if this tells you why:
async getNews(category = this.category) {
let url = `${this.urlBase}${category}.json?api-key=${this.apikey}`;
try {
let res = await fetch(url)
if (!res.ok) {
return res.statusText;
}
return await res.json();
} catch(err) {
console.error(err);
throw new Error(err);
}
}

variable value not changing inside axios then method [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed last year.
let found = false;
axios
.get(link)
.then((response) => {
response.data.map((element) => {
if (element.id_ticket.toString() === nbTicket) found = true;
});
console.log(found);
});
so im trying to get data from the api and see if the value of 'nbTicket' is in the returned data and that is done using the 'found' variable so if i log the 'found' variable value outside the .then method it stays false even if the value exists and when i do it iside the .then methods it gives the correct value
let found = false;
axios
.get(link)
.then((response) => {
response.data.map((element) => {
if (element.id_ticket.toString() === nbTicket) found = true;
});
});
console.log(found);
Your supposed to use async/await strategy, because console.log happens after Axios request:
async function getNbTicket(){
let found = false;
const { data } = await axios.get(link);
data.data.map((element) => {
if (element.id_ticket.toString() === nbTicket) found = true;
});
return found;
}
your console.log(found); is executing is executing before you fetch data from api
you can call it inside then or use async await if you want to call console.log(found); after you get data from api
This is normal behavior, because of the async and sync.
you have two options
first one is to wrap your code inside a async function
const checkIfTicketIsFound = async () => {
let found = false;
const { data } = await axios.get(link);
data.map((element) => {
if (element.id_ticket.toString() === nbTicket) found = true;
})
console.log(found)
return found
}
Or
you need to print inside the then chain
let found = false;
axios
.get(link)
.then((response) => {
response.data.map((element) => {
if (element.id_ticket.toString() === nbTicket) found = true;
});
console.log(found);
});

Async function returning promise but console.log does give out the result as wanted | NodeJS Own Module [duplicate]

This question already has answers here:
Async function returning promise, instead of value
(3 answers)
Closed 1 year ago.
I just wrote a simple and small API to define the gender of a person. It works via a get-request to a page which gives me back a JSON object.
This is the code of my module:
'use strict';
const https = require('https');
exports.getGender = function (name) {
return new Promise((resolve) => {
//send request to api
https.get(`https://api.genderize.io?name=${name}`, (resp) => {
var data = '';
//replace data
resp.on('data', (chunk) => {
data += chunk;
});
//define information on end of query
resp.on('end', () => {
var info = JSON.parse(data);
//check accuracy of gender
if (info.probability >= 0.8) {
//check gender
if (info.gender == 'male') {
resolve('Sehr geehrter Herr');
} else if (info.gender == 'female') {
resolve('Sehr geehrte Frau');
}
} else {
resolve('Sehr geehrte/r Frau / Herr');
}
});
});
});
};
And this is the call I do in the main file:
//get gender of person
async function getG(name) {
const data = await genderApi.getGender(name);
return data;
}
getG('Tim');
But the output is just Promise { <pending> }, If I replace the return data with a console.log(data) it just works fine and it shows me the output in the console but I really need it to return the value cause later on I use this function in a text back to the user
getG function is also async so you must use with await keyword or .then callback
const val = await getG('Tim');
// or
getG('Tim').then((val) => {
console.log(val)
})

Multiple awaits in function

I'm struggling a bit with JS promises.
I am using a library to pull data from Spotify that returns promises.
In my main function I can use an await to build an object from the response data and push it to an array (called nodes):
var nodes = [];
main();
async function main() {
var id = '0gusqTJKxtU1UTmNRMHZcv';
var artist = await getArtistFromSpotify(id).then(data => buildArtistObject(data));
nodes.push(artist);
When I debug here then all is good, nodes has my object.
However, when I introduce a 2nd await underneath to make another call:
nodes.forEach((node, i) => {
if (node.done == false) {
console.log(node.toString());
var related_artists = await getRelatedArtists(node.spotify_id);
I get the following error: SyntaxError: await is only valid in async function
I thought the first await statement would be resolved and the execution would continue until the next..?
Any help would be greatly appreciated.
EDIT
The other functions, if that helps, are just as follows:
function getArtistFromSpotify(id) {
let response = spotify
.request('https://api.spotify.com/v1/artists/' + id).then(function (data) {
return data;
})
.catch(function (err) {
console.error('Error occurred: ' + err);
return null;
});
return response;
}
function getRelatedArtists(id) {
let response = spotify
.request('https://api.spotify.com/v1/artists/' + id + '/related-artists').then(function (data) {
return data;
})
.catch(function (err) {
console.error('Error occurred: ' + err);
return null;
});
return response;
}
function buildArtistObject(data) {
var artist = {
node_id: nodes.length,
name: null,
genres: null,
popularity: null,
spotify_id: null,
done: false
}
artist.name = data.name;
artist.genres = data.genres;
artist.popularity = data.popularity > 0 ? data.popularity : 0;
artist.spotify_id = data.id;
return artist;
}
The code below has multiple problems.
var nodes = [];
main();
async function main() {
var id = '0gusqTJKxtU1UTmNRMHZcv';
var artist = await getArtistFromSpotify(id).then(data => buildArtistObject(data));
nodes.push(artist);
// ...
First of all, main mutates global scope nodes. Not only is this an antipattern even in synchronous code (functions should not rely on, or modify, global variable names; use parameters and return values instead), in asynchronous code, nodes will never be available for use anywhere but within main. See How do I return the response from an asynchronous call?.
Secondly, try to avoid combining then and await. It's confusing.
It's also a little odd that an array of nodes is used, yet only one artist is pushed onto it...
As for this code:
nodes.forEach((node, i) => {
if (node.done == false) {
console.log(node.toString());
var related_artists = await getRelatedArtists(node.spotify_id);
// ...
The error is self-explanatory. You must add async to the enclosing function if you want it to be asynchronous: nodes.forEach(async (node, i) => { // .... But that spawns a new promise chain per node, meaning future code that's dependent on the result won't be able to await all of the promises in the loop resolving. See Using async/await with a forEach loop. The likely solution is for..of or Promise.all.
While I'm not 100% sure what your final goal is, this is the general pattern I'd use:
async function main() {
const id = '0gusqTJKxtU1UTmNRMHZcv';
const data = await getArtistFromSpotify(id);
const artist = await buildArtistObject(data);
const nodes = [artist]; // odd but I assume you have more artists somewhere...
for (const node of nodes) {
if (!node.done) {
const relatedArtists = await getRelatedArtists(node.spotify_id);
}
}
/* or run all promises in parallel:
const allRelatedArtists = await Promise.all(
nodes.filter(e => !e.done).map(e => getRelatedArtists(e.spotify_id))
);
*/
// ...
}
main();
Since your code isn't runnable and some of the intent is unclear from the context, you'll likely need to adapt this a bit, so consider it pseudocode.
You have some misunderstandings of how to use promises -
let response = spotify
.request(url)
.then(function(data) { return data }) // this does nothing
.catch(function (err) { // don't swallow errors
console.error('Error occurred: ' + err);
return null;
})
return response
You'll be happy there's a more concise way to write your basic functions -
const getArtist = id =>
spotify
.request('https://api.spotify.com/v1/artists/' + id)
const getRelatedArtists = id =>
spotify
.request('https://api.spotify.com/v1/artists/' + id + '/related-artists')
Now in your main function, we can await as many things as needed. Let's first see how we would work with a single artist ID -
async function main(artistId) {
const artistData = await getArtist(artistId)
const relatedData = await getRelatedArtists(artistId)
return buildArtist(artistData, relatedData)
}
If you have many artist IDs -
async function main(artistIds) {
const result = []
for (const id of artistIds) {
const artistData = await getArtist(artistId)
const relatedData = await getRelatedArtists(artistId)
result.push(buildArtist(artistData, relatedData))
}
return result
}
Either way, the caller can handle errors as
main([693, 2525, 4598])
.then(console.log) // display result
.catch(console.error) // handle errors
Which is the same as -
main([693, 2525, 4598]).then(console.log, console.error)
The pattern above is typical but sub-optimal as the caller has to wait for all data to fetch before the complete result is returned. Perhaps you would like to display the information, one-by-one as they are fetched. This is possible with async generators -
async function* buildArtists(artistIds) {
for (const id of artistIds) {
const artistData = await getArtist(artistId)
const relatedData = await getRelatedArtists(artistId)
yield buildArtist(artistData, relatedData) // <- yield
}
}
async function main(artistIds) {
for await (const a of buildArtists(artistIds)) // <- for await
displayArtist(a)
}
main([693, 2525, 4598]).catch(console.error)

Categories