Node strange user input async behaviour - javascript

I am just getting used to Node programming, but I have come across this execution issue which I am slightly confused about.
I am trying to test if a write path already exists and if it does then ask the user for input.
function testPath(fileName) {
fs.exists(path.resolve('.', fileName), function(exists) {
//the filepath already exists, ask for user confirmation
if(exists) {
process.stdin.on('keypress', function (str, key) {
//print result of keypress to console
console.log("str: ", str, " key: ", key);
if ((str.toLowerCase() == "n") || (~["y", "n"].indexOf(str.toLowerCase()))) {
return false;
}
else {
return true;
}
});
}
else {
//the filepath does not already exist - return true
return true;
}
console.log("Filename in the target directory already exists, would you like to overwrite? (y/n)");
});
}
This function as a whole will be be resolved (or not) by a promise called on it.
The message to user and wait for keypress seem to action in the correct way but it sticks in a loop and never returns even on a valid keypress, does anyone know why this would be?

If you want to use it as a promise, you need to return a promise:
function testPath(fileName) {
return new Promise((resolve, reject) => {
fs.exists(path.resolve('.', fileName), function(exists) {
//the filepath already exists, ask for user confirmation
if(exists) {
process.stdin.on('keypress', function (str, key) {
//print result of keypress to console
console.log("str: ", str, " key: ", key);
if ((str.toLowerCase() == "n") || (~["y", "n"].indexOf(str.toLowerCase()))) {
return reject();
}
else {
return resolve();
}
});
}
else {
//the filepath does not already exist - return true
return resolve();
}
console.log("Filename in the target directory already exists, would you like to overwrite? (y/n)");
});
}
})

Related

