Promises returned data '<pending>' - javascript

my data which is fetched from youtube is using nodejs is -
[ { title:
'Dil Zaffran Video Song | Rahat Fateh Ali Khan | Ravi Shankar | Kamal Chandra | Shivin | Palak',
id: 'vChX3XZXqN4',
view: '28652353' },
{ title:
'Full Video: Akh Lad Jaave | Loveyatri | Aayush S|Warina H |Badshah, Tanishk Bagchi,Jubin N, ,Asees K',
id: 'e_vl5aFXB4Q',
view: '17328447'
}]
Now I want to search 5 related videos to each video
my api
getVids(videosID)
.then(result => {
console.log(result) //this is the data which I showed above
result.sort((a,b) => b.view - a.view);
return result;
})
.then(result => {
return result.map(function(el) {
// console.log(el);
let o = Object.assign({}, el);
o.relatedVideos = relatedVideos(el.id); //function is called below
return o;
});
})
.then(result => {
console.log(result);
})
.catch(err => {
console.log(err)
})
and api to 5 related Videos is given below
function relatedVideos(videoId) {
return new Promise((resolve, reject) => {
let urls = 'https://www.googleapis.com/youtube/v3/search?relatedToVideoId=' + videoId + '&key=' + API_KEY + '&part=snippet&type=video';
request(urls, (err, result) => {
const data = JSON.parse(result.body);
const relatedVideos = [];
data.items.forEach(related => {
relatedVideos.push({
title: related.snippet.title
});
if(relatedVideos.length === 5){
resolve(relatedVideos);
}
});
});
});
}
It is giving the output like
[ { title:
'Dil Zaffran Video Song | Rahat Fateh Ali Khan | Ravi Shankar | Kamal Chandra | Shivin | Palak',
id: 'vChX3XZXqN4',
view: '28655496',
relatedVideos: Promise { <pending> } },
{ title:
'Full Video: Akh Lad Jaave | Loveyatri | Aayush S|Warina H |Badshah, Tanishk Bagchi,Jubin N, ,Asees K',
id: 'e_vl5aFXB4Q',
view: '17334681',
relatedVideos: Promise { <pending> }
}]
How to solve this pending problem or how to wait so it gets full data.

It happens because you assign the promise returned by relatedVideos() to the property relatedVideos, you never actually wait for it to resolve. You need to await all of the promises before you proceed, this is most easily done with Promise.all(). Let's focus on this part of your code:
return result.map(function(el) {
// console.log(el);
let o = Object.assign({}, el);
o.relatedVideos = relatedVideos(el.id); //function is called below
return o;
});
What you need to do is to wait for each promise to resolve in order to get its resolved value before you assign it to the object. Then you also need to create a promise that doesn't resolve until all of those promises have resolved. This is what Promise.all() is for. Change the code mentioned above into this:
return Promise.all(result.map(function(el) {
// console.log(el);
let o = Object.assign({}, el);
return relatedVideos(el.id)
.then(function(relatedVideo) {
o.relatedVideos = relatedVideo;
return o;
});
}));
Please note that ALL of the promises mapped to Promise.all() needs to resolve in order for the chain to continue with the next then. If even one of those promises rejects or don't resolve at all it will never continue. You have a condition for your resolve() call in the relatedVideos() function. This implies that in some cases a promise might end up never resolving. You should always resolve or reject a promise, perhaps resolve(null) would be appropriate in your case.

This happens when the promise has not been resolved, so your resolve function isn't being called. It would probably be a good idea to test this without the below conditional.
if(relatedVideos.length === 5){
resolve(relatedVideos);
}

Related

Outside promise needs to wait for inner nested promise

