So, i have a function in file that converts into bundler.js via webpack (devtool: 'eval-source-map'):
function getTable(tname, trowid) {
return get(ref(db, `table_name/${tname}/table_row_${String("0" + trowid).slice(-2)}`)).then((snapshot) => {
if (snapshot.exists()) {
console.log(snapshot.val()); // need to return this value
}
});
}
window.getTable = getTable;
and i need call this function in main.js smt like this:
for (i = 0; i < 69; i++) {
document.getElementById(i).value = window.getTable(tname, i);
}
I'd tried simply return this vale, do it global variable and more, but in result i'm still getting "undefined".
Instead of logging your value, return it to the Promise your getTable() function is returning with return snapshot.val() :
if (snapshot.exists()) {
return snapshot.val();
}
This makes it so getTable() returns a Promise that fulfills with the value that snapshot.val() returns. Now you have a few options. One is to use async/await on each promise returned by the getTable() function to "extract" the value from the Promise:
async function populateValues() {
for (let i = 0; i < 69; i++) {
document.getElementById(i).value = await getTable(tname, i);
}
}
populateValues();
This will call each getTable function one at a time, perform the asynchronous operation/code within it, wait for that to complete (ie: wait for the Promise getTable() returns to fulfill with a value), then update your element's .value. Once done, it will proceed to the next iteration, and it will do this 69 times. For something faster, you can kick off all your calls to getTable() in parallel, and then use Promise.all() to wait until all of them have been fulfilled:
async function populateValues() {
const promises = [];
for (let i = 0; i < 69; i++) {
promises.push(getTable(tname, i)); // kick off all your asynchronous calls (so that they run in parallel)
}
const tableValues = await Promise.all(promises); // wait for each async operation to finish, then iterate the data and update your UI
for(const [i, val] of tableValuese.entries()) { // you can use a standard `for` loop here to iterate your `tableValues` array if you'd like
document.getElementById(i).value = val;
}
}
populateValues();
Related
So I've read quite a few articles on the asyn nature of JS, so I'm kind of understanding what I'm doing wrong but I still don't understand how callbacks fix the asyn nature to allow you to retrieve a result. In my code, I am using the YouTube Data API to retrieve the video of movies that I am embedding in my project. I can get the video to console login to my function but I need that videoId outside of the function. How would I use a callback to extract that data?
Here is my code:
// Blank Variable to store videoId
var idForMovie = "";
async function getMovieTrailer() {
let resultAll = [];
for (var k = 0; k < 1; k++) {
let searchResults = await fetch("https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=1&q=" + encodeURIComponent(`${movieArray[k].Title} Trailer`)
+ "&key=" + apiKey);
let search = await searchResults.json();
resultAll.push(search);
}
for (var i = 0; i < resultAll.length; i++) {
var {items} = resultAll[i]; //object destructuring
console.log(items);
for (var j = 0; i < items.length; i++) {
var {videoId} = items[i].id; //object destructuring
console.log(videoId);
idForMovie = videoId;
}
}
//console.log(searchItems);
return idForMovie;
}
getMovieTrailer();
console.log(idForMovie);
Callbacks do not resolve that behaviour, if you do:
let variable;
object.onevent = (event) => variable = event
console.log(variable) // undefined
It's the same of doing:
let variable;
promise.then(result => variable = result)
console.log(variable) //undefined
This happens because the variable declaration and the console.log are placed in queue of the EventLoop synchronously hence executed sequentially, immediately, while both the callback mechanism and the promise mechanism are deferred.
If you want to do something relative to a global/upper-scoped variable, and you know that that variable will be changed as a consequence of an asynchronous task, you must necessarily do that inside the callback if you are using callbacks, or in the then() callback of a promise.
Consider two more things:
A callback based task, can easily be converted in Promise based:
object.onevent = (event) => // do something
becomes:
const promise = new Promise( resolve => object.onevent = resolve )
promise.then( event => // do something )
An async function as that one in your example, is just a function that returns a Promise, based on JavaScript generators, so inside of it it "looks" synchronous, but it all happens asynchronously:
const asynchronousFn = async () => {
const something = await somethingAsync()
doSomethingSynchronous()
return something
}
is the same of:
const asynchronousFn = () => somethingAsync().then(something => {
doSomethingSynchronous()
return something
})
The only real reason why Promises and then async/await were introduced in ECMA was to avoid Callbacks Hell phenomenum.
There are patterns that help you to manage this kind of scenarios, one of this is the Observable Pattern, and there are tools that help us doing that ( observables ) : https://rxjs.dev/guide/observable
But this is all another matter!
NOTE: There is an active stage4 proposal to introduce ES modules that are totally asynchronous. This means that you will be able to do something like:
let variable;
const somethingThatChangesVariableAsynchronously = async () => variable = "Something"
const somethingThatUsesVariable = () => console.log(variable)
await somethingThatChangesVariableAsynchronously()
somethingThatUsesVariable() // Something
This means that the whole module is asynchronous but you can write a sort of synchronous code inside of it as you do inside async functions.
https://github.com/tc39/proposal-top-level-await
You have to wait your getMovieTrailer() to return, then assign idForMovie to the returned value. For this you will need another async function, because await is only available within async functions
// Blank Variable to store videoId
var idForMovie = "";
async function getMovieTrailer() {
let resultAll = [];
for (var k = 0; k < 1; k++) {
let searchResults = await fetch("https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=1&q=" + encodeURIComponent(`${movieArray[k].Title} Trailer`)
+ "&key=" + apiKey);
let search = await searchResults.json();
resultAll.push(search);
}
for (var i = 0; i < resultAll.length; i++) {
var {items} = resultAll[i]; //object destructuring
console.log(items);
for (var j = 0; i < items.length; i++) {
var {videoId} = items[i].id; //object destructuring
console.log(videoId);
idForMovie = videoId;
}
}
//console.log(searchItems);
return idForMovie;
}
async function nextFunction() {
idForMovie = await getMovieTrailer();
console.log(idForMovie);
}
I'm trying to resolve this problem, I have a function that calls two other function that will push elements inside array. My problem is that in this way the first function function1 when it has finished calculating, it returns its value and the code continues. As soon as the second function finishes, it returns its value and the rest of the code is executed again.
What I would like to do is that the first function function1(..) is called as soon as it ends the second function function2(..) is called and when both arrays are full the code goes on. How can I do?
Thanks so much
async initialFunction(){
( async () => {
await function1(arrayPopulated1);
await function2(arrayPopulated2);
}) ();
// Code that use: newArray1 and newArray2 to write in the DB
}
async function1(array1){
for(let i = 0; i < array1.length; i++){
//operation there
// value = somedata
newArray1.push(value)
}
}
async function1(array2){
for(let i = 0; i < array2.length; i++){
//operation there
// value = somedata
newArray2.push(value)
}
}
EDIT:
The problem is that after function1 finishes its value returns to InitialFunction continue the code of this function and write in the db. It then returns the value of function2 to InitialFunction continues the code of this function and writes again to the DB.
I would like function1 to finish its for by writing to the newArray1 array, function2 to write the values to the newArray2 array and when both have been populated then InitialFunction writes to the db.
If you want both promises to complete before continuing, you can wrap both of the promises in a Promise.all.
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
Checkout MDN link
You don't have to wrap the promises again in another async function
async initialFunction(){
const newArray1 = [];
const newArray2 = [];
await function1(arrayPopulated1);
await function2(arrayPopulated2);
// Save to db
}
async function1(array1){
for(let i = 0; i < array1.length; i++){
newArray1.push(value)
}
}
async function1(array2){
for(let i = 0; i < array2.length; i++){
newArray2.push(value)
}
}
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
The community reviewed whether to reopen this question 3 months ago and left it closed:
Duplicate This question has been answered, is not unique, and doesn’t differentiate itself from another question.
I am running an event loop of the following form:
var i;
var j = 10;
for (i = 0; i < j; i++) {
asynchronousProcess(callbackFunction() {
alert(i);
});
}
I am trying to display a series of alerts showing the numbers 0 through 10. The problem is that by the time the callback function is triggered, the loop has already gone through a few iterations and it displays a higher value of i. Any recommendations on how to fix this?
The for loop runs immediately to completion while all your asynchronous operations are started. When they complete some time in the future and call their callbacks, the value of your loop index variable i will be at its last value for all the callbacks.
This is because the for loop does not wait for an asynchronous operation to complete before continuing on to the next iteration of the loop and because the async callbacks are called some time in the future. Thus, the loop completes its iterations and THEN the callbacks get called when those async operations finish. As such, the loop index is "done" and sitting at its final value for all the callbacks.
To work around this, you have to uniquely save the loop index separately for each callback. In Javascript, the way to do that is to capture it in a function closure. That can either be done be creating an inline function closure specifically for this purpose (first example shown below) or you can create an external function that you pass the index to and let it maintain the index uniquely for you (second example shown below).
As of 2016, if you have a fully up-to-spec ES6 implementation of Javascript, you can also use let to define the for loop variable and it will be uniquely defined for each iteration of the for loop (third implementation below). But, note this is a late implementation feature in ES6 implementations so you have to make sure your execution environment supports that option.
Use .forEach() to iterate since it creates its own function closure
someArray.forEach(function(item, i) {
asynchronousProcess(function(item) {
console.log(i);
});
});
Create Your Own Function Closure Using an IIFE
var j = 10;
for (var i = 0; i < j; i++) {
(function(cntr) {
// here the value of i was passed into as the argument cntr
// and will be captured in this function closure so each
// iteration of the loop can have it's own value
asynchronousProcess(function() {
console.log(cntr);
});
})(i);
}
Create or Modify External Function and Pass it the Variable
If you can modify the asynchronousProcess() function, then you could just pass the value in there and have the asynchronousProcess() function the cntr back to the callback like this:
var j = 10;
for (var i = 0; i < j; i++) {
asynchronousProcess(i, function(cntr) {
console.log(cntr);
});
}
Use ES6 let
If you have a Javascript execution environment that fully supports ES6, you can use let in your for loop like this:
const j = 10;
for (let i = 0; i < j; i++) {
asynchronousProcess(function() {
console.log(i);
});
}
let declared in a for loop declaration like this will create a unique value of i for each invocation of the loop (which is what you want).
Serializing with promises and async/await
If your async function returns a promise, and you want to serialize your async operations to run one after another instead of in parallel and you're running in a modern environment that supports async and await, then you have more options.
async function someFunction() {
const j = 10;
for (let i = 0; i < j; i++) {
// wait for the promise to resolve before advancing the for loop
await asynchronousProcess();
console.log(i);
}
}
This will make sure that only one call to asynchronousProcess() is in flight at a time and the for loop won't even advance until each one is done. This is different than the previous schemes that all ran your asynchronous operations in parallel so it depends entirely upon which design you want. Note: await works with a promise so your function has to return a promise that is resolved/rejected when the asynchronous operation is complete. Also, note that in order to use await, the containing function must be declared async.
Run asynchronous operations in parallel and use Promise.all() to collect results in order
function someFunction() {
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(asynchonousProcessThatReturnsPromise());
}
return Promise.all(promises);
}
someFunction().then(results => {
// array of results in order here
console.log(results);
}).catch(err => {
console.log(err);
});
async await is here
(ES7), so you can do this kind of things very easily now.
var i;
var j = 10;
for (i = 0; i < j; i++) {
await asycronouseProcess();
alert(i);
}
Remember, this works only if asycronouseProcess is returning a Promise
If asycronouseProcess is not in your control then you can make it return a Promise by yourself like this
function asyncProcess() {
return new Promise((resolve, reject) => {
asycronouseProcess(()=>{
resolve();
})
})
}
Then replace this line await asycronouseProcess(); by await asyncProcess();
Understanding Promises before even looking into async await is must
(Also read about support for async await)
Any recommendation on how to fix this?
Several. You can use bind:
for (i = 0; i < j; i++) {
asycronouseProcess(function (i) {
alert(i);
}.bind(null, i));
}
Or, if your browser supports let (it will be in the next ECMAScript version, however Firefox already supports it since a while) you could have:
for (i = 0; i < j; i++) {
let k = i;
asycronouseProcess(function() {
alert(k);
});
}
Or, you could do the job of bind manually (in case the browser doesn't support it, but I would say you can implement a shim in that case, it should be in the link above):
for (i = 0; i < j; i++) {
asycronouseProcess(function(i) {
return function () {
alert(i)
}
}(i));
}
I usually prefer let when I can use it (e.g. for Firefox add-on); otherwise bind or a custom currying function (that doesn't need a context object).
var i = 0;
var length = 10;
function for1() {
console.log(i);
for2();
}
function for2() {
if (i == length) {
return false;
}
setTimeout(function() {
i++;
for1();
}, 500);
}
for1();
Here is a sample functional approach to what is expected here.
ES2017: You can wrap the async code inside a function(say XHRPost) returning a promise( Async code inside the promise).
Then call the function(XHRPost) inside the for loop but with the magical Await keyword. :)
let http = new XMLHttpRequest();
let url = 'http://sumersin/forum.social.json';
function XHRpost(i) {
return new Promise(function(resolve) {
let params = 'id=nobot&%3Aoperation=social%3AcreateForumPost&subject=Demo' + i + '&message=Here%20is%20the%20Demo&_charset_=UTF-8';
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() {
console.log("Done " + i + "<<<<>>>>>" + http.readyState);
if(http.readyState == 4){
console.log('SUCCESS :',i);
resolve();
}
}
http.send(params);
});
}
(async () => {
for (let i = 1; i < 5; i++) {
await XHRpost(i);
}
})();
JavaScript code runs on a single thread, so you cannot principally block to wait for the first loop iteration to complete before beginning the next without seriously impacting page usability.
The solution depends on what you really need. If the example is close to exactly what you need, #Simon's suggestion to pass i to your async process is a good one.
I have a standard JavaScript project. It is mostly synchronous with a few callbacks. I needed to use a 3rd party library to simplify things. The issue is that this library is based on an async approach and Promises. I have never really used this approach before.
I have a situation where I just need something = get_info(); The code is all very modular, so there are multiple function calls stacked up. The problem is this get_info is async. I just can't figure out how to use this async function withing my project without having to rewrite the whole thing.
Edit: Here is an example of similar code
function my_class() {
this.do_something( data ) {
if ( this.get_data() == data ) {
do_the_thing();
}
}
this.get_data = function() {
return library.get_info( arguments ); //can't do this, returns a promise
}
}
var stuff = new my_class();
for ( var i = 0; i < len; i++ ) {
stuff.do_something( i )
}
Again, the goal is to not rewrite this entire part of the application. Thoughts?
There is no need to promisify every function that gets called in the chain. You can call either synchronous or asynchronous functions as much as you want within the chain of the promises. All you have to think about is where to wait for the resolved value.
Your code isn't far off from working, a simple addition of a .then() block makes it work.
Working example below:
function my_class() {
this.do_something(data) {
//Call the promise-returning function
this.get_data()
.then((resolvedData) => {
//Wait for promise resolution
//and compare the resolved value to 'data'
if (resolvedData === data) {
do_the_thing();
}
})
.catch();
}
this.get_data = () => {
return library.get_info(arguments);
}
}
var stuff = new my_class();
for (var i = 0; i < len; i++) {
stuff.do_something(i)
}
How does it work?
do_something(i) is called
this.get_data() is called from inside the do_something() function.
When the promise that get_data() returns has been resolved, execution continues into then .then() block.
The resolvedData is the resolved value from the get_data() promise.
Compare the values, and continue.
As you can see, this does not take into consideration that the for-loop will continue even though the call to stuff.do_something(i) hasn't completed.
It is unclear in your question if you allow them all to run in parallel (like now) or if you need that they run in sequence.
If you want to to know if all calls are done, you can let do_something() return a promise and then wait for them all to resolve using Promise.all(), like below
function my_class() {
this.do_something(data) {
//Call the promise-returning function
//Note that we return the same promise - no need to create a new one!
return this.get_data()
.then((resolvedData) => {
//Wait for promise resolution
//and compare the resolved value to 'data'
if (resolvedData === data) {
do_the_thing();
}
})
.catch();
}
this.get_data = () => {
return library.get_info(arguments);
}
}
var stuff = new my_class();
//
var promises = [];
for (var i = 0; i < len; i++) {
//Push each promise to the array
promises.push(stuff.do_something(i));
}
Promise.all(promises)
.then(() => {
console.log("All promises have been resolved!")
})
.catch(() => {
//Handle errors
});
function my_class() {
this.do_something( data ) {
return new Promise(function(resolve,reject){
this.get_data().then(function(dataReceiced){
//reading promise retuned by get_data();
if(data == dataReceiced){
do_the_thing(); //need to promisify this also to use then();
resolve('Done');
} else { reject('Error'); }
});
});
}
}
this.get_data = function() {
return library.get_info( arguments ); //can't do this, returns a promise
}
}
i=0; // keep i global
var stuff = new my_class();
function doStuff(){
if(i < len){ mainStuff(); }
}
function mainStuff(){
stuff.do_something( i ).then(function(){
i++;
doStuff();
});
};
doStuff(); // to start process.
You need to promisify every function involved in the chain ..
Something like this..
Not sure about the complete scenario , code will look something like this.
I have a case where I want to do something once 10 async calls have completed
let i = 0;
let array = [];
do {
this.service.getSomething(i).subscribe(response => {
array[i] = response;
});
} while (i < 10);
// how can I know when the 10 async calls have completed?
How can I achieve this?
This depends on whether you know the async operations (read Observables/Promises) beforehand or not.
For example if you can compose an array of Observables then the easiest way is to use forkJoin:
let observables = [ ... ];
Observable.forkJoin(observables)
.subscribe(results => /* whatever */);
Otherwise, you can just mergeMap them into a single chain a listen only to the complete signal:
Observable.range(1, 10) // or whatever
.mergeMap(i => /* return Observable here */)
.subscribe(undefined, undefined, () => console.log('all done'));
'The Rx way' is to use forkJoin:
const requestParams = [0,1,2,3,4,5,6,7,8,9];
const requests = requestParams.map(i => this.service.getSomething(i));
Observable.forkJoin(requests).subscribe(reponseArray => alldone(responseArray));
You have to make your loop asynchronous, so that an iteration will only occur when the next response is available. Here is how you can do that:
(function loop(arr) {
if (arr.length >= 10) return alldone(array); // all done
this.service.getSomething(arr.length).subsribe(response => {
loop(array.concat(response)); // "recursive" call
});
})([]); // <--- pass empty array as argument to the loop function
function alldone(arr) {
console.log(arr);
}
The loop function is immediately invoked with an empty array as argument. When you get the response, you call that function again, now with the extended array, ...etc. Once you have 10 responses, you call another function that will do something with the final array.
As you can see, I chose to eliminate the variable i, since arr.length has the same value.
Note that this kind of asynchronous processing can also be done with promises and some recent features like async and await. You might want to look into that. Here is an example
You can just count responses in separate variable, and check it before continue:
let i = 0;
let array = [];
var finishedCnt=0;
do {
this.service.getSomething(i).subsribe(response => {
array[i] = response;
finishedCnt++;
if(finishedCnt>=10) {
// all requests done, do something here
}
});
} while (i < 10);