I currently have some jQuery code that looks a bit like this:
for ( i = 0; i < limitVar; i++ ) {
doAjaxStuff(i);
}
function doAjaxStuff( i ) {
Here we make a SYNCHRONOUS ajax call, sending i.
}
The ajax call needs to be synchronous - one isn't fired until the last one is done.
As synchronous JS is deprecated, I want to move this code to use promises. How would I achieve this? I've been unable to find an example that close enough fits this situation.
You don't do synchronous ajax in the browser (well technically, you can in some circumstances, but it's a really bad idea to do so because it locks up the browser during the ajax call).
Instead, you redesign your loop so that it only carries out the next ajax call when the previous one is done which means you have to loop manually, you can't use a for loop. Since your code is pseudo-code (you don't show the real ajax operation), I'll use a jQuery ajax example, but you can substitute any ajax function you have as long as it either returns a promise or uses a callback to signal when its done.
The general idea is that you create a function for your ajax call and you use the completion callback from that to increment your index and then run the next iteration of your loop.
function runLoop(data) {
var i = 0;
function next() {
if (i < data.length) {
return $.ajax(data[i]).then(function(data) {
++i;
return next();
});
else {
// all done with loop
}
}
return next();
}
// call it like this
runLoop(someArray).then(function() {
// all done here
});
If you don't have an array of data, but just want a loop index:
function runLoop(limitVar) {
var i = 0;
function next() {
if (i < limitVar) {
return $.ajax(something_with_i_in_it).then(function(data) {
++i;
return next();
});
else {
// all done with loop
}
}
return next();
}
// call it like this
runLoop(theLimit).then(function() {
// all done here
});
If your limitVar is not large and there is no other logic involved in deciding whether to continue the loop, you can also use a little bit simpler pattern if you have an ajax function that returns a promise:
function runLoop(limitVar) {
var p = Promise.resolve();
for (var i = 0; i < limitVar; i++) {
p = p.then(function(prevResult) {
return someAjax(i);
});
}
return p;
}
// call it like this
runLoop(theLimit).then(function() {
// all done here
});
If you aren't using ajax functions that return a promise, then it's only a few lines of code to wrap your function with one that does and then you can more easily use these design patterns.
Process the array using some separate function. Each time you take off another element from array, then process it and when it's done call the function again. If there is no more item in list then the whole process is done.
var listOfRequests = ...;
new Promise( function( resolve, reject ) {
requestNext();
function requestNext() {
if ( !listOfRequests.length ) {
return resolve();
}
var next = listOfRequests.shift();
doAjaxStuff( next, reject, requestNext );
}
} )
doAjaxStuff( request, errCallback, doneCallback ) {
...
}
this is a pretty simple pattern:
var queue = Promise.resolve();
var nop = () => null;
for(let i=0; i<limitVar; ++i){
queue = queue.then(() => doAjaxStuff(i));
//or if you want to ignore Errors
//queue = queue.then(() => doAjaxStuff(i)).catch(nop);
}
queue.then(() => console.log("finished"));
Or if you use an Array as input:
var done = data.reduce(
(queue, value, index) => queue.then(() => doSomethingWith(value, index)),
Promise.resolve()
);
done.then(() => console.log("finished"));
Related
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 am writing a recursive function inside for loop like below:
var output = [];
function myFunc(myValue, callback) {
myAnotherFunc(myValue, function(result){
for (var i=0; i < result.myKey.length; i++){
if(result.myKey[i].name === 'something'){
myFunc(result.myKey[i].recurseValue, function(recursiveResult){
//some recursive stuff
output.push(recursiveResult.someValue)
});
}
}
});
}
And initiating the recursive function like below:
myFunc(initialValue, function(result){
//some stuff
});
Its working fine, but how do I know when my recursive flow ends so that I can do something else from the final output?
You can use Promises™! It's basically a way to defer a callback till after an Asynchronous flow is completed: Example:
// Instead of passing your normal callback, we'll tell the
// function to use resolve(results) to pass your results to the
// next code block so it can do something after all your recursions are completed
const someTask = new Promise(resolve => myFunc(initialValue, resolve))
someTask.then(result => {
/* Do Something with the results at the end of aformentioned callback hell :D */
})
PS. You also have to modify your original function signature to:
function myFunc(myValue, callback) {
myAnotherFunc(myValue, function(result){
const cbks = [] //Store the async resuls of all myFunc() executions
for (i=0; i < result.myKey.length; i++){
if(results[i] === 'something'){
cbks.push(new Promise(res => myFunc(result[i].recurseValue, res)))
}
}
//Run all async myFunc() and return the results in an array
Promise.all(cbks).then(callback)
});
}
function myFunc(resolve) {
var rec = function(myVal, cb) {
myOther(recurseValue, function(result) {
var hasName = result[myKey].filter(function(obj) {
return obj.name === 'something';
})[0];
if (hasName) {
rec(hasName[recurseValue], function(recResult) {
// other recursive stuff
});
} else {
resolve(?); // whatever the final value should be
}
});
};
return rec;
}
function recurseAsync(f, initial) {
return new Promise(function(resolve, reject) {
f(resolve)(initial);
});
}
Couple notes.
The recurseAsync function takes a function that takes a resolution callback and returns a recursive function that calls that callback when finished to resolve the promise. myFunc has been altered to fit that format.
I used array filtering rather than a for loop and shortened some names. Also if you are using a variable for object access use [] instead of .. To use the final value when all of this is finished you can call .then on the promise.
// whatever initial value 'foo' should be
var finished = recurseAsync(myFunc, foo);
finished.then(function(finalValue) {
// do something with the final result of all the recursion
});
I have a function with nested for-loops and IF statements and one call of an asynchronous function. The structure looks similar to this:
search(){
// for loop 1
for (let i in object){
// IF statement 1
if (condition1){
// for loop 2
for (let o in object[i]){
// IF statement 2
if (condition2){
// call of an asynchronous function
this.asyncFunction().then( data => {
this.result.push(data)
});
}
}
}
}
}
I want to call one function (let's say goToNextPage() ) as soon as all loops are done and all async-function calls are completed and, thus, my this.result object is completed.
However, I don't know how to manage this. I tried to work with counters, to know when the last object[i] is handled and when the last object[i][o] was handled to call goToNextPage() afterwards. But this doesn't work properly, if the last object fails one if statement. Then goToNextPage() is called, while the async function of the second to last object is still running.
And I can't just call goToNextPage() within
this.asyncFunction().then( data => {
this.result.push(data)
// call it here, if last element is reached
});
since then it would not be called if the last element doesn't meet one of the IF statements.
I hope it's understandable what my problem is and that there is a solution for this...
Thank you for your time!
Koleman answered this perfectly on the official Ionic forum and thereby helped me with the solution: Link
His approach is to insert promises in the structure and to use Promise.all(promises).then(...) to wait until all promises are resolved.
search(){
let promises = [];
// for loop 1
for (let i in object){
// IF statement 1
if (condition1){
// for loop 2
for (let o in object[i]){
// IF statement 2
if (condition2){
let promise = new Promise((resolve, reject) => {
this.asyncFunction().then( data => resolve(data));
});
promises.push(promise);
}
}
}
}
return new Promise((resolve, reject) => {
if(!promises.length){
reject('Something went worng!');
}else{
Promise.all(promises).then((results) => {
this.result = this.result.concat(results);
resolve('We done it!');
});
}
});
}
search().then(
successText => {
console.log(statusText);
console.log(this.result);
},
failText => {
console.log(failText);
}
);
I'm trying to do several calls inside a loop to an asynchronous API until the value that I want (true in this case) is returned. The issue is that I don't want to keep looping after that value is found and I don't want to execute the lines after the loop without the value returned from it...but so far I can't get that working. I think I'm doing something wrong here, but "I can't get the whole picture".
function isSelected(element, callback) {
// This is a little bit confusing for me...can I just get the value from
// getDataAsync without using isSelected function?
Office.select(element).getDataAsync(function (asyncResult) {
var result = true;
// some logic here that may change 'result'
callback(result);
});
}
function delete(elements) {
var index = -1;
for (var i = 0, (i < elements.length) && (index < 0); i++) {
isSelected(elements[i], function(result) {
if (result) { index = i; }; // ...and I don't want to keep "looping"
});
}
// I want to execute these lines after the for-loop is done
// ...do something with the "correct" index value
}
Have you tried Kriskowal's Q? There's a nice function called Q#allSettled:
Q.allSettled(promises)
.then(function (results) {
results.forEach(function (result) {
if (result.state === "fulfilled") {
var value = result.value;
} else {
var reason = result.reason;
}
});
});
So basically this is how it would work in your case:
var promises = [];
for(/* conditions */) {
promises.push(/* async call which returns a promise */);
}
Q.allSettled(promises).then(function(results) {
results.forEach(function (result) {
var value;
if (result.state === "fulfilled") {
value = result.value;
// do something with "value"
}
});
});
allSettled just makes sure the then will be executed regardless of whether or not the promise was successful or not, and you can check the value of the object you retrieve from your async call.
I am recommending three ways to doing it.
Using just JavaScript.
Using Async library.
Using underscore library.
Here you can see the javascript implementation:
You can do something like that:
You need to track home many times, you call the function, and how many times the callback happened
function delete(elements) {
var index = -1;
var stack=0;
for (var i = 0, (i < elements.length) && (index < 0); i++) {
stack++ // Go up on each loop
isSelected(elements[i], function() {
stack--; //Go down each callback
index = i;
if(stack==0) afterAllFinish() //When it return to 0 mean all callback have finished
});
}
function afterAllFinish(){
// I want to execute these lines after the for-loop is done
// ...do something with the "correct" index value
}
}
Using other libraries:
Please take a look at http://underscorejs.org/#after for the underscore way to solve it.
Please take a look at https://github.com/caolan/async#parallel to see the async way to solve it.
I have this code as a starting point.
// $ = jQuery
// groupAdata and groupBdata are arrays
function funcA(elem) {
for (f = 0; f < groupAdata.length ; f++) {
// this is an example on how this function calls other functions asynchronously.
elem.children('.partyA').each( function() {
this.innerHTML = "been here" + groupAdata[f];
});
}
}
function funcB(elem) {
// another function that fires more calls
for (f = 0; f < groupAdata.length ; f++) {
$.post(url, somedata, function(data) {
elem.children('.partyB').each( function() {
this.innerHTML = "will be there" + groupBdata[f] + data;
});
}
}
}
$(document).ready(function() {
$('.groupA').each(function () {
funcA(this);
});
$('.groupB').each(function (){
funcB(this);
});
});
function endofitall() {
// call this after all instances of funcA and funcB are done.
}
When running endofitall(), I'd like to be sure that all calls of funcA and funcB are done.
I take that Promises and jQuery.Deferred() would be a good/preferred approach but was not able to map the answers I found to this specific scenario. (It is part of a templating tool that fires multiple dom manipulators func[AB] for multiple DOM elements.)
You can use $.when().
Your goal should be to get to:
// call funcA, call funcB
$.when( funcA(), funcB() )
// when everything is done go on with the callback
.done(endofitall);
In the case of funcA (synchronous function there's no problem and it will work as is).
In the case of funcB (asynchronous) there are some things to consider. If it would be just one ajax call your code should be something like:
// This function returns a promise.
// When it's fulfilled the callback (in your case '.done(endofitall)')
// will be called.
function funcB(somedata){
return $.post(url, somedata);
}
As you are actually making more requests you have to return a resolved promise only when all calls have been fulfilled.
// an *Asynchronous* function, returns an array of promises
function funcB(elem, groupAdata) {
var allCalls = [];
// for each element in the array call the relative async
// function. While you're calling it push it to the array.
groupAdata.forEach(data, function(data){
allCalls.push( $.post(url, data) );
});
// allCalls is now an array of promises.
// why .apply(undefined)? read here: https://stackoverflow.com/a/14352218/1446845
return $.when.apply(undefined, allCalls);
}
At this point you can go for a flat and clear:
$.when( funcA(), funcB() ).done(endofitall);
As a rule of thumb: if you are making async requests try to always return a promise from them, this will help flatten out your code (will post some link later on if you want) and to leverage the power of callbacks.
The above code can surely be refactored further (also, I haven't used a lot of jQuery in the last few years, but the concept applies to any Js library or even when using no library at all) but I hope it will help as a starting point.
References:
$.when
A similar answer here on SO
Call endofitall() inside each iteration for funcA and funcB. Keep a counter and perform the actual work once the counter reaches the number signifying all the tasks are complete.
function funcA(elem) {
for (f = 0; f < groupAdata.length ; f++) {
// these calls are not async
elem.children('.partyA').each( function() {
this.innerHTML = "been here" + groupAdata[f];
});
endofitall();
}
}
function funcB(elem) {
// another function that fires more calls
for (f = 0; f < groupBdata.length ; f++) {
$.post(url, somedata, function(data) {
elem.children('.partyB').each( function() {
this.innerHTML = "will be there" + groupBdata[f] + data;
});
endofitall();
}
}
}
$(document).ready(function() {
$('.groupA').each(function () {
funcA(this);
});
$('.groupB').each(function (){
funcB(this);
});
});
var counter=0;
function endofitall() {
if(++counter==groupAdata.length + groupBdata.length){
//do stuff
}