Node.js call async function from within do while loop - javascript

I want to call an async function from within a do while loop and exit only if the function doesn't return a value.
Is there a way to manage this without having to actually wait outside the callback?
Here's the code that obviously always exits in the first loop since the function hasn't called back yet, when the while () is evaluated:
var i = 0;
do {
var foundListing;
if (i) {
listing.slug = listing.slug + '-' + (i + 1);
}
listingService.getListingBySlug(listing, function(error, pFoundListing) {
if (error) {
return callback(util.errorParser(error));
} else if (Object.keys(pFoundListing).length) {
foundListing = pFoundListing;
i++;
}
});
//Do I have to wait here???
} while (foundListing && Object.keys(foundListing).length);
For Clarification:
The point here is to generate a unique slug. If the slug already exists, i append a number and check again if it exists. When I get to the number that doesn't exist yet, I'm done.
Update:
I found a way using recursion. I posted the working snippet as an answer.

I don't have the possiblity to test it, but maybe a recursive function should do:
const listingService = {
getListingBySlug(listing, callback) {
setTimeout(() => {
if (listing.slug === 'listing-5') {
callback(
false,
{
name: 'Listing 1',
slug: 'listing-1',
}
);
}
else {
callback(false, {});
}
}, 1000);
},
};
function checkListingExists(slug) {
return new Promise((resolve, reject) => {
const listing = { slug };
listingService.getListingBySlug(listing, (error, pFoundListing) => {
if (error) {
reject(error);
} else if (Object.keys(pFoundListing).length) {
resolve(pFoundListing);
} else {
resolve(false);
}
});
});
}
function nextListing(prefix, index) {
checkListingExists(prefix + '-' + index)
.then((listing) => {
if (listing === false) {
nextListing(prefix, index + 1);
} else {
console.log(listing);
}
})
.catch(() => {
// deal with error response
});
}
nextListing('listing', 0);