I've this usecase wherein I wish to do the following :
Set metadata in indexDb
Iterate over an array of images
See if img is already set in indexDb
if yes, do nothing, if not, download the img
set the downloaded img (as blob) in indexDb
Raise all images processed event at the end
ads data :
[{
ETag:"",
S3URL:"",
duration:30,
filename:"",
linear-gradient:"",
status:"",
timerRequired:"yes"
}]
My code at the moment :
this.Tvlocalforage.setItem('meta', newMeta).then(() => { //Step 1
for (let idx in ads) { //Step 2
this.localforage.getItem(ads[idx]['filename']).then(blob => {
if(!blob){ //Step 3
LSPromise = imgSrcToBlob(ads[idx]['S3URL'], undefined, 'Anonymous', 1).then((blob) => { //Step 4
return this.localforage.setItem(ads[idx]['filename'], blob); //Step 5
});
LSPromises.push(LSPromise);
}
});
}
}).then(() => {
if(LSPromises.length) {
Promise.all(LSPromises).then((data) => {
this.TvLSkeyCount = LSPromises.length;
this.fireLoadAssetsEvent(); //Step 6
});
}
});
Problems I am facing :
After the promise for setting metadata is resolved, it straightaway goes to then() block and by that time LSPromises is null. Of course I understand that internal nested promises haven't been resolved yet.
Resolution I tried :
Return LSGetter promises and download images later. this did not work either.
Code I tried :
this.Tvlocalforage.setItem('meta', newMeta).then(() => {
for (let idx in ads) {
let p = this.Tvlocalforage.getItem(ads[idx]['filename']);
LSPromises.push({'promise' : p, 'filename' : ads[idx]['filename'], 'url' : ads[idx]['S3URL']});
}
}).then(() => {
if(LSPromises.length){
Promise.all(LSPromises.map(obj => {
obj['promise'].then(blob => {
if(!blob){
imgSrcToBlob(obj['url'], undefined, 'Anonymous', 1).resolve(blob => {
return this.Tvlocalforage.setItem(obj['filename'], blob);
});
}
});
})).then((data) => {this.fireLoadAssetsEvent();});
}
I tried 2 more ways to wrapper up and try to return promise.all of download step from inside & on trying to resolve that, return promise.all of set downloaded images to LS. But it did not work.
Get rid of the for() loop since idx will not be what you want it to be inside the promise callback since loop will complete before promises do
Can use map() instead to create the array using a closure
Something like:
this.Tvlocalforage.setItem('meta', newMeta).then(() => { //Step 1
let LSPromises = ads.map(ad => {
return this.localforage.getItem(ads[idx]['filename']).then(blob => {
if (!blob) { //Step 3
return imgSrcToBlob(ad['S3URL'], undefined, 'Anonymous', 1).then((blob) => { //Step 4
return this.localforage.setItem(ad['filename'], blob); //Step 5
});
}
return null
});
});
return Promise.all(LSPromises).then((data) => {
this.TvLSkeyCount = data.filter(o => o).length;
this.fireLoadAssetsEvent(); //Step 6
// not sure what needs to be returned here
});
});
There might be other errors, but there is a missing return:
this.Tvlocalforage.setItem('meta', newMeta).then(() => { //Step 1
for (let idx in ads) { //Step 2
LSPromises.push(this.localforage.getItem(ads[idx]['filename']).then(blob => {
if(!blob){ //Step 3
return /* added return */ imgSrcToBlob(ads[idx]['S3URL'], undefined, 'Anonymous', 1).then((blob) => { //Step 4
return this.localforage.setItem(ads[idx]['filename'], blob); //Step 5
});
// LSPromises.push(LSPromise);
}
}));
}
// }).then(() => {
if(LSPromises.length) {
return /* <<<=== */ Promise.all(LSPromises).then((data) => {
this.TvLSkeyCount = LSPromises.length;
this.fireLoadAssetsEvent(); //Step 6
});
}
});
If the promise returned from Promise.all() is not returned, the caller can not wait for it to complete.
I could not test this, but you should try to flatten the nesting, chaining the thens at the outermost level. You can use Promise.all even more in order to pass the ad value through the chain together with the resolved values:
this.Tvlocalforage.setItem('meta', newMeta).then(() => // Step 1
Promise.all(ads.map( ad => // Step 2
Promise.all(ad, this.localforage.getItem(ad.filename))
))
).then(blobs =>
blobs.filter( ([ad, blob]) => !blob ) // Step 3
).then(blobs =>
Promise.all(blobs.map( ([ad]) =>
[ad, imgSrcToBlob(ad.S3URL, undefined, 'Anonymous', 1)] // Step 4
))
).then(blobs =>
Promise.all(blobs.map( ([ad, blob]) =>
this.localforage.setItem(ad.filename, blob) // Step 5
))
).then(data => {
this.TvLSkeyCount = data.length;
this.fireLoadAssetsEvent(); // Step 6
});

Promise not waiting until request is finished [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I'm trying to chain promises together to make a series of requests. This is one of the few times I've used promises, so I'm not too sure what I'm doing. Hoping that someone can catch whats going on wrong. Basically, my first promise is working correctly, then the second function runs after the first promise is resolved, however, the third does not wait so the information I'm requesting is incomplete.
I can see that my functions are returning the correct information. So mainly my question is how do I format my promises so they each wait for each other to complete before running?
function getPaths(folderpath) {
return new Promise(function(resolve, reject) {
db.getPaths(folderpath, function(response) {
//get paths for files and folders
for (let i = 0; i < response.entries.length; i++) {
let entries = response.entries[i];
if (entries[".tag"] == "folder") {
folderPaths.push(response.entries[i].path_lower);
}
if (entries[".tag"] == "file") {
filePaths.push(response.entries[i].path_lower);
}
}
resolve();
});
});
}
function openFolders(folders) {
return new Promise(function(resolve, reject) {
for (let i = 0; i < folders.length; i++) {
db.getPaths(folders[i], function(response) {
for (let j = 0; j < response.entries.length; j++) {
let entries = response.entries[j];
if (entries[".tag"] == "file") {
filePaths.push(response.entries[j].path_lower);
}
}
//console.log(filePaths); //returns correct information
});
}
resolve();
});
}
getPaths("/path").then(function() {
openFolders(folderPaths);
}).then(function() {
console.log(filePaths); //returns incomplete information
});
You have a big problem here in bold
function openFolders(folders) {
return new Promise(function(resolve, reject) {
for (let i = 0; i < folders.length; i++) {
db.getPaths(folders[i], function(response) {
// ...
});
}
resolve();
});
}
You're running a synchrounous for loop around an asynchronous function call. The loop finishes iterating synchronously and then immediately calls resolve() - the code in the async handler (eg, filePaths.push(...)) does not run before the Promise is resolved
Your problems don't stop there though. You're using Promises unconventionally by modifying global state and then resolving and empty Promise - resolve() vs resolve(someValue). #FrankModica's answer has more to say about that.
As for remedies, I'd recommend you take a look at Promise.all - and in general, using Promises in a more conventional way
In the snippet below, I've mocked your db function to read from a fake in-memory database, fakedb. Then, I made a getPaths function which wraps db.getPaths but returns a Promise instead. Then I rewrite openFolders to create an array of Promises, pass them to Promise.all, then finally resolve the values I want
Worth noting: Promise.all will run your array of Promises in parallel - if this undesirable, you could run them in serial using .reduce and .then.
const db = {
getPaths: (path, k) => {
setTimeout(k, 50, fakedb[path])
}
}
function getPaths (path) {
return new Promise((resolve, reject) =>
db.getPaths(path, response => resolve(response)))
}
function openFolders (folders) {
return Promise.all(folders.map(getPaths))
.then(responses =>
responses.reduce((acc, {entries}) =>
acc.concat(entries.filter(x => x['.tag'] === 'file').map(x => x.path_lower)), []))
}
const fakedb = {
'cat': {
'entries': [
{ '.tag': 'dir', 'path_lower': './cat/.' },
{ '.tag': 'dir', 'path_lower': './cat/..' },
{ '.tag': 'file', 'path_lower': './cat/a' },
{ '.tag': 'file', 'path_lower': './cat/b' },
{ '.tag': 'file', 'path_lower': './cat/c' }
]
},
'dog': {
'entries': [
{ '.tag': 'dir', 'path_lower': './dog/.' },
{ '.tag': 'dir', 'path_lower': './dog/..' },
{ '.tag': 'file', 'path_lower': './dog/a' },
{ '.tag': 'file', 'path_lower': './dog/b' },
{ '.tag': 'file', 'path_lower': './dog/c' }
]
}
}
openFolders(['cat','dog']).then(console.log, console.error)
The operation I'm performing in openFolders might feel a little complex because it handles all transformations of the responses in a single .then handler - you could (optionally) separate the work into multiple .then calls which might result in a lighter cognitive load
function openFolders (folders) {
return Promise.all(folders.map(getPaths))
// get `.entries` of each response
.then(responses =>
responses.map(x => x.entries))
// flatten array of entries arrays into a single array
.then(arrOfEntries =>
arrOfEntries.reduce((acc, entries) =>
acc.concat(entries), []))
// only keep entries where `.tag` of each entry is `'file'`
.then(entries =>
entries.filter(x => x['.tag'] === 'file'))
// return `path_lower` of the resulting entries
.then(entries =>
entries.map(x => x.path_lower))
}
If I understand correctly, you are using some variables from the outer scope (folderPaths and filePaths). I'm not sure that's the best way to go about this, but I believe it will start working once you return the promise from openFolders. This way you wait until openFolders completes:
getPaths("/path").then(function() {
return openFolders(folderPaths);
}).then(function() {
console.log(filePaths);
});
But I'd recommend resolving the information required for the next call:
resolve(folderPaths);
And:
resolve(filePaths);
So your code can look more like:
getPaths("/path").then(function(folderPaths) {
return openFolders(folderPaths);
}).then(function(filePaths) {
console.log(filePaths);
});
Edit: Looks like you may have another issue mentioned by #naomik. If db.getPaths is async, in your openFolders function you may be resolving before all of the async calls in the loop complete. Unfortunately I don't have time right now to show the solution. Hopefully #naomik can!

Binding variable on redux dispatch ActionCreator

I have an array of promises, and I'm trying to push new promises into that array inside of another dispatch.then function, but it appears that the array is always out of scope
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params
let promises = []
versions.forEach((id) => {
// get the version info, and then pass it along
dispatch(ActionCreators.version({ id: id })).bind(promises).then((version) => {
promises.push(dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
})))
})
})
//
promises.push(dispatch(ActionCreators.configuration()))
promises.push(dispatch(ActionCreators.byRef({ language_tag })))
console.log(promises.length)
return Promise.all(promises)
}
},
I've tried a few different approaches, such as setting var that = this right before the dispatch inside of the versions loop, and what is shown here, trying to use .bind(promises) on the dispatch.
promises.length is always 2, (because of the two that are actually getting pushed at the bottom). I can console statements inside of the .then so I know it's getting executed, but the dispatches are not ending up in the promises array.
I could very well be thinking of the dispatch function in an incorrect way.
Any help would be appreciated!
The problem is that since you're adding the promises on then(), you have already returned the array by the time you're adding the promises. So they do get added, but too late.
Instead, try this:
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params;
let promises = [];
versions.forEach((id) => {
// get the version info, and then pass it along
promises.push(dispatch(ActionCreators.version({ id: id })).then((version) => {
return dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
}));
}));
});
//
promises.push(dispatch(ActionCreators.configuration()));
promises.push(dispatch(ActionCreators.byRef({ language_tag })));
console.log(promises.length);
return Promise.all(promises)
}
}

