Returning array from promise is undefined - javascript

I have a function that calls the iTunes API, and I return an array of objects from that. My main function calls this (and other promises) and waits for all promises to complete. This works, however, for the iTunes API promise the array returned is always "undefined".
My promise:
function getiTunesMusic() {
var options = {
uri: "https://itunes.apple.com/lookup?id=62820413&entity=song&limit=10",
json: true
}
retrieve(options) // This does a GET request
.then(repos => {
var result = repos.results
result.shift() // I get the array of results, minus the first result
console.log(result) // This prints out the full array of song objects
return result
})
.catch((error) => {
return null
})
}
My code waiting for the promises completion block:
Promise.all(promises)
.then(objects => {
var music = objects[0]
console.log("music", objects[0]) // This prints out "music undefined"
profile.music = music
}
The weird thing is when I print out the iTunes api result that I'm returning in the promise, it prints fine. However, in the promise completion block it's always undefined. How do I solve this?

You are not returning the promise from the function, hence, the default return undefined is returned
Try
return retrieve(options) // this returns the promise
in place of
retrieve(options)

Related

Question about asynchronous JavaScript with Promise

Here I have a function that takes an array of string that contains the user names of github accounts. And this function is going to return an array of user data after resolving. There should be one fetch request per user. and requests shouldn’t wait for each other. So that the data arrives as soon as possible. If there’s no such user, the function should return null in the resulting array.
An example for the input would be ["iliakan", "remy", "no.such.users"], and the expected returned promise after resolving would give us [null, Object, Object], Object being the data that contained info about a user.
Here is my attempt to solve this question.
function getUsers(names) {
return new Promise(resolve => {
const array = [];
const url = "https://api.github.com/users/";
const requests = names.map(name => {
const endpoint = `${url}${name}`;
return fetch(endpoint);
});
Promise.all(requests).then(reponses => {
reponses.forEach(response => {
if (response.status === 200) {
response.json().then(data => {
array.push(data);
});
} else {
array.push(null);
}
});
resolve(array);
});
});
}
It does work, i.e. returning an array [null, Object, Object]. And I thought it fulfilled the requirements I stated above. However, after looking at it closely, I felt like I couldn't fully make sense of it.
My question is, look at where we resolve this array, it resolved immediately after the forEach loop. One thing I don't understand is, why does it contain all three items when some of the items are pushed into it asynchronously after the json() is finished. what I mean is, in the case where response.status === 200, the array is pushed with the data resolved from json(), and I would assume this json() operation should take some time. Since we didn't resolve the array after json() operation is finished, how come we still ended up with all data resolved from json()?
Promise.all(requests).then(reponses => {
reponses.forEach(response => {
if (response.status === 200) {
response.json().then(data => {
array.push(data); <--- this should take some time
});
} else {
array.push(null);
}
});
resolve(array); <--- resolve the array immediately after the `forEach` loop
});
});
It looks to me like the array we get should only have one null in it since at the time it is revolved, the .json() should not be finished
You're right, the result is pushed later into the array.
Try to execute this:
const test = await getUsers(['Guerric-P']);
console.log(test.length);
You'll notice it displays 0. Before the result is pushed into the array, its length is 0. You probably think it works because you click on the array in the console, after the result has arrived.
You should do something like this:
function getUsers(names) {
const array = [];
const url = "https://api.github.com/users/";
const requests = names.map(name => {
const endpoint = `${url}${name}`;
return fetch(endpoint);
});
return Promise.all(requests).then(responses => Promise.all(responses.map(x => x.status === 200 ? x.json() : null)));
};
You should avoid using the Promise constructor directly. Here, we don't need to use it at all.
const url = "https://api.github.com/users/";
const getUsers = names =>
Promise.all(names.map(name =>
fetch(url + name).then(response =>
response.status === 200 ? response.json() : null)));
getUsers(["iliakan", "remy", "no.such.users"]).then(console.log);
The Promise constructor should only be used when you're creating new kinds of asynchronous tasks. In this case, you don't need to use the Promise constructor because fetch already returns a promise.
You also don't need to maintain an array and push to it because Promise.all resolves to an array. Finally, you don't need to map over the result of Promise.all. You can transform the promises returned by fetch.
The thing is that because json() operation is really quick, especially if response data is small in size it just has the time to execute. Second of all as objects in JavaScript passed by reference and not by value and Array is a object in JavaScript, independently of execution time it'll still push that data to the array even after it was resolved.

