I started working on an android app. I am at the point that i need to use an API to get some info. I am using an API for movies so it contains id, title and some other info. My problem is that in this URL i don't hav every information that i need so i have to make another fetch for every movie to get the additional info but as i noticed first JS runs the code in the first fetch ( without the nested fetch ) and then it runs the nested fetch. This way some info are not displayed on the screen.
fetch(movie)
.then(function(response) { return response.json(); })
.then(function(responseObject) {
movies.value = responseObject;
for(var i=0;i<movies.value.results.length;i++)
{
movies.value.results[i].poster_path = poster+movies.value.results[i].poster_path;
var info = "http://api.themoviedb.org/3/movie/"+movies.value.results[i].id+"?api_key";
fetch(info)
.then(function(response) { return response.json(); })
.then(function(responseObject) {
moreInfo.value = responseObject;
if(j < movies.value.results.length)
{
movies.value.results[j].runtime = moreInfo.value.runtime;
console.log("print 1 "+movies.value.results[j]);
j++;
}
});
console.log("print 2 "+movies.value.results);
}
});
what i am trying to do here is add from the second fetch the runtime to my movies Observable object. A sortened version of the result :
print 2 : movies.value.results
print 1 : movies.value.results[i]
The problem as i said is that the code inside the first fetch is executed without executing the nested fetch and then the nested fetch executes. Sorry if i did something really bad but i just started developing on android so please show me mercy :D
*Sorry for not explaining about my j variable. It is defined as zero above fetch(movie). The only reason i use it is because i inside fetch(info) is always at the max number i cang get from movie.value.results.length and because i need to pass the runtime at the specific movie.value.result[j]. So i have a counter that increases only when the fetch(info) is executed.
This snippet might have some other problems too, like pointed out in the comments, but I think I know where the main confusion comes here.
The problem as i said is that the code inside the first fetch is executed without executing the nested fetch and then the nested fetch executes.
This is not true. Or, at least the order of console logs doesn't imply that. The order of execution inside the outer then-callback is
fetch(info) This statement starts fetching the data
.then(function(response){ ... }) Register a callback to execute when the promise gets resolved. Note that as fetching is asynchronous, the callback function does not get called right away. It will get called later, when the data has finished loading. Meanwhile, synchronous execution of the call stack continues.
.then(function(responseObject) { ... }) Same thing here. We create a function to run when the data is ready (if ever). Meanwhile the statements outside that function can still be evaluated.
console.log("print 2 "+movies.value.results); Now here comes the first log statement. The JavaScript execution does not halt to wait for the fetch to finish. That's the whole idea of asynchronous programming.
Now, some time passes and finally, the data has finished loading.
return response.json(); The Promise returned from the fetch gets fulfilled because the operation was successful. It then runs the callback that you provided earlier.
moreInfo.value = responseObject;
if(j < movies.value.results.length)
{
movies.value.results[j].runtime = moreInfo.value.runtime;
console.log("print 1 "+movies.value.results[j]);
j++;
}
This is the second callback we created and same thing applies here. Now that the data is finally ready, this can be executed. I'm not sure about the whole j thing, but apparently the execution will eventually get to the console.log("print 1 "+movies.value.results[j]); statement.
The important thing to understand here is that whether or not the 'print 1' statement was in the source code above the 'print 2' line is irrelevant. The 'print 1' was scheduled for later, namely, when the second fetch is done.
Fixed version
var info = //URL
fetch(info)
.then(function(response) { return response.json(); })
.then(function(responseObject) {
moreInfo.value = responseObject;
...
console.log("print 1 "+movies.value.results[j]);
...
})
.then(function(){
console.log("print 2 "+movies.value.results);
});
Now it could also be that I totally misunderstood the question. But if this was about asynchronicity, you should also check out the MDN Promise docs linked above and this SO question about asynchronicity in Node.js, which also applies to other JavaScript environments.
Related
I am having a bit of trouble understanding why my code is not working. I am trying to read data from firebase inside a react-native project. I can read it just fine, but I cannot set the data to any variables.
This is my code here.
let tmp;
let userRef = firebase.firestore().collection("Users");
userRef.doc(this.state.FirstName).get().then((document) => {
tmp = document.data().FirstName;
alert(tmp);
})
.catch((errorMsg) => {
alert(errorMsg);
})
alert("tmp Data: " + tmp);
};
The problem is that if I alert tmp inside of the function it shows the FirstName variable as expected. But when I alert tmp outside of the function, it shows undefined. I just cannot seem to wrap my head around why this is not working, if anyone could tell me what I'm doing wrong here, I would much appreciate it.
This is totally normal. It happens because if you put the alert outside the block, then it will get executed before the block, with tmp being uninitialized. The code that gets the FirstName from the database (the get() function) executes some code in another thread and your original thread continues without waiting for it to finish. When that another thread finishes execution, the code inside the block gets executed. You can verify this behavior by adding alerts before, inside, and after the block to see the order of the execution. To know more, read about asynchronous operations and promises.
Why all of this? Why does get() execute some code in another thread? Briefly, because it uses the network to access the Firebase database and it may take some time before getting a response back. If get() executes the 'networking code' in the same calling thread, then calling it from the main thread (the UI thread) will make your UI unresponsive until the response returns back. So, instead, get() dispatches the 'networking code' to another thread and returns a promise object immediately even before the 'networking code' finishes execution. You use that promise object to specify what you want to do with the result whenever it arrives. This way, the calling thread continues execution and does not need to wait, and in the case where the calling thread is the UI thread (which is usually the case), it means that your UI is always responsive.
I am working with this while loop and it is not working. I decided to use the Google Chrome debugger and I saw that the code inside is not being executed.
All the time it checks the condition, starts the first line of the code inside, and goes back again to check the condition.
It is a NodeJS server and I am using the Spotify API.
app.get('/process', ensureAuthenticated, function (req, res) {
let init_array = text.split(" ");
let modtext = init_array;
while (init_array.length != 0) {
spotifyApi.searchTracks(modtext.join(" "))
.then(function (data) {
try {
console.log(data.body.tracks.items[0].name);
for (let i = 0; i < modtext.length; i++) {
init_array.shift();
}
modtext = init_array;
} catch (err) {
console.log("No song");
modtext.pop();
}
});
}
res.redirect('/');
});
This question is best understood by understanding how node.js uses an event loop. At its core, node.js runs your Javascript in a single thread and it uses an event loop in order to manage the completion of things outside that single thread such as timers, network operations, file operations, etc...
Let's first start with a very simple while() loop:
let done = false;
setTimeout(() => {
done = true;
}, 100);
while(!done) {
// do whatever you want here
}
console.log("done with while loop"); // never gets called
At first blush, you would think that the while loop would run for 100ms and then done would be set to true and the while loop would exit. That is not what happens. In fact, this is an infinite while loop. It runs and runs and runs and the variable done is never set to true. The console.log() at the end never runs.
It has this issue because setTimeout() is an asynchronous operation and it communicates its completion through the event loop. But, as we described above, node.js runs its Javascript as single threaded and only gets the next event from the event loop when that single thread finishes what it's doing. But, the while can't finish what it's doing until done gets set to true, but done can't get set to true until the while loop finishes. It's a stand-off and the while loop just runs forever.
So, in a nutshell, while any sort of loop is running, NO asynchronous operation ever gets its result processed (unless it's using await inside the loop which is something different). Asynchronous operations (or anything that uses the event loop) has to wait until the current running Javascript is done and then the interpreter can go back to the event loop.
Your while loop has the exact same issue. spotifyApi.searchTracks() is an asynchronous operation that returns a promise and all promises communicate their results via the event queue. So, you have the same standoff. Your .then() handler can't get called until the while loop finishes, but your while loop can't finish until the .then() handler gets called. Your while loop will just loop infinitely until you exhaust some system resource and your .then() handlers never get a chance to execute.
Since you haven't included code in your request handler that actually produces some result or action (all it appears to do is just modify some local variables), it's not obvious what exactly you're trying to accomplish and thus how to better write this code.
You appear to have N searches to do and you're logging something in each search. You could do them all in parallel and just use Promise.all() to track when they are all done (no while loop at all). Or, you can sequence them so you run one, get its result, then run another. Your question doesn't give us enough info to know what the best option would be.
Here's one possible solution:
Sequence the operations using async/await
Here the request handler is declared async so we can use await inside the while loop. That will suspend the while loop and allow other events to process while waiting for the promise to resolve.
app.get('/process', ensureAuthenticated, async function (req, res) {
let init_array = text.split(" ");
let modtext = init_array;
while (init_array.length != 0) {
try {
let data = await spotifyApi.searchTracks(modtext.join(" "));
console.log(data.body.tracks.items[0].name);
for (let i = 0; i < modtext.length; i++) {
init_array.shift();
}
modtext = init_array;
} catch (err) {
console.log("No song");
modtext.pop();
}
}
res.redirect('/');
});
The reason you're only seeing one line execute is because it's asynchronous. When you call an asynchronous function, it returns immediately and continues to do its work in the background. Once it's done, it calls another function (a "callback") and passes the results of the function to that. That's why your code has to go inside of a call to then() rather than just being on the next line of code.
In this case, spotifyApi.searchTracks() returns a Promise. Once the Spotify API has completed the search, the function in then() will run.
Lets use async/await to solve this problem, I have no clue what data you get in a text but I think it is good example to understand a concept of asynchronous processing.
app.get('/process', ensureAuthenticated, async function (req, res, next) {
try {
const modtext = text.split(" ");
const promises = modtext.map(item => spotify.searchTracks(item));
const response = await Promise.all(promises);
response.forEach(data => {
// if you use lodash, simply use a get function `get(data, 'body.tracks.items[0].name')` => no need to check existence of inner attributes
// or use something like this
if (data && data.body && data.body.track && data.body.tracks.items && data.body.tracks.items[0]) {
console.log(data.body.tracks.items[0].name);
} else {
console.log('No song');
}
});
res.redirect('/');
} catch(err) {
next(err)
}
});
For me much simpler and cleaner code, again, i dont know what is structure of your text attribute and logic behind it, so maybe you will have to make some changes.
I created a function that makes a series of API calls through a Promise like this
//userApiList is a variable with an array of links
return Promise.all(userApiList.map(url =>{
var a = $.getJSON(url);
//userData is a global variable
userData.push(a);
return a;
}));
I want to both save the data to a variable for later use and return it right away to be iterated into html with jquery. Everything loads up perfectly as expected, however when I go to access the data in the variable I get all "undefined" properties. I console.logged the variable and it shows the data is there, I click the buttons well after the data is fetched so it's not an async issue. here's an example of one of the functions
$("#load_online").click(function(){
var users = [];
for(var i = 0; i < userData.length; i++){
var status = userData[i].status;
if(status !== null || status !== undefined){ users.push(userData[i]); }
}
$("#result_frame").empty();
//loadUsers() iterates the data into html
loadUsers(users);
});
I tried console.log(userData[i].status) just to see what the results would be and I got 200 as a response when it's suppose to be null' or the title of an episode being streamed on the channel.
The problem is I thought the responseJSON: field is what's always returned for use, as I've never walked into this issue before. This time it seems the whole object is being read therefore userData[i].status was reading the status: property a layer up rather than inside the responseJSON: object. I tried thinkering with getting a response out of userData[i].responseJSON.status and that returned undefined for each object. Does anybody readily see what I'm doing wrong? here's a CodePen of the overall project if you need a closer look at things.
You're not pushing the data into your array, you're pushing a jQuery jqXHR object (which is thenable, e.g., Promise-like) into it (that's what the return value of $.getJSON is). The reason the Promise.all result works is that Promise.all waits for those thenables to resolve.
It's quite likely you don't want to have a userData global at all. Instead, have whatever code needs the data get it from a resolution handler on the the promise returned by Promise.all.
But if you really want to populate userData, wait for resolution:
return Promise.all(userApiList.map($.getJSON))
.then(results => {
userData.push(...results);
return results;
});
Note that userData won't have the data until the ajax requests complete (of course).
The above won't add the results to userData until they're all avaialble. You also have the option of populating it as you go:
return Promise.all(userApiList.map(url => $.getJSON(url).then(result => {
userData.push(result);
return result;
})));
But it's important to remember the results will be added over time, not instantaneously, as the requests complete.
If you wait for all of them, Promise.all will ensure they're in the same order as the promises you giave it; if you add them as you go, they may not be in that order (because you're pushing as each one completes, and an earlier one may complete after a later one).
agreed with the upper answer,
this may work too,
return userApiList.map(url => { $.getJSON(url).done(function(results){
userData.push(results)
});})
I have a promise that returns data and I want to save that in variables. Is this impossible in JavaScript because of the async nature and do I need to use onResolve as a callback?
Can I somehow use this (e.g. wrap it with async/await):
const { foo, bar } = Promise.then(result => result.data, errorHandler);
// rest of script
instead of this?
Promise.then(result => {
const { foo, bar } = result.data;
// rest of script
}, errorHandler);
Note: Bluebird library is used instead of native implementation, and I can't change from Promise to asnyc/await or Generators.
NO you can't get the data synchronously out of a promise like you suggest in your example. The data must be used within a callback function. Alternatively in functional programming style the promise data could be map()ed over.
If your are OK using async/await (you should it's awesome) then you can write code that looks synchronous yet retain the asynchronicity of a promise (see #loganfsmyth comments).
const { foo, bar } = await iAmAPromise.then(result => result.data);
Overall since you are already using ES6 I assume you are also using a transpiler. In which case you should definitely give async/await a try.
Just be sure to weight in the decision that as today they are not yet a ratified specification.
While you can get a value from an awaited Promise inside an async function (simply because it pauses the function to await a result), you can't ever get a value directly "out" of a Promise and back into the same scope as the code that created the Promise itself.
That's because "out of" would mean trying to take something that exists in the future (the eventually resolved value) and putting it into a context (synchronous variable assignment) that already happened in the past.
That is, time-travel. And even if time-travel were possible, it probably wouldn't be a good coding practice because time travel can be very confusing.:)
In general, if you find yourself feeling like you need to do this, it's good sign that you need to refactor something. Note that what you're doing with "result => result.data" here:
Promise.then(result => result.data, errorHandler);
// rest of script
..is already a case of you working with (literally, mapping over) the value by passing it to a function. But, assuming that "// rest of script" does something important related to this value, you probably want to continue mapping over the now updated value with yet another function that then does something side-effect-y with the value (like display the data on the screen).
Promise
.then(({ data }) => data)
.then(data => doSomethingWithData(data))// rest of script
.catch(errorHandler);
"doSomethingWithData" will be called (if it's ever called) at some unknown point in the future. Which is why it's a good practice to clearly encapsulate all that behavior into a specific function and then hook that function up to the Promise chain.
It's honestly better this way, because it requires you to clearly declare a particular sequence of events that will happen, explicitly separated out from the first run through all of your application code's execution.
To put it another way, imagine this scenario, hypothetically executed in the global, top-level scope:
const { foo, bar } = Promise.then(result => result.data, errorHandler);
console.log(foo);
//...more program
What would you expect to happen there? There are two possibilities, and both of them are bad.
Your entire program would have to halt and wait for the Promise to execute
before it could know what "foo" & "bar" would... nay, might be. (this is
what "await," inside an async function, does in fact do: it pauses
the entire function execution until the value is available or an the error is thrown)
foo and bar would just be undefined (this is what actually
happens), since, as executed synchronously, they'd just be
non-existent properties of the top-level Promise object (which is not itself a "value,"
but rather a quasi-Monadic wrapper around getting an eventual value OR an error) which most
likely doesn't even contain a value yet.
let out; mypromise.then(x => out = x); console.log(out)
Only use this code when
you are debugging by hand,
and you know the promise has already succeeded
Behaviour of this code:
While the promise has not resolved yet, out will be undefined.
Once the promise resolves to a failure, an error is thrown.
When the promise resolves to success, (which may be after the console.log), the value of out will change from undefined to the Promise result — maybe in the middle of what you were doing.
In production code, or really any code that runs without you, the code that uses the Promise result should be inside the .then() callback, and not use some out variable. That way:
your code won't be run too early (when the result is still undefined),
and won't run too late (because you don't need 'I think sleeping for 10 seconds should do it' workarounds),
and won't erroneously run when the promise fails.
I have a solution of getting this value "out" if you will. This is a method at backend for uploading multiple files to AWS S3 which must be dealt asynchronously. I also need the responses from S3, so I need the values out of the Promise:
async function uploadMultipleFiles(files) {
const promises = []; //Creating an array to store promises
for (i = 0; i < files.length; i++) {
const fileStream = fs.createReadStream(files[i].path)
const uploadParams = {
Bucket: bucketName,
Body: fileStream,
Key: files[i].filename
}
promises.push(s3.upload(uploadParams).promise()) //pushing each promise instead
//of awaiting, to enable for concurrent uploads.
}
await Promise.all(promises).then(values => {
console.log("values: ", values) //just checking values
result = values; //storing in a different variable
});
return result; //returning that variable
}
The key lines in context with the issue being discussed here are these :
await Promise.all(promises).then(values => {
console.log("values: ", values) //just checking values
res = values; //storing in a different variable
});
return res; //returning that variable
But of course we have to also await in the function that will be calling this :
const result = await uploadMultipleFiles(files);
All you need to do is to extract all you have in your promise by using a .then
yourFunction().then( resp => {
... do what you require here
let var1 = resp.var1;
let var2 = resp.var2;
...
.....
})
yourFunction() should return a Promise
How to Get A Value From A Promise
YES! You can extract value out of a promise!
Do NOT let anyone here say you cannot. Just realize any variable that stores your returned promise value will likely have a short delay. So if you have a JavaScript script page that needs that data outside of the Promise or async-await functions, you may have to create loops, interval timers, or event listeners to wait to grab the value after some time. Because most async-await-promises are REST calls and very fast, that wait would require just a quick while loop!
It is easy! Just set a variable (or create a function) that can access the value inside your async or promise code and store the value in an outside variable, object, array, etc you can check on. Here is a primitive example:
// I just created a simple global variable to store my promise message.
var myDelayedData = '';
// This function is only used to go get data.
// Note I set the delay for 5 seconds below so you can test the delay
const getData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('my promise data'), 5000);
});
}
// I like to create a second async function to get the data
// from the promise object and save the data to my global variable.
const processData = async () => {
let data = await getData();
// Save the delayed data to my global variable
myDelayedData = data;
}
// Start the data call from the promise.
processData();
// Open up your browser, hit F12 to pull up the browser devtools
// Click the "console" tab and watch the script print out
// the value of the variable with empty message until after
// 5 seconds the variable is assigned to the resolved promise
// and apears in the message!
// THAT IS IT! Your variable is assigned the promise value
// after the delay I set above!
// TEST: But let's test it and see...
var end = setInterval(function(){
console.log("My Result: " + myDelayedData);
if(myDelayedData !== ''){
clearInterval(end);
}
}, 1000);
// You should see this in devtools console.
// Each line below represents a 1 second delay.
My Result:
My Result:
My Result:
My Result: my promise data
Most people seeing this code will say "Then why use a Promise, just make a call for the data, pause, and update your application?" True: The whole point of a Promise is to encapsulate data processes inside the promise and take actions while the rest of the script continues.
But... you may need to output a result outside the Promise. You may have other global processes that need that data because it changes the state of the global application, for example. But at least you know you can get to that Promise data if you needed it.
I've got a pretty simple function like this inside my controller:
fetchUsers = function () {
UserService.getUsers().
success(function(data, status) {
$scope.users = data.users;
console.log($scope.users);
});
};
fetchUsers();
console.log($scope.users);
It's basically using a service to retrieve some data from an API, and the console.log inside of the function is working fine and sends an array to console of [Object Object] which I can open up and see all my users no problem. However, when I then put this outside of the function by calling fetchUsers() and running a console.log I simply get an empty array returned where [] is output to the console.
Can anyone think of anything that would cause this? I'm a bit baffled by it.
Ahh I see you've finally been hit by asynchronous code of javascript. Not everything runs in order.
For example if I have this piece of code
setTimeout(function(){
var test = 'hello';
console.log('first test : ' + test);
}, 1000)
console.log('second test : ' + test);
You'll notice that the second test will return nothing even though test is set earlier (in terms of line number). Now you may think, so what you set it to 1000 milli-seconds (but try the same code and set it to 0 seconds, you will see the same effect. This is due to the event loop which is used to manage asynchronous code - basically whatever is in your setTimeout is placed at the end of the priority, which means when the second console log is called - test is not defined yet.
Why asynchronous you ask? The thing with browsers is UI and javascript is run on the same thread, so imagine if the setTimeout got stuck there and waited for 1 whole second, nothing in your UI would work - it would freeze.
With that said, another important usage of asynchronous code is http requests to a server. These requests can take a variant of time which means if you use synchronous code, your UI would freeze up. Imagine facebook - which is constantly retrieving data from its servers.
Going back to your code, this method below retrieves data from the server
UserService.getUsers().
success(function(data, status) {
$scope.users = data.users;
console.log($scope.users);
});
The stuff inside the success function is again asynchronous, so whatever you put after that will run straight away and whatever is inside the success function will run once your 'promise' or request has been fulfilled.
Hence you might have
fetchUsers();
console.log($scope.users);
But note that $scope.users is set after this console.log
UserService.getUsers() is returning a promise which will resolve in future as it is executed in async with your controller code so your console.log($scope.users) in your controller is most likely being executed before the API call returns and success callback is executed.
Put a breakpoint in browser developer tools on both console.log() statement and you should observe this behavior.
Here is a pretty simple explanation of promises in general
Your code is running properly. It is the asynchronous nature of the request that is causing the confusion. Because its asynchronous, the success callback is actually executed last, after your 2nd console.log() call.
Make $scope.users global by initializing it outside of the function perhaps? I also threw the function inside the getUsers method:
$scope.users = [];
fetchUsers = function () {
UserService.getUsers(function(data, status) {
$scope.users = data.users;
console.log($scope.users);
})
};
fetchUsers();
console.log($scope.users);