How can I wait until the end of the two functions, and only after calling callbacks for them to continue the script.
I paid attention to jQuery deferred.then(), but I do not understand how to use it in my case
GoogleDriveModule.checkAuth(function(authResult) {
if (authResult) {
return parts_count += 1;
}
});
DropboxModule.isAuthenticated(function(authResult) {
if (authResult) {
return parts_count += 1;
}
});
Create two deferred objects and resolve them within the callbacks. Then you can wait on both deferreds with $.when:
var googleDone = $.Deferred(),
dropboxDone = $.Deferred();
GoogleDriveModule.checkAuth(function(authResult) {
googleDone.resolve();
if (authResult) {
return parts_count += 1;
}
});
DropboxModule.isAuthenticated(function(authResult) {
dropboxDone.resolve();
if (authResult) {
return parts_count += 1;
}
});
$.when(googleDone, dropboxDone).then(function() {
alert("Both authentication checks completed.");
});
You could have 2 boolean and check in a callback function if they are ready :
var gDriveREADY = false, dBoxREADY = false;
GoogleDriveModule.checkAuth(function(authResult) {
if (authResult) {
return parts_count += 1;
}
gDriveREADY = true;
doSomething()
});
DropboxModule.isAuthenticated(function(authResult) {
if (authResult) {
return parts_count += 1;
}
dBoxREADY = true;
doSomething();
});
function doSomething(){
if(dBoxREADY && gDriveREADY){
//Your code
}
}
In the callbacks you could toggle a simple flag which the other function checks:
var driveDone = 0
, dropDone = 0
;
GoogleDriveModule.checkAuth(function(authResult) {
driveDone = 1;
if (authResult) {
parts_count += 1;
}
if(dropDone){
bothDone();
}
});
DropboxModule.isAuthenticated(function(authResult) {
dropDone = 1;
if (authResult) {
parts_count += 1;
}
if(driveDone){
bothDone();
}
});
function bothDone(){}
This has less overhead than the deferred method, but is not quite as clean.
Related
I have a piece of code:
var a = false;
function wait(milliseconds, async) {
if(!async) {
setTimeout(function() {
console.log('Sync timer done.');
a = true;
return true;
}, milliseconds*1000);
}
(...)
f_recipe.forEach(function(item, index) {
if (obj['actual_step'] != 0 && obj['actual_step'] != index ) {
e = "Desync";
throw e;
};
console.log("Step: " + obj.actual_step);
if(item.substr(item.length - 6) != "false)"){
if (eval(item)) {
obj['actual_step']++;
}
} else {
eval(item);
var ival = setInterval(function(){
if(a) {
console.log('do the next thing');
clearInterval(ival);
}
}, 1000);
}
});
But when I get to 'do the next thing'(interval complete), the forEach loop doesn't continue to the next element of the array. 'a' is set to true after timeout (kind of a synchronous wait in JS). f_recipes is a string array with function call (e.g. 'wait(20, false)').
How to get it to work?
What you're trying to do seems like a very bad idea, but promises can help with this (using Bluebird here because it provides Promise.delay and Promise.each):
function wait(seconds, dontActuallyWait) {
return dontActuallyWait ? null : Promise.delay(seconds * 1000);
}
function runSequence(things) {
return Promise.each(things, function(thing) {
return eval(thing);
});
}
runSequence([
'console.log("hello")',
'wait(2, false)',
'console.log("hello again")',
'wait(5, false)',
'console.log("goodbye")'
]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.5.1/bluebird.min.js"></script>
If I have some code for example -
loadImages(pos - 1,function(){
loadImages(pos,function(){
loadImages(pos + 1);
});
});
function loadImages(key){
$('.slide:nth-child('+key+') .imgholder').each(function(){
par = $(this).parent();
imgHold = $(this);
if (loaded_images.indexOf($(imgHold).data('img-src')) > 0){
return true;
} else {
$(imgHold).attr('src',$(imgHold).data('img-src')).on('load', function() {
if (!$(imgHold).hasClass('fgimg')){
$(par).css('background-image','url('+$(imgHold).data('img-src')+')');
} else {
$(imgHold).css('visibility','visible');
}
// How do I return loadImages from here??
})
}
})
}
I want to trigger code after this function has completed so I need to return true at my // comment line. What is the official/best way of doing this?
You'll need a callback:
function loadImages(key, callback){
$('.slide:nth-child('+key+') .imgholder').each(function(){
par = $(this).parent();
imgHold = $(this);
if (loaded_images.indexOf($(imgHold).data('img-src')) > 0){
return true;
} else {
$(imgHold).attr('src',$(imgHold).data('img-src')).on('load', function() {
if (!$(imgHold).hasClass('fgimg')){
$(par).css('background-image','url('+$(imgHold).data('img-src')+')');
} else {
$(imgHold).css('visibility','visible');
}
callback(true);
// How do I return loadImages from here??
})
}
})
}
Usage:
loadImages(key, function(yourVar){
console.log(yourVar); // true
});
You have to pay attention on cases when callback is not invoked.
More info: http://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/
I am new to jQuery Promises but I am using them fine in other parts of the app, but this one does not seem to be working as expected. Seems it is not waiting for the return before executing the next code.
function setActivityOnGrid(){
...snip...
var sameActivityCnt = $(actId).length;
if(sameActivityCnt){
sameActivityDialog().then(function(retVal) {
if(retVal == true){
activityid += "-"+sameActivityCnt++;
}
});
}
//code here is being executed before the dialog is even shown
}
function sameActivityDialog(){
var deferred = $.Deferred();
bootbox.confirm("You have already added this activity. Would you like to add it again?", function(result) {
deferred.resolve(result);
});
return deferred.promise();
}
Move the //code here is being executed before the dialog is even shown to here:
var sameActivityCnt = $(actId).length;
if(sameActivityCnt){
sameActivityDialog().then(function(retVal) {
if (retVal === true){
activityid += "-"+sameActivityCnt++;
}
// code goes here!!!!
});
}
Inside the then callback function (or later chained then functions).
Alternatives
var sameActivityCnt = $(actId).length;
sameActivityDialog().then(function(retVal) {
if(sameActivityCnt && retVal === true){
activityid += "-"+sameActivityCnt++;
}
// code goes here!!!!
});
or
var promise = sameActivityDialog();
var sameActivityCnt = $(actId).length;
if(sameActivityCnt){
promise = promise.then(function(retVal) {
if (retVal === true){
activityid += "-"+sameActivityCnt++;
}
return retVal;
});
}
promise = promise.then(function(retVal) {
// code goes here!!!!
return retVal;
});
And if you split this logic into smaller functions then you can actually compose this well:
function doIf(condition, fn) {
return function(data) {
if (condition) {
return fn(data);
} else {
return data
}
};
}
function appendID(retVal) {
if (retVal === true){
activityid += "-"+sameActivityCnt++;
}
return retVal;
}
function doMoreStuff(retVal) {
// code goes here!!!!
return retVal;
}
var sameActivityCnt = $(actId).length;
sameActivityDialog()
.then(doIf(sameActivityCnt, appendID))
.then(doMoreStuff);
The promise is working correctly. A promise doesn't make a non-blocking function block.
It gives you an object which you can repeatedly add new callbacks to which will run when the non-blocking function is done.
So what I want to do is create a casperJS function which allows us to repeat a step X times, by refreshing the page first, when this step function reaches the timeout.
For unreliable test due to a specific page bug/freeze at the moment and reduce the percentage of false negative.
I have just a problem, I don't know how to break this loop, because I'm in IIFE scope, see following code :
var echoTest = function(){
casper.echo('Hi');
};
var trueFunction = function(){
return true;
};
var verifyFailedTest = function(number, trueReturn, thenFunction){
var i = 0;
//outer: <-------
for (; i <= number; i++){ // <------ how to break this loop in my function then()
//IIFE
(function(index){
if (index < number-1){
//Execute 'number-1' times the then() step (reload the page each time) if timeout or until trueReturn returns true
casper.then(function(){
casper.waitFor(function checkReturnTrue(){
return trueReturn();
}
, function then() {
thenFunction();
//break outer; break; return false; <------ here where I want to break the loop
}
, function timeout() {
casper.reload();
});
});
}
//last execution, will return the normal error if it fails each time
else if (index === number){
casper.then(function(){
casper.waitFor(function checkReturnTrue(){
return trueReturn();
}
, function then() {
console.log('else');
thenFunction();
});
});
}
else{console.log('verifyFailedTest() bug');}
})(i);
}
};
I tried with label, but I got a syntax error.
Execution :
casper.test.begin('\n*************** Suite of planned test : scenario 1 **************\n', 1, function suite(test) {
casper.start('https://www.google.fr/', function() {
verifyFailedTest(3, trueFunction, echoTest);
});
casper.run(function() {
test.done();
});
});
});
I think can do this without a for loop, by clustering your code into parts and do this recursively:
var verifyFailedTest = function(number, repeatStep, trueReturn){
var index = 0;
function intermediate(){
casper.then(function(){
repeatStep();
casper.waitFor(function checkReturnTrue(){
return trueReturn();
}
, function then() {
this.test.pass("Test passes after " + (index+1) + " try");
}
, function timeout() {
casper.reload();
if (index < number-1) {
intermediate();
} else {
lastTry();
}
index++;
});
});
}
function lastTry(){
casper.then(function(){
repeatStep();
casper.waitFor(function checkReturnTrue(){
return trueReturn();
}
, function then() {
this.test.pass("Test passes after " + (index+1) + " try");
});
});
}
intermediate();
};
You'll have an error only after the number'th try.
But if you want to use your IIFE, the following might work by redefining thenFunction and skipping then block after you know that it is unnecessary (doBreak === true):
var verifyFailedTest = function(number, trueReturn, thenFunction){
var i = 0, doBreak = false;
var oldThenFunction = thenFunction;
thenFunction = function(){
doBreak = true;
oldThenFunction();
};
for (; i <= number; i++){
if (doBreak) {
break;
}
// your IIFE here
(function(index){
if (index < number-1){
//Execute 'number-1' times the then() step (reload the page each time) if timeout or until trueReturn returns true
casper.then(function(){
if (doBreak) { return; }
casper.waitFor(...);
});
}
//last execution, will return the normal error if it fails each time
else if (index === number){
casper.then(function(){
if (doBreak) { return; }
casper.waitFor(...);
});
}
else{console.log('verifyFailedTest() bug');}
})(i);
}
};
Use CasperJS's repeat(int times, Function then) function.
I have this jQuery function that is using another jQuery library called html5csv.js (which explains some of the CSV stuff you will see)
Here is it:
function validateNewQuiz()
{
CSV.begin("#upload_csv").go(function(e,D)
{
if (e)
{
return console.log(e);
alert("Sorry, an error occured");
}
var s = "";
for (var i = 0; i <= D.rows.length - 1; i++)
{
s +=D.rows[i].join(',');
s += "\n";
}
var fullString = s;
if(/^(([^,]+,){4}[^,]+\n){3}$/.test(fullString))
{
return true;
}
else
{
return false;
}
});
}
Here is how I am trying to call my function, from an onsubmit within my form:
<form method="post" action="createplay.php" onsubmit="return validateNewQuiz();" enctype="multipart/form-data">
My function has been thoroughly tested, along with my regex to make sure it was working. When I decided to implement it into my large document, and wrap it around function validateNewQuiz(){ //my function here } , it stopped working.
I did not make my tests with the onsubmit part within my form either.
I tried fixing it two ways. One way was like this, splitting them into two functions:
var fullString = "";
CSV.begin("#upload_csv").go(function(e,D)
{
if (e)
{
return console.log(e);
alert("Sorry, an error occured");
}
var s = "";
for (var i = 0; i <= D.rows.length - 1; i++)
{
s +=D.rows[i].join(',');
s += "\n";
}
fullString = s;
});
function validateNewQuiz()
{
if(/^(([^,]+,){4}[^,]+\n){3}$/.test(fullString))
{
return true;
}
else
{
return false;
}
}
And the second way, by added the return outside of the CSV part:
var fullString = "";
function validateNewQuiz()
{
CSV.begin("#upload_csv").go(function(e,D)
{
if (e)
{
return console.log(e);
alert("Sorry, an error occured");
}
var s = "";
for (var i = 0; i <= D.rows.length - 1; i++)
{
s +=D.rows[i].join(',');
s += "\n";
}
fullString = s;
});
if(/^(([^,]+,){4}[^,]+\n){3}$/.test(fullString))
{
return true;
}
else
{
return false;
}
}
Does anyone have any suggestions to why my form is always submitting, even when my function should be returning false?
Here is another edit that I tried to make, although it is still submitting to my PHP and the console messages are not being displayed since that page is being submitted to PHP, therefore reloading
jQuery("#newQuizID").click(function(e)
{
e.preventDefault();
CSV.begin("#upload_csv").go(function(e,D)
{
if (e)
{
return console.log(e);
alert("Sorry, an error occured");
}
var s = "";
for (var i = 0; i <= D.rows.length - 1; i++)
{
s +=D.rows[i].join(',');
s += "\n";
}
var fullString = s;
if(/^(([^,]+,){4}[^,]+\n){3}$/.test(fullString))
{
console.log("Working");
jQuery("#form-step2").submit();
}
else
{
console.log("Not Working");
}
});
});
html5csv puts a event handler on the file input so it only triggers when a file is added so you need to set a valid flag somewhere and then check it before submitting
function checkValidCSV(e) {
var isValid = jQuery("#form-step2").data("hasValidData");
if( typeof(isValid) != "undefined" && isValid ) {
jQuery("#form-step2").submit();
} else {
//Do whatever invalid data cals you want to do here
alert("csv file was invalide so i am not submitting the form");
e.preventDefault();
}
}
function csvFileLoaded(e,D) {
if (e) {
return console.log(e);
alert("Sorry, an error occured");
}
var s = "";
for (var i = 0; i <= D.rows.length - 1; i++) {
s +=D.rows[i].join(',');
s += "\n";
}
var fullString = s;
if(/^(([^,]+,){4}[^,]+\n){3}$/.test(fullString)){
console.log("Valid Data");
jQuery("#form-step2").data("hasValidData",true);
} else {
console.log("Invalid Data");
jQuery("#form-step2").data("hasValidData",false);
}
}
jQuery(document).ready(function() {
CSV.begin("#upload_csv").go(csvFileLoaded);
jQuery("#newQuizID").click(checkValidCSV);
});