Returning the value of promise inside a promise

I have a few promises that need to complete before the function is returned. I have one promise that retrieves a profile. If the profile is of type artist I want to the get some json data using the itunesId property on that profile, then I want to return from the promise and access both the profile value and the json data. If the profile is of type fan then after the profile has been retrieved, it should just return the profile.
I'm trying something like this, but I know it is wrong, I just don't know how to make it work. Currently when I print out music it says it's undefined. I know the getiTunesMusic function does correctly return an array of objects.
var promises = []
var profilePromise = userRef.get().then(doc => {
if (doc.exists) {
var profile = doc.data()
profile.id = doc.id
if (type === UserType.artist) {
if (profile.hasOwnProperty("itunesId")) {
return [profile, getiTunesMusic(profile.itunesId)]
} else {
return profile
}
} else {
return profile
}
} else {
throw new Error("Profile doesn't exist")
}
})
promises.push(profilePromise)
// Later on
Promise.all(promises)
.then(objects => {
var profile = objects[0]
var music = objects[1]
console.log(music) // Undefined
})
As far as I can tell, getiTunesMusic(id) returns a Promise. So the result in this case is an array like this: [profile, Promise].
What you need instead is to chain your inner promise like this:
return getiTunesMusic(profile.itunesId).then(music => [profile, music]);
Returning a Promise from the callback passed to next results in a Promise which resolves only when both promises resolve, and whose result is the result of the inner Promise. In this case, [profile, music] which is what you intend.

Get Values from array of resolved Promises

I am using axios.all to call loop through an array of items and make a get request for each so that I can save their data in the correct order. Right now I have an array of Promises, all of which are being resolved with the correct data, and the callback function for when all of these are done is being triggered.
Now I just need to loop through the Promises and save them their values, but I don't know how to access their values!
let promises = [];
for (let report of response.data.res.body.result) {
let dto2 = {
customerId: state.member.customerId,
reportToken: report.reportToken
}
promises.push(axios.post('http://localhost:3001/api/pullReport', dto2));
}
console.log("promises", promises);
axios.all(promises).then(function() {
console.log("done!");
promises.forEach(function(res) {
console.log("res", res);
// commit('SAVE_REPORT', res.value);
})
// resolve('Reports saved.');
});
Here's what each promise looks like when it is consoled in the forEach loop :
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Object <<<<<<<<<<< NEED THIS
I just need to be able to call the commit('SAVE_REPORT') with the PromiseValue Object, but I don't know what to pass in! I've tried res.value, res.val, res.promiseValue... Is there a secret to this?
axios.all creates a new promise, which will resolve with an array of the results. So to interact with those results, you just need to add a parameter to the function in the .then block:
axios.all(promises).then(function(results) {
results.forEach(function(res) {
commit('SAVE_REPORT', res.value);
});
});

Using async await when mapping over an arrays values