Wait for the end of a function to set state with UnderscoreJS under React Native

I want to do a fetch then update the markers state. The problem is the state is updating before my fetch ends (due to asynchronous I think). I think there would be a solution with UnderscoreJS using the _.after function to set the state after my fetch ends. I don't know how to do that. Any idea?
Here my code:
onRegionChange(region) {
let latitude = region.latitude;
let longitude = region.longitude;
let markers = [];
_.filter(this.props.stations, (v) =>
{
if (this.getDistanceFromLatLonInKm(latitude,longitude,v.position.lat,v.position.lng) < 1) {
fetch('https://api.jcdecaux.com/vls/v1/stations/' + v.number + '?contract=Paris&apiKey=3a9169028401f05f02bcffd87f4a3963dcd52f63')
.then((response) => response.json())
.then((station) => {
console.log("station", station);
markers.push({
number: station.number,
coordinate: {
latitude: station.position.lat,
longitude: station.position.lng
},
title: station.name,
description: station.address,
banking: station.banking,
bonus: station.bonus,
status: station.status,
bike_stands: station.bike_stands,
available_bike_stands: station.available_bike_stands,
available_bikes: station.available_bikes,
last_update: station.last_update
});
})
.catch((error) => {
console.warn(error);
});
}
}
)
this.setState({
markers: markers
})
console.log("markers", this.state.markers);
}
Your mixing synchronous and asynchronous ideas and they don't mix well.
You're better off converting the synchronous values into promises (same thing the fetch is doing).
First I break out the drivel parts into functions. This cleans up the actual code and lowers the complexity.
function stationFetchUrl(station) {
return `https://api.jcdecaux.com/vls/v1/stations/${station.number}?contract=Paris&apiKey=3a9169028401f05f02bcffd87f4a3963dcd52f63`;
}
function convertStationData(station) {
return {
number: station.number,
coordinate: {
latitude: station.position.lat,
longitude: station.position.lng
},
title: station.name,
description: station.address,
banking: station.banking,
bonus: station.bonus,
status: station.status,
bike_stands: station.bike_stands,
available_bike_stands: station.available_bike_stands,
available_bikes: station.available_bikes,
last_update: station.last_update
};
}
function stationNearRegion(region, station) {
const distance = this.getDistanceFromLatLonInKm(
region.latitude,
region.longitude,
stationStub.position.lat,
stationStub.position.lng
);
return (distance < 1);
}
Next in the event handler I map over the stations and convert them to either null (in the case of the distance being >= 1) or fetch the new station data from the server (fetch()).
This results in an array of either promises or nulls. I use Underscore's .compact() to remove the null (same as filter did). Then I have an array of promises which I pass to Promise.all which waits till all the promises in the array resolve. When they do it passes the result of those promises (as an Array) to the .then() function which I dutifully use this.setState() on.
onRegionChange(region) {
const markerPromises = _.chain(this.props.stations)
.map(stationStub => {
if (stationNearRegion.call(this, region, stationStub) {
return fetch(stationFetchUrl(station))
.then(response => response.json())
.then(convertStationData);
}
})
.compact()
.value();
Promise.all(markerPromises)
.then(markers => this.setState({markers}))
.catch(error => console.log(error.toString()));
});
Since the onRegionChange is an event handler it would not return a promise. Instead we handle a possible error(s) by logging them in the .catch() block.
References
Chaining in Underscore.js (miniarray.com)
Promise - JavaScript | MDN (developer.mozilla.org)
You're Missing the Point of Promises (blog.domenic.me)

how to correctly use promises to return 2 different results [duplicate]

This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 7 years ago.
I am trying to use Promises in JavaScript using ES6 to return data from 2 methods from an object, which would in production call out to an endpoint.
My MovieApi object I have 2 promises which I want to return firstly a list of movies, and then 1 movie by id. I want to use promises to avoid callback hell. I am following the approach listed as the answer to the question Arent promises just callbacks but I am clearly doing it wrong, as I have the error findMovie is not defined
let movieData = [
{
id: '1011',
name: 'Gleaming the cube',
year: "1989"
},
{
id: "1012",
name: "Airborne",
year: "1989"
}
]
let MovieApi = {
findMovie: function(id) {
return new Promise(function(resolve, reject) {
if(id === undefined) reject(Error('incorrect movie id'));
let movie = ''
for (let m of movieData) {
if (m.id.toLowerCase() === id.toLowerCase()) {
movie = m
break
}
}
resolve(movie)
});
},
findAllMovies: function() {
return new Promise(function(resolve, reject) {
if(movieData === undefined) reject(Error('Could not find any movies'))
resolve(movieData)
});
}
}
Call the movie promise like this... but I get an error trying to call my second .then() method
MovieApi.findAllMovies()
.then( function (movies){
return findMovie(req.params.id)
}).then(function(movie){
let MovieStore = { movie: movie, movies: movies }
}).catch(function(error) {
console.error("Failed!", error);
});
Is there a way to get out of callback hell here, or will I just have to make another call to the MovieApi object, essentially having the same readability issue as if I were using callbacks.
The problem is return findMovie(req.params.id), your findMovie is a property of the MovieApi object so
MovieApi.findAllMovies()
.then( function (movies){
return MovieApi.findMovie(req.params.id)
})
Demo: Fiddle

Categories