Variable is returning false, even though it is clearly true [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
I have this snippet of code from my recent project, I am wondering why this returns false when I provide the correct input. I have put console logs everywhere and checked everything. I tried using global varibles, temp varables, all of that and nothing has worked so far.
var directoryPath = path.join(__dirname, 'images');
var result = false;
fs.readdir(directoryPath, function (err, files) {
var local = false;
//handling error
if (err) {
return console.log('Unable to scan directory: ' + err);
}
//listing all files using forEach
files.forEach(function (file) {
// Do whatever you want to do with the file
if(file == password) {
result = true;
}
});
});
return result
It may seem to you like result is true, but the single thread that runs your code sees something more along the lines of
var result = false
fs.readdir(directoryPath, function (err, files) => {...}) // thread: i'll just set this off
return result // thread: i'll return this immediately
So you're getting false when you really want true. Here's how you can do it with async/await
const main = async (directoryPath, password) => {
let result = false
let files
try {
files = await fs.promises.readdir(directoryPath)
} catch (err) {
return console.log('Unable to scan directory: ' + err);
}
files.forEach(file => {
if (file == password) {
result = true
}
})
return result // now result will be true if file == password
}

How to chain promises within nested for loops?

var verifyEmail = function (thisEmail){
return new Promise(
function (resolve, reject) {
quickemailverification.verify(thisEmail, function (err, response) {
// Print response object
console.log(response.body);
if (response.body["success"] == "true"){
var validity = response.body["result"];
if (validity == "valid"){
console.log("Email Valid!");
resolve(validity);
} else {
console.log("Email Invalid!")
resolve(validity);
}
} else {
var reason = new Error("API unsuccessful");
reject(reason);
}
});
}
);
};
var saveValidity = function (validity){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
var state = true;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
}
}
);
};
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
var snap = snapshot.val();
keys = Object.keys(snap);
for (var i = 0; i < 100; i++){
var emails = snap[keys[i]]["emails"];
if (emails){
for (var x = 0; x<emails.length; x++){
var thisEmail = emails[x]["email"];
var emailVerified = emails[x]["verified"];
if (emailVerified != true || emailVerified != false){
verifyEmail
.then(saveValidity)
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error){
console.log(error.message);
});
}
}
}
}
});
Above is the code I put together. I'm not all too convinced that it will work. I'm new to promises, so I'm trying to understand how to do this right.
The verifyEmail function should take in the email address from the firebase query in the third chunk of the code. The saveValidity function should take on the validity response from verifyEmail.
But, what I'm also worried about the nested for loop I have in the firebase query block. I'm looping through each user to validate their emails, but each user sometimes also has multiple emails. I'm worried that it will loop on to the next user before finishing checking all the emails of the previous user.
I'm also not sure if I can pass data into the promise functions the way I did.
Could definitely use some help here. Really trying hard to understand how this works.
First, you need to fix saveValidity() to always resolve or reject the promise and to pass in the other variables key and thisEmail that it references:
const saveValidity = function (validity, key, thisEmail){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
let state = true;
admin.database().ref("/users_unverified/"+key+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error("Email ("+thisEmail+") verfication could not be saved" + error));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error(msg));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
}
}
);
};
Then, several changes are made to your main loop:
I assume we can run all the verifyEmail() calls in parallel since they don't appear to have anything to do with one another.
Change verifyEmail.then(...) to verifyEmail(thisEmail).then(...)` to actually call the function
Collect all the verifyEmail() promises in an array
Call Promise.all() on the array of promises to monitor when they are all done
Return value from .then() so we get the returned values in Promise.all()
rethrow in .catch() so promise stays rejected and will filter back to Promise.all(). You could eat errors here if you want to ignore them and continue with others.
Switch from var to let
Change from != to !== since it looks like your explicitly looking for a true or false value and don't want type casting.
Pass in the variables that saveValidity() needs.
Change logic when comparing emailVerified because what you had before was always true and thus probably not the right logic. I think what you want is to know when emailVerified is not yet set to true or to false which means you have to use &&, not ||.
Compare outer for loop with keys.length, not hard-coded value of 100.
And, here's the resulting code for the main nested for loop:
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
let snap = snapshot.val();
let keys = Object.keys(snap);
let promises = [];
for (let i = 0; i < keys.length; i++){
let key = keys[i];
let emails = snap[key]["emails"];
if (emails){
for (let x = 0; x < emails.length; x++) {
let currentKey = key;
let thisEmail = emails[x]["email"];
let emailVerified = emails[x]["verified"];
if (emailVerified !== true && emailVerified !== false){
promises.push(verifyEmail(thisEmail).then(validity => {
return saveValidity(validity, currentKey, thisEmail);
}).then(function (fulfilled) {
console.log(fulfilled);
return fulfilled; // after logging return value so it stays the resolved value
}).catch(function (error) {
console.log(error.message);
throw error; // rethrow so promise stays rejected
}));
}
}
}
}
return Promise.all(promises);
}).then(results => {
// all results done here
}).catch(err => {
// error here
});
If ES2017 is available in your case, you can just use the keywords await and async to do that directly. Following is an example:
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
And you can read more about async/await here.
If you want to do that without async/await to achieve better browser compatibility, you can use Babel to do the pre-compile.
If you really want a lightwight implementation, you can use a function named chainPromiseThunks, or chain for short. This chain function accepts an Array of Thunks of Promises, And returns a new Thunk of Promise, Following is an one-line-implementation of chain:
const chain = thunks => thunks.reduce((r, a) => () => r().then(a));
And here is a usage demo:
const echo = x =>
new Promise(function(resolve) {
return setTimeout((function() {
console.log(x);
return resolve(x);
}), 1000);
})
;
const pThunks = [1,2,3,4,5].map(i => () => echo(i));
chain(pThunks)();

Promise handling - update db entry if exists

I am stuck with new challenge on Promises.
Goal: Update the DB entry only if P_KEY exists.
current db is exposed through module and module has get and put method for db. Both returning Promise.
Approach:
API calls for update method handler on node js with ID and Set of values (json)
In handler for post method call to get method of db module check if value on promise success is empty or not if yes return false else true.
If true; data exists call to put method of db module.
but somehow data it always returns false. even if db entry has been made through db api.
/** Function to check if p_key exist*/
function checkIfPKExists(idVal){
pkdb.get(idVal).then(function(value){
if(value){
return true;
} else {
return false;
}
},
function(err){
console.log(err);
return false;
})
}
/** UPDATE METHOD **/
var ch = checkIfPKExists("p_k"+req.body.id);
if(!ch){
res.send("pk does not exist oo " + req.body.id);
} else {
var pk_promise = pkdb.put("p_k"+req.body.id, req.body.pk);
pk_promise.then(
function(){
res.send(JSON.stringify(req.body.pk) + "Updated Successfully");
},
function(err){
res.send("Error occurred : " + err);
}
)
}
My understanding is ch value is set from checkPK function and since thats a promise it just goes ahead and processes if loop which by default stands true and done irrespective whether element is there or not same result. Not found.
What can I do to correct it?
One issue is that no value is returned from checkIfPKExists() function call, see Why is value undefined at .then() chained to Promise?. Use .then() and .catch() to get the Promise value returned from the function
function checkIfPKExists(idVal) {
// `return` the `Promise` here
return pkdb.get(idVal).then(function(value) {
if (value) {
return true;
} else {
return false;
}
}, function(err) {
console.log(err);
return false;
})
}
/** UPDATE METHOD **/
var ch = checkIfPKExists("p_k" + req.body.id);
ch.then(function(bool) {
// if `true` do stuff
if (bool) {
var pk_promise = pkdb.put("p_k" + req.body.id, req.body.pk)
return pk_promise.then(function() {
return res.send(JSON.stringify(req.body.pk) + "Updated Successfully");
}, function(err) {
return res.send("Error occurred : " + err);
})
} else {
// do other stuff
return res.send("pk does not exist oo " + req.body.id);
})
.catch(function(err) {
// handle error
})
checkIfPKExists() is an asynchronous function, if you want to use ch you would have to use a .then() to get it in a function and then use the value.
function checkIfPKExists(idVal)
{
return pkdb.get(idVal).then(function(value)
{
if(value)
{
return true;}
else
{
return false;
}
}, function(err)
{ console.log(err);
return false;
})
}
/** UPDATE METHOD **/
checkIfPKExists("p_k"+req.body.id).then(function(ch){
if(!ch)
{
res.send("pk does not exist oo " + req.body.id);
}
else
{
return pkdb.put("p_k"+req.body.id, req.body.pk).then(
function()
{
res.send(JSON.stringify(req.body.pk) + "Updated Successfully");
},
function(err)
{
res.send("Error occurred : " + err);
})
}})

getting object Parse from a loop

In a Parse server function, it's getting Matches and profiles.
From a query to get matches another function is called to get Profiles by id but the result is :
{"_resolved":false,"_rejected":false,"_reso resolvedCallbacks":[],"_rejectedCallbacks":[]}
Main Query :
mainQuery.find().then(function(matches) {
_.each(matches, function(match) {
// Clear the current users profile, no need to return that over the network, and clean the Profile
if(match.get('uid1') === user.id) {
match.set('profile2', _processProfile(match.get('profile2')))
match.unset('profile1')
}
else if (match.get('uid2') === user.id) {
var profileMatch = _getProfile(match.get('profile1').id);
alert(">>>"+JSON.stringify(profileMatch));
match.set('profile1', _processProfile(match.get('profile1')))
match.unset('profile2')
}
})
the function to get Profile info:
function _getProfile(id){
var promise = new Parse.Promise();
Parse.Cloud.useMasterKey();
var queryProfile = new Parse.Query(Profile);
return queryProfile.equalTo("objectId",id).find()
.then(function(result){
if(result){
promise.resolve(result);
alert("!!!!"+result);
}
else {
console.log("Profile ID: " + id + " was not found");
promise.resolve(null);
}
},
function(error){
promise.reject(error)
});
return promise;
}
Just found this a little late. You've probably moved on, but for future readers: the key to solving something like this is to use promises as returns from small, logical asynch (or sometimes asynch, as in your case) operations.
The whole _getProfile function can be restated as:
function _getProfile(id){
Parse.Cloud.useMasterKey();
var queryProfile = new Parse.Query(Profile);
return queryProfile.get(id);
}
Since it returns a promise, though, you cannot call it like this:
var myProfileObject = _getProfile("abc123");
// use result here
Instead, call it like this:
_getProfile("abc123").then(function(myProfileObject) { // use result here });
Knowing that, we need to rework the loop that calls this function. The key idea is that, since the loop sometimes produces promises, we'll need to let those promises resolve at the end.
// return a promise to change all of the passed matches' profile attributes
function updateMatchesProfiles(matches) {
// setup mainQuery
mainQuery.find().then(function(matches) {
var promises = _.map(matches, function(match) {
// Clear the current users profile, no need to return that over the network, and clean the Profile
if(match.get('uid1') === user.id) {
match.set('profile2', _processProfile(match.get('profile2'))); // assuming _processProfile is a synchronous function!!
match.unset('profile1');
return match;
} else if (match.get('uid2') === user.id) {
var profileId = match.get('profile1').id;
return _getProfile(profileId).then(function(profileMatch) {
alert(">>>"+JSON.stringify(profileMatch));
match.set('profile1', _processProfile(match.get('profile1')))
match.unset('profile2');
return match;
});
}
});
// return a promise that is fulfilled when all of the loop promises have been
return Parse.Promise.when(promises);
}

Javascript Loop over elements and click a link using WebdriverIO

I am using Javascript, webdriverio (v2.1.2) to perform some data extraction from an internal site. So the idea is
Authenticate
Open the required URL, when authenticated
In the new page, search for an anchor tag having specific keyword
Once found, click on the anchor tag
Below is what I have tried and it works (last two points). I had to use Q and async to achieve it. I was hoping to use only Q to achieve it. Can someone help me, on how to achieve it using Q only ??
var EmployeeAllocationDetails = (function () {
'use stricy';
/*jslint nomen: true */
var Q = require('Q'),
async = require('async'),
_ead_name = 'Employee Allocation Details',
goToEadFromHome;
goToEadFromHome = function (browserClient) {
browserClient.pause(500);
var deferred = Q.defer();
browserClient.elements('table.rmg td.workListTD div.tab2 div.contentDiv>a', function (err, results) {
if (err) {
deferred.reject(new Error('Unable to get EAD page. ' + JSON.stringify(err)));
} else {
async.each(results.value, function (oneResult, callback) {
console.log('Processing: ' + JSON.stringify(oneResult));
browserClient.elementIdText(oneResult.ELEMENT, function (err, result) {
if (err) {
if (err.message.indexOf('referenced element is no longer attached to the DOM') > -1 ){
callback();
} else {
callback('Error while processing :' + JSON.stringify(oneResult) + '. ' + err);
}
} else if(!result){
console.log('result undefined. Cannot process: ' + JSON.stringify(oneResult));
callback();
} else if(result.value.trim() === _ead_name){
deferred.resolve(oneResult);
callback();
}
});
}, function (err) {
// if any of the processing produced an error, err would equal that error
if( err ) {
// One of the iterations produced an error.
// All processing will now stop.
console.log('A processing failed to process. ' + err);
} else {
console.log('All results have been processed successfully');
}
}); //end of async.each
}
});
return deferred.promise;
};
return {
launchEad : goToEadFromHome
}
})();
module.exports = EmployeeAllocationDetails;
Related Github Issue link https://github.com/webdriverio/webdriverio/issues/123
I think you should use async. I think your code is great. It runs everything in parallel and it handles error well.
If
If you want to remove async, there are several options:
use Q flow control
copy paste async's implementation
implement it yourself
If you try to use Q's flow control it will look something like this (pseudo-code):
var getTextActions = [];
function createAction(element){
return function(){
return element.getText();
}
}
each(elements, function(element){ getTextActions.push( createAction(element) ) });
Q.all(getTextActions).then(function(results) {
... iterate all results and resolve promise with first matching element..
} );
note this implementation has worse performance. It will first get the text from all elements, and only then try to resolve your promise. You implementation is better as it all runs in parallel.
I don't recommend implementing it yourself, but if you still want to, it will look something like this (pseudo-code):
var elementsCount = elements.length;
elements.each(function(element){
element.getText(function(err, result){
elementsCount --;
if ( !!err ) { logger.error(err); /** async handles this much better **/ }
if ( isThisTheElement(result) ) { deferred.resolve(result); }
if ( elementsCount == 0 ){ // in case we ran through all elements and didn't find any
deferred.resolve(null); // since deferred is resolved only once, calling this again if we found the item will have no effect
}
})
})
if something is unclear, or if I didn't hit the spot, let me know and I will improve the answer.

Categories

Resources