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.
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>
In this code I want to get the posSnapshot.val().case and store it in the tatarget var, for this I need to execute the dbCall() function BEFORE console.log('TARGETOUT', tatarget)
function dbCall() {
var toReturn;
return admin.database().ref().child('games/' + event.params.gameId + '/player1/boats').once('value').then(function(snapshot) {
snapshot.forEach(function(typeSnapshot) {
typeSnapshot.forEach(function(posSnapshot) {
console.log('POSKEY', posSnapshot.key);
if(posSnapshot.key != 'status') {
console.log(rngPos, incr, posSnapshot.val().state);
if(rngPos == incr && posSnapshot.val().state == 0) {
console.log('===');
return(posSnapshot.val().case);
}
else if(rngPos == incr && posSnapshot.val().state == 1) {
return('1');
}
incr ++;
}
});
});
});
}
var loopPromise = new Promise(function(resolve, reject) {
DD = dbCall();
tatarget = DD.then(function(dbResult) {
console.log(dbResult);
return dbResult;
});
console.log('TARGETOUT', tatarget);
console.log('TARGETRESOLVE', tatarget);
resolve(tatarget);
});
loopPromise.then(function(loopResult) {
console.log(loopResult);
});
My problem is that the dbCall() function end AFTER the console.log !
I tried every promise, every callback functions, I also tried async and await but Firebase don't handle it.
I worked to this part of code for more than 15 hours.
Can somebody help me ?
Thanks !
This could always be organized better but basically you:
Return the Firebase ref promise from dbCall()
Wait for the promise with then()
Perform your desired functionality
resolve your promise, because loopPromise is a promise
function dbCall() {
var toReturn;
return admin.database().ref().child('games/' + event.params.gameId + '/player1/boats').once('value');
}
var loopPromise = new Promise(function(resolve, reject) {
dbCall().then(function(snapshot) {
snapshot.forEach(function(typeSnapshot) {
typeSnapshot.forEach(function(posSnapshot) {
console.log('POSKEY', posSnapshot.key);
if(posSnapshot.key != 'status') {
console.log(rngPos, incr, posSnapshot.val().state);
if(rngPos == incr && posSnapshot.val().state == 0) {
console.log('===');
resolve(posSnapshot.val().case);
}
else if(rngPos == incr && posSnapshot.val().state == 1) {
resolve('1');
}
incr ++;
}
});
});
print(dbResult);
});
});
loopPromise.then(function(loopResult) {
console.log(loopResult);
});
function print(tatarget) {
console.log('TARGETOUT', tatarget);
console.log('TARGETRESOLVE', tatarget);
}
I have several functions that use this given for loop below.
function startClaw(dir){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
isAnimating = $("#claw").is(':animated');
if(!isAnimating){// prevents multiple clicks during animation
if(isMoving || isDropping){ return; }
MCI = setInterval(function(){ moveClaw(dir); },10);
//console.log("startClaw:" + dir);
stopSwingClaw();
}
}
}
}
//.................................................................
function dropClaw(){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
if(isDropping){ return; } //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
}
}
Everything in the else if statement is different within the various functions. I'm wondering if there is any way to place the "pieces" of the for loop on the outside of the else if into its very own function. I feel like I've seen this or had done this a very long time ago, but it escapes me and I couldn't find any examples. Thanks everyone!
Previewing, I see this is similar to the above. Two differences (it looks like) are here the count gets passed to the function in case they needed to ever have different checks in the if statement, and, it's checking what the return value is since it looks like you return out of the loop if the condition is met. There are notes in comments in the code below.
function startClaw(dir) {
// Pass a function as a callback to the method which expects to receive the count as a param
doReadCount(qdata, function(theCount) {
if (theCount === 5) {
isAnimating = $("#claw").is(':animated');
if (!isAnimating) { // prevents multiple clicks during animation
if (isMoving || isDropping) {
return true;
}
MCI = setInterval(function() { moveClaw(dir); }, 10);
//console.log("startClaw:" + dir);
stopSwingClaw();
}
return false;
});
}
//.................................................................
function dropClaw() {
// Pass a function as a callback to the method which expects to receive the count as a param
doReadCount(qdata, function(theCount) {
if (theCount === 5) {
if (isDropping) {
return;
} //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
});
}
function doReadCount(qdata, elseFunction) {
var readCount = 0;
var elseReturn;
for (var isRead in qdata) {
readCount++;
if (qdata[isRead]['reading'] == true) {
return;
} else {
// call the function that was sent and pass it the current read count. If the return is true, then also return true here
elseReturn = elseFunction(readCount);
if (elseReturn) {
return;
}
}
}
}
You can pass a function into another function to achieve this. I've done it for dropClaw, and it should be clear from my example how to do also extract startClaw.
function operateClaw(func){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
func();
}
}
}
function drop () {
if(isDropping){ return; } //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
function dropClaw () {
operateClaw(drop);
}
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.
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.