You should use promise in such cases. You can do something like this.
var DEFERRED = require('deferred');
var deferred = new DEFERRED();
var i =0;
while(1) {
listingService.getListingBySlug(listing, function(error, pFoundListing) {
if (error) {
return callback(util.errorParser(error));
deferred.reject();
break;
} else if (Object.keys(pFoundListing).length) {
i++;
listing.slug = listing.slug + '-' + (i + 1);
if(pFoundListing) {deferred.resolve()}
break;
}
});
deferred.promise.then(function() {
// You will have the listing.slug value here.
});
Btw you should not use Object.keys() to determine whether the object is empty or not. Simply create your own isEmpty method somewhere in utils file and check for the properties. If your pFoundListing is very large it will have the severe performance issue. For small objects (array) you would not notice the difference.

I made it work using recursion as suggested by Giacomo Cosimato.
Here's the snippet that worked:
listingController.js
saveListing: function(listing, callback) {
listingService.findOpenSlug(listing, listing.slug, 1, function(error, listing) {
if (error) {
return callback(util.errorParser(error));
} else {
listingService.saveListing(listing, function(error, savedListing) {
//do some work
});
}
});
}
listingService.js
var classMethods = {
findOpenSlug: function(listing, baseSlug, i, callback) {
listingRepository.getListingBySlug(listing.slug, function(error, foundListing) {
if (error) {
return callback(error);
} else if (Object.keys(foundListing).length) {
listing.slug = baseSlug + '-' + (i + 1)
i++;
classMethods.findOpenSlug(listing, baseSlug, i, callback);
} else {
callback(null, listing);
}
});
},
[...]
}

Related

How do i carry function result back up functions to reiterate over the functions again?

I have an Array that is passed through a forEach loop. Each item is then passed through and is checked for a specific order or characters. If it is true the characters are meant to be removed from the string, then it is supposed to be looped back over with the new string. (Old string minus the checked characters)
My problem is the fact that all the functions are still locally holding the old String (Before the characters were removed). How can I pass the new string into the functions without resetting the value "deleteStringData" currently holds?
let dnaArray = ["ACATATAGACATACGT","AAAAAATACATAGTAGTCGGGTAG","ATACATCGGGTAGCGT"];
dnaStrand = "";
//SORT THROUGH EACH ITEM IN ARRAY
dnaArray.forEach((dnaStrand, index) => {
if (findDna(dnaStrand)) {
console.log("Case #" + index + " " + dnaStrand + ": YES");
}
else {
console.log("Case #" + index + " " + dnaStrand + ": NO");
};
});
function findDna(dnaStrand){
if (findHead(dnaStrand)){
if(findBody(dnaStrand)){
console.log("dna");
return true;
}
}
else {
return false;
}
};
function findHead(dnaStrand){
if (findGlobe(dnaStrand)){
if (findEyeSpots(dnaStrand)) {
return true;
}
}
else{
return false;
}
};
function findBody(dnaStrand){
if (findGlobe(dnaStrand) && findLegs(dnaStrand)) {
return true;
}
else {
return false;
}
};
function findGlobe(dnaStrand){
if(findMatch(dnaStrand, /(A+(TAC|CAT)A)/)){
return true;
}else{
console.log("No Globe");
}
};
function findEyeSpots(dnaStrand){
if(findMatch(dnaStrand, /T(CG*T)*AG/)){
return true;
}else{
console.log("No Eyes");
}
};
function findLegs(dnaStrand){
if(findMatch(dnaStrand, /CG*T/)){
return true;
}else{
console.log("No Legs");
}
};
function findMatch (dnaStrand, regex) {
dnaStrand = String(dnaStrand);
let isMatch = dnaStrand.match(regex);
isMatch = String(isMatch[0]);
//console.log(isMatch);
if (isMatch) {
deleteStringData(dnaStrand, isMatch);
return true;
}
else {
return false;
}
};
function deleteStringData (dnaStrand, string) {
dnaStrand = dnaStrand.replace(string, "");
};
Sorry, but I am sort of busy and don't have the time to read through all your code. Based on your question, I can give you some basic guidance.
make sure your function takes a parameter. If each time the end result is not satisfactory or you just want to repeat it again, do an if statement, and if you're going to repeat it, do yourFunction(endresult) inside. If you worry about lag, consider making your end result a global variable and setting a setTimeout to your function.
If you declare the test functions inside the loop you can act on single instance of the strand in that scope without needing to pass any references around.
You can also use Promises to chain the tests.
This may not be the most efficient method but it is very readable.
(async () => {
const strands = [
'ACATATAGACATACGT',
'AAAAAATACATAGTAGTCGGGTAG',
'ATACATCGGGTAGCGT'
];
const results = [];
async function analyze(strand, index) {
let result = {
'case' : parseInt(index), strand
};
function findMatch(pattern) {
return new Promise((resolve, reject) => {
let m = strand.match(pattern)[0];
if (m) {
strand = strand.replace(m, '');
resolve(true);
} else {
reject();
}
});
}
function findHead() {
return findGlobe().then(findEyes);
}
function findBody() {
return findGlobe().then(findLegs);
}
function findGlobe() {
return findMatch(/(A+(TAC|CAT)A)/);
}
async function findEyes() {
result.eyes = await findMatch(/T(CG*T)*AG/);
}
async function findLegs() {
result.legs = await findMatch(/CG*T/);
}
function found() {
result.dna = true;
}
function failed() {
result.dna = false;
}
function done() {
results.push(result);
}
await Promise.all([
findHead(),
findBody()
]).then(found)
.catch(failed)
.finally(done);
}
for (let i in strands) {
await analyze(strands[i], i);
}
console.log(results);
})();

for loop async in node js

Hi I have a for loop in my node js application which calls an async function. I want to check a value and decide whether a customer is found or not. But the loop iterates until the last element. Hence my error loop is not working. I want the loop to check the response and then iterate the next loop.
for loop:
for (let i = 0; i < customerlookupresponse.data.length; i++) {
var customer = customerlookupresponse.data[i];
if (customer != undefined) {
console.log("customer.id :: " + customer.id)
var accountlookUpData = {
customerId: customer.id
};
customerAccountLookUpRequest(accountlookUpData).then(data => {
console.log("----" + i + " -- " + data);
if (data && data.status === 1) {
resolve(data);
return;
}else{
reject({
status: 404,
message: "Customer not found"
});
return;
}
});
} else {
reject({
status: 404,
message: "Customer not found"
});
return;
}
}
the async function:
async function customerAccountLookUpRequest(customerLookUpData) {
var accountLookUp = config.app.url;
let data = await axios.get(accountLookUp).then(accountLookUpResult => {
for (i = 0; i < accountLookUpResult.data.length; i++) {
var requestaccount = accountLookUpResult.data[i].accountNumber;
if (requestaccount == customerLookUpData.lookupAccount) {
accountLookUpResult.data[i].customerId = customerLookUpData.customerId;
accountLookUpResult.data[i].status = 1;
return accountLookUpResult.data[i];
}
}
});
return data;
}
I am new to node js and trying to understand the concept of async await. Please help.
An async function waits for a Promise to return. The function that has the loop should be declared as async and the customerAccountLookUpRequest function should return a promise. Then use the await operator to call the function. Simple example:
class some_class {
constructor() {
}
async my_loop() {
let _self = this;
for (let i = 0; i < customerlookupresponse.data.length; i++) {
let data = await _self.customerAccountLookUpRequest(accountlookUpData);
console.log("----" + i + " -- " + data);
}
}
customerAccountLookUpRequest(customerLookUpData) {
return new Promise((resolve, reject) => {
axios.get(accountLookUp).then(accountLookUpResult => {
resolve(accountLookUpResult);
});
});
}
}

Foreach with Promise not waiting on method results

I am trying to iterate through the JSON files generated by the protractor tests. I pull all the file names into an array and call a method that opens and parses through the each file, post the results to the database and pass back a passed/failed flag.
I have tried all the examples here
Make angular.forEach wait for promise after going to next object and still get the same results.
The method is actually called, but the results are not posted to the db. I have tested the parser.parseResults on an individual file and it successfully posted to the db, so it has to have something to do with the promise not resolving correctly.
Is it not possible to do something like this in the jasmine/protractor framework? Or do I have something wrong in the code?
I have included the code for my latest attempt.
Thank You
Christine
matches.reduce(function (p, val) {
console.log('val', val);
return p.then(function () {
return parser.parseResults(val);
});
}, Promise.resolve()).then(function (finalResult) {
console.log('finalResult = ', finalResult);
}, function (err) {
console.log('error in reduce',err);
});
parser.parseResults code
protractorParser.prototype.parseResults = function (fileName) {
return new Promise((resolve, reject) => {
console.log('In parseresults', fileName);
json.readFile(fileName, function (err, obj) {
try {
if (err != null) {
console.log('error reading file',err);
reject(err);
}
console.log('obj - ',obj);
var results = [];
var Passed = 0;
var Message = '';
var Stack = '';
for (var suite in obj) {
var specs = obj[suite].specs;
console.log('spec - ', specs);
if (specs.length > 0) {
for (var i = 0; i < specs.length; i++) {
var assert = specs[i];
var tcR = new RegExp(/TC[\d]+/);
var tc = assert.description.match(tcR);
if (!assert.failedExpectations.length) {
Passed = 1;
}
else {
assert.failedExpectations.forEach((expectation) => {
Message = expectation.message;
Stack = expectation.stack.split('\n')[1].trim();
})
Passed = 0;
}
if (tc != null) {
utility.TestDataManager.insertAutomationResults(tc[0], assert.description, Passed, process.env.testBuild,
'P', Message, Stack, 0, moment().utcOffset(config.get('settings.timeOffset')).format('YYYY-MM-DDTHH:mm:ss'), '')
.then(function (resp) {
resolve(Passed);
}, (err) => {
console.log('Posting to Database failed ', err);
reject(err);
});
} else {
console.log('no test case found for test: ' + assert.description + ' -- skipping');
reject(err);
}
}
}
}
}
catch (err) {
console.log('rejecting opening file');
reject(err);
}
});
})
}
If there is not exactly one suite in the obj, with exactly one spec, then your promise is either resolved not at all or multiple times.
Avoid wrapping too many things in the new Promise constructor - always promisify on the smallest possible level, and use promise chaining afterwards.
protractorParser.prototype.parseResults = function (fileName) {
return new Promise((resolve, reject) => {
console.log('In parseresults', fileName);
json.readFile(fileName, function (err, obj) {
if (err != null) {
console.log('error reading file', err);
reject(err);
} else {
resolve(obj);
}
});
}).then(function(obj) {
console.log('obj - ',obj);
var results = [];
for (var suite in obj) {
var specs = obj[suite].specs;
console.log('spec - ', specs);
for (let i = 0; i < specs.length; i++) {
const assert = specs[i];
const tcR = /TC[\d]+/;
const tc = assert.description.match(tcR);
let Passed = 1;
let Message = '';
let Stack = '';
if (assert.failedExpectations.length) {
const expectation = assert.failedExpectations[assert.failedExpectations.length-1];
Passed = 0;
Message = expectation.message;
Stack = expectation.stack.split('\n')[1].trim();
}
if (tc != null) {
const time = moment().utcOffset(config.get('settings.timeOffset')).format('YYYY-MM-DDTHH:mm:ss');
const promise = utility.TestDataManager.insertAutomationResults(tc[0], assert.description, Passed, process.env.testBuild, 'P', Message, Stack, 0, time, '');
results.push(promise.catch(err => {
console.log('Posting to Database failed ', err);
throw err;
}));
} else {
console.log('no test case found for test: ' + assert.description + ' -- skipping');
// I don't think you want to `throw err` here, right?
}
}
}
return Promise.all(results);
});
};

How to guarantee that asynchronous return values are saved before function returns?

As I understand it the 100 asyncFunctions in the code below will not be executed until after func has returned true, and at this point the referens to i will not be valid. Nothing in this code would work as expected I guess.
Psedo example code:
function func(){
var needToKnow = []
for i = 1 to 100 {
needToKnow[i] = asyncFunction( i )
}
//Do some work on needToKnow[i]
return true
}
What would be the Javascript way to do something like this?
Use callbacks:
function func(callback) {
var needToKnow = [],
max = 100;
for (var i = 0; i < max; i++) {
asyncFunction(i, function (result) {
needToKnow.push(result);
if (needToKnow.length == max) { // or something that let you know that its finished
callback(needToKnow);
}
});
}
}
function asyncFunction(i, callback) {
setTimeout(function () {
callback({ index: i });
}, 1000); // Im an async func!
}
And use it this way:
func(function (result) {
console.log(result);
});
Be careful, don't get in callback hell
Here is an example using the Q Promise library:
function functionThatCouldThrowError(i){
//It doesn't, but just to give an idea of error propagation.
return { index: i };
}
function asyncFunction(i) {
var deferred = Q.defer();
setTimeout(function () {
try{
var data = functionThatCouldThrowError(i);
deferred.resolve(data);
} catch (error) {
deferred.reject({ index: i, error: error });
}
}, 1000);
return deferred.promise;
}
function doAll() {
var needToKnow = []
for (var i = 0; i < 100; i++) {
needToKnow[i] = asyncFunction( i );
}
return Q.all(needToKnow);
}
doAll().then(function(arg) {
//arg contains all 100 elements
alert("All done");
})
Update: expanded the example to demonstrate how to handle errors.
Plunker:http://plnkr.co/edit/djWpTKxgvzK2HmkVwvTy?p=preview

Multiple user inputs using Nodejs

I'm trying to write a script that asks three questions in a row, while waiting for user input in between each question.
It seems that I have difficulty understanding how to do this with the non-blocking nature of node.
Here's the code I'm running:
var shell = require('shelljs/global'),
utils = require('./utils'),
readline = require('readline'),
fs = require('fs'),
path = require('path');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
setUserDefaultSettings();
function setUserDefaultSettings() {
var defaultSettingsFile = find('DefaultSettings.json');
var defaultSettingsJSON = [
{
macro: '__new_project_path__',
question: 'Please enter the destination path of your new project: '
},
{
macro: '__as_classes_path__',
question: 'Please enter the path to your ActionScript Classes: ',
},
{
macro: '__default_browser_path__',
question: 'Please enter the path to your default browser: '
}
];
var settingsKeys = [];
var index = 0;
if (!test('-f', 'UserSettings.json')) {
cp(defaultSettingsFile, 'UserSettings.json');
}
var userSettingsFile = pwd() + path.sep + find('UserSettings.json');
fs.readFile(userSettingsFile, 'utf8', function (err, data) {
if (err) {
echo('Error: ' + err);
return;
}
data = JSON.parse(data);
for(var attributename in data) {
settingsKeys.push(attributename);
}
defaultSettingsJSON.forEach(function(key) {
index++;
// Check if macros have been replaced
if (data[settingsKeys[index - 1]] === key.macro) {
// Replace macros with user input.
replaceSettingMacro(userSettingsFile, key.macro, key.question);
}
});
});
}
function replaceSettingMacro(jsonFile, strFind, question) {
askUserInput(question, function(strReplace) {
sed('-i', strFind, strReplace, jsonFile);
});
}
function askUserInput(string, callback) {
rl.question(string, function(answer) {
fs.exists(answer, function(exists) {
if (exists === false) {
echo('File ' + answer + ' not found!');
askUserInput(string, callback);
} else {
callback(answer);
}
});
});
}
Only the first question is asked, as the script continues execution while the user is inputting their answer. I understand why this is the case, but don't know how I can work around this.
This looks like an appropriate place to implement a queue. The queue will initiate tasks one at a time only starting the next after the previous has finished.
in your askUserInput method try something like:
var questionQueue = [];
function askUserInput(string, callback) {
if(questionQueue.length == 0) {
rl.question(string, function(answer) {
fs.exists(answer, function(exists) {
if (exists === false) {
echo('File ' + answer + ' not found!');
askUserInput(string, callback);
} else {
callback(answer);
if(questionQueue.length > 0){
var question questionQueue.shift();
askUserInput(question.string, question.callback);
}
}
});
});
}
else {
questionQueue.push({string: string, callback: callback});
}
}
Another option included extending your callback further up the call stack and invoke the next question when you receive the callback from the previous.
The 2 ways I'd handle this:
a) use sync-prompt - would make it very easy
If you still wanted to handle it asynchronously,
b) I'd use a promise library such as When and do a When.all([array of promises]) before continuing.
Below is the correct solution to my question, following #Preston-S advice:
var questionList = [];
var macroList = [];
setUserDefaultSettings();
function setUserDefaultSettings() {
var defaultSettingsFile = find('DefaultSettings.json');
var defaultSettingsJSON = [
{
macro: '__new_project_path__',
question: 'Please enter the destination path of your new project: '
},
{
macro: '__as_classes_path__',
question: 'Please enter the path to your ActionScript Classes: ',
},
{
macro: '__default_browser_path__',
question: 'Please enter the path to your default browser: '
}
];
if (!test('-f', 'UserSettings.json')) {
cp(defaultSettingsFile, 'UserSettings.json');
}
userSettingsFile = pwd() + path.sep + find('UserSettings.json');
var settingsKeys = [];
var index = 0;
var canAskQuestion = false;
fs.readFile(userSettingsFile, 'utf8', function (err, data) {
if (err) {
echo('Error: ' + err);
return;
}
data = JSON.parse(data);
for(var attributename in data) {
settingsKeys.push(attributename);
}
defaultSettingsJSON.forEach(function(key) {
index++;
// Check if macros have been replaced
if (data[settingsKeys[index - 1]] === key.macro) {
// Replace macros with user input.
questionList.push(key.question);
macroList.push(key.macro);
if (!canAskQuestion) {
askQuestion(function() {
copyTemplate();
});
canAskQuestion = true;
}
} else {
copyTemplate();
}
});
});
}
function askQuestion(callback) {
replaceSettingMacro(userSettingsFile, macroList.shift(), questionList.shift(), function() {
if (macroList.length < 1 || questionList.length < 1) {
callback();
} else {
askQuestion(callback);
}
});
}
function replaceSettingMacro(jsonFile, strFind, question, callback) {
askUserInput(question, function(strReplace) {
sed('-i', strFind, strReplace, jsonFile);
callback();
});
}
function askUserInput(string, callback) {
rl.question(string, function(answer) {
fs.exists(answer, function(exists) {
if (exists === false) {
echo('File ' + answer + ' not found!');
askUserInput(string, callback);
} else {
callback(answer);
}
});
});
}
function copyTemplate() {
rl.close();
}

Categories