I am trying to use the new async/await in conjunction with mapping over an array of values.
However I am a bit confused, which should have the await keyword infront of it, in this specific scenario.
There isnt much information regarding this topic yet.
I am trying to simply map() over and array of values, which are used in an Axios get() request, which then returns a then() value, which gets added to the array returned by the map() function.
This is my code right now:
async function get_prices (arrayOfDates, crypto, FIAT) {
// I tried this way (return value can be found below)
let arry_of_prices = arrayOfDates.map(async function(date){
let a = await fetch_data(date, crypto, FIAT)
return a
});
// And this way (return value can be found below)
let arry_of_prices = await arrayOfDates.map(date => fetch_data(date, crypto, FIAT));
}
const fetch_data = (timestamp, crypto, FIAT) => {
axios.get(`https://min-api.cryptocompare.com/data/pricehistorical?fsym=${crypto}&tsyms=${FIAT}&ts=${timestamp}`)
.then(function (response) {
return Object.values(response.data).map(key => Object.values(key))[0][0]
})
.catch(function (error) {
console.log(error);
});
}
let arr = [1468965600, 1469052000, 1469138400,1469484000]
get_prices(arr, "BTC", "USD").then(arr => console.log(arr))
The first try returned 4 resolved promises, but the values were undefined.
Whereas the second return an array with 4 undefined.
Does anyone have an idea of how this could be structured, so that the map() waits until the function is resolved and then adds the value to the array?
map doesn't know anything about promises, so when you use an async callback with map, you'll get an array of promises, not values. If you then want a promise that will resolve when all of those promises have resolved, you can use Promise.all.
However, in your case, you don't need an async function, since you're using fetch_data in the callback. You need to modify fetch_data so it returns the promise from axios.get (and remove the catch handler):
const fetch_data = (timestamp, crypto, FIAT) => {
return axios.get(`https://min-api.cryptocompare.com/data/pricehistorical?fsym=${crypto}&tsyms=${FIAT}&ts=${timestamp}`)
.then(function (response) {
return Object.values(response.data).map(key => Object.values(key))[0][0]
});
}
(This is one of the rules of promises: If you're returning the promise, you don't handle rejections from it [unless you want to convert them into different rejections, or if you can usefully convert them to resolutions instead]. If you don't return the promise, you do handle rejections.)
Now you already have a promise, so you use that in the map (no need for async):
async function get_prices (arrayOfDates, crypto, FIAT) {
let arr_of_price_promises = arrayOfDates.map(function(date){
let a = fetch_data(date, crypto, FIAT)
return a
});
return Promise.all(arr_of_price_promises);
}
Notice I removed the await from in front of fetch_data. We return fetch_data's promise directly, then wait for them all.
Or with an arrow function:
async function get_prices (arrayOfDates, crypto, FIAT) {
let arr_of_price_promises = arrayOfDates.map(date => fetch_data(date, crypto, FIAT));
return Promise.all(arr_of_price_promises);
}

bluebird - promise chain return unexpected "true" value

I'm writing an atom-shell app using ReactJs. Therefore I wrote 2 simple node.js-modules. The first fetches an XML api and returns an array of objects. The second is a simple datastore, which should save the fetched dataset. Both modules are async utilizing bluebird.
Now I have the following scenario:
var Promise = require('bluebird');
store.get()
.tap(print) //#1: []
.then(function (data) {
if (data.length) {
return Promise.resolve(data); //#6: ? also true in the second run ?
} else {
return xmlApi.get()
.tap(print) //#2: [...]
.then(store.insert) // <-- this returns an array with inserted indices
.tap(print) //#3: [...]
-then(store.write) // <-- this returns all stored objects as array
.tap(print) //#4: true <-- ? this is, what I doesn't understand... ?
}
})
.tap(print) //#5: true
.then(function (data) {
//Under normal circumstances, I would set ReactJs state from data array...
})
.catch(handleError)
.done();
My problem is to understand, why beyond step #3 everything resolves to true instead of arrays with values? When testing the libraries in pure node, everything is fine. But also in the second run, when the store holds data, the promise resolves to true...
UPDATE:
store.write
var write = Promise.promisify(require('fs').writeFile)
Store.prototype.write = function () {
return write(this.filename, JSON.stringify(this.data))
.bind(this)
.then(function () {
return Promise.resolve(this.data);
});
};

Categories

Resources