Node.js 'Not Found' callback for async.detect? - javascript

I have a file path list file_paths, and I want to detect which file exists.
If any file exists, I want to read that one. Otherwise call another function,
not_found for example.
I wish to use async.detect but I found no way to add a 'Not Found' callback
when all the iterators return false.
I've tried this one, but no work. Returned undefined and nothing outputted.
async = require 'async'
async.detect [1,2,3], (item, callback) ->
callback true if item == 4
, (result) ->
console.log result ? result : 'Not Found'
If there's another way to do it, please add it to the answer.

from the documentation you mentioned.
in case of detect(arr, iterator, callback)
callback(result) - A callback which is called as soon as any iterator
returns true, or after all the iterator functions have finished.
Result will be the first item in the array that passes the truth test (iterator) or the value undefined if none passed.
from your question you want to find a way to detect if no file in list is found, which could be done by comparing the result with undefined and checking whether this condition is true.
like
async.detect(['file1','file2','file3'], fs.exists, function(result){
if(typeof(result)=="undefined") {
//none of the files where found so undefined
}
});

I would use async.each and use fs.exists to detect if the file exists. If it exists, then read the file otherwise, call the not found function then proceed to the next item. See sample code I have written on top of my head below.
async.each(file_paths, processItem, function(err) {
if(err) {
console.log(err);
throw err;
return;
}
console.log('done reading file paths..');
});
function notFound(file_path) {
console.log(file_path + ' not found..');
}
function processItem(file_path, next) {
fs.exists(file_path, function(exists) {
if(exists) {
//file exists
//do some processing
//call next when done
fs.readFile(file_path, function (err, data) {
if (err) throw err;
//do what you want here
//then call next
next();
});
}
else {
//file does not exist
notFound(file_path);
//proceed to next item
next();
}
});
}

Related

define outer scope variable inside function

i am building validation for one of form's field serverside (expressjs) and doing following actions for that:
Read data from json file
Get property from it (Array)
Check if it contains every single element of user generated array and nothing more, for example:
[1,2,3,4,5]; (json array)
[1,2,3,4,5,6] (user generated array) //must return false
[1,2,3,4,5];
[1,3,4] //must return true;
[1,2,3,4,5];
[1,2,7] //must return false;
so i am using this code for that:
const contains = (arr1, arr2) => {
arr2.every(v => arr1.indexOf(v) !== -1)
}
var match;
fs.readFile('../tags.json', 'utf8', (err, data)=>{
var JsonData = JSON.parse(data);
var tagsArray = JsonData.tags;
console.log(tagsArray)
console.log(tags)
if(tagsArray instanceof Array){
console.log('tagsArray is array')
}
if(!contains(tagsArray, tags)){
match = false
}
else{
match = true
}
console.log(match + ' blah1')
});
console.log(match + ' blah2')
if(match == false){
return res.status(409).send({
message: 'Do not provide your own tags'
});
}
but it always returns false inside fs.readFile block because it returns undefined outside fs.readFile block, so this means that contains function return undefined (i tested it)
so what is the clue for this?
Thanks!
fs.readFile is asynchronous, so any code that depends on its result (the file being read) needs to go within your callback function. (The callback function is the (err, data) => { ... } part.)
Move the console.log(match + 'blah2') and if(match == false) { ... } parts inside of the callback (after the blah1 line).
You could also look into async or use fs.readFileSync which would allow you to avoid using callback functions.
Another side point, you will want to make sure you always reach a res.send() line, i.e. when match == true in your case. Otherwise your http request will not return when match is true.
Edit:
Here's a really basic structure for express, mostly pseudocode & comments, just to illustrate callbacks:
app.post('/tags', (req, res) => {
// your setup code here
fs.readFile('../tags.json', 'utf8', (err, data) => {
console.log('readFile has finished')
// now you have heard back from readFile
// check the err and send 500 if there was a problem
// otherwise work with the file in the var data
// any other db-related stuff also goes in here, which probably
// has its own callback you need to use
db.save(data, (err) => {
// db call is done, potentially with an error
// Here you can use `res` to send http response
})
// !! again here the db is still doing its work
})
// !! anything you add here will be executed before readFile is done
console.log('readFile is probably still at work')
})
I should also point out that you want contains to return the bool value, i.e. return arr2.every(...)
You can use async/await :
async function read(){
let data = await fs.readFile(<path>);
console.log(data); //You can use data everywhere within this scope
}

Trying to wait for list to be populated before iterating through it NodeJS

I have some code that takes an uploaded file, executes it and gets the output. It then compares this output against expected output to check if the script did as expected.
I am now trying to improve this functionality so that an uploaded file will be run several times, each time being checked against a different expected output, or "test case". I then want to push "correct" or "incorrect" onto a results array, so that I can go through that array at the end and check whether there are any "incorrect" (whether the file failed any test case).
I have tried just callbacks within each function.
I have tried using await and async on the getArray as seen below
Using both callbacks and async together.
This is the parent function code that calls for the array to be created, and wants to iterate through it after it has been created.
var resultsArr = await getResults(file.name, files.length, markerDir);
//file.name is the name from the uploaded file object
//files.length is the number of subdirectories (number of testcases to run against)
//markerDir is the str path to where these testcases are stored
if (resultsArr){
for(var i=0;i<resultsArr.length;i++) {
if (resultsArr[i] == "incorrect"){
checkForMarkerCb('incorrect'); //Calls back to frontend to
break; //display result on web app
}
else if (i+1 == resultsArr.length) {
checkForMarkerCb('correct');
}
}
}
The following is inside the getResults function that is called above
for(var i=1; i<=fileLength; i++) {
var sampleOut = markerDir + '/test' + i + '/stdout.txt';
//Grab expected stdout.txt
var markerOut = fs.readFileSync(sampleOut, 'utf-8', function(err){
if (err){
throw err;
};
});
//Run the file and grab the output
executeFile(filename, function(fileOut){
//Compare output with sample stdout
if (markerOut == fileOut){
resultsArr.push('correct');
}
else {
resultsArr.push('incorrect');
}
});
}
//If results array has a response for each testcase
if (resultsArr.length == fileLength) {
return resultsArr;
}
Implementation of executeFile() as requested:
function executeFile(filename, execFileCb){
//pathToUpload is a str path to where the upload is stored
const child = execFile('python', [pathToUpload], (err,stdout,stderr) => {
if (err) {
throw err;
}
execFileCb(stdout); //Callback with output of file
});
}
function executeFileAsync(filename) {
return new Promise(function(resolve,reject){
executeFile(filename, function(err, data){
if (err !== null) reject(err);
else resolve(data);
});
});
}
which was called inside getResults() using
var fileOut = await executeFileAsync(filename)
The initial function that calls getResults().
getResults(): which gets the path to each directory and calls pushes the results of comparing outputs onto a results array.
executeFile(): uses 'child_process' to run a file and calls back with the output.
I expect the code to wait for getResults to return with the resultsArr so that the for loop can iterate through and check for any "incorrect". Instead, getResults returns before resultsArr is populated.
Using some logging, I see that the code for checking markerOut == fileOut is executed at the end after the getResults() for loop has already completed. I tried setting up the call to executeFile() to also be an async/await similar to how getResults() is called but still no change.
I may not be using async/callbacks correctly, any help is greatly appreciated.
Your executeFileAsync function currently calls executeFile with a callback that is expecting two arguments, but executeFile then does call this execFileCb always with only one argument which is interpreted as an error. It also should not use throw in an asynchronous callback.
Instead, merge them into one function:
function executeFile(filename) {
return new Promise(function(resolve,reject){
//pathToUpload is a str path to where the upload is stored
const child = execFile('python', [pathToUpload], (err,stdout,stderr) => {
if (err) reject(err);
else resolve(stdout); //Callback with output of file
});
});
}

Get Value from parameter function

Sorry for the Noob Question. I'm trying to write a node.js function called "getTransitionId" that uses the jira-connector plugin to retrieve data for possible transitions for a particular issue.
The getTransitions function from the jira-connector takes two parameters: an object with the parameters of the ticket, and a function to execute once the call is finished.
Here's my problem: for reasons beyond the scope of this question, I want to access this data outside the function that's being passed as parameter to "getTransitions." But I can't figure out how. I understand that the last return statement (return "transitionData") is returning "undefined" because it's executing a return statement before the call is finished, but I don't know how to fix that.
Can I correctly use a callback in this case? If so, how would I use it in a function that is being passed as a parameter to another function?
const JiraApi = require('jira-connector');
const jira = new JiraApi( {
host: //Jira URL
basic_auth: {
//Authentication Information
}
});
function getTransitionId (ticketNumber, transition) {
jira.issue.getTransitions({
issueKey: ticketNumber,
}, function(error, transitions){
const transitionData = transitions['transitions'];
});
return transitionData;
}
Thanks for the help. Hope this made sense.
You could make your own getTransitionId function take a callback function as an argument. Here's an incomplete example (see ahead):
function getTransitionId (ticketNumber, transition, callback) {
jira.issue.getTransitions({
issueKey: ticketNumber,
}, function(error, transitions){
const transitionData = transitions['transitions'];
const id = /* ..get ID fron transitionData, somehow.. */
callback(id);
});
}
// Called like this:
getTransitionId(ticketNumber, transition, function(id) {
console.log("Got the ID:", id);
});
This isn't perfect, though. What if getTransitions has an error?
When you call jira.issue.getTransitions, you pass a callback function which takes two parameters: error and transitions. This is standard for functions which take callbacks in JavaScript - that is, callbacks usually take an error parameter (null or undefined if there was no error) and a data parameter (containing results of the action, like fetched transitions or an id).
We can change getTransitionId to take an error and then pass the error to the callback that you gave to getTransitionId:
function getTransitionId (ticketNumber, transition, callback) {
jira.issue.getTransitions({
issueKey: ticketNumber,
}, function(error, transitions){
if (error) {
callback(error);
return;
}
const transitionData = transitions['transitions'];
const id = /* ..get ID fron transitionData, somehow.. */
callback(null, id);
});
}
(Note that we use a return; statement inside if (error) -- that's so that we don't continue and try to use the transitions argument, which is probably undefined, since there was an error in jira.issue.getTransitions. This also prevents callback from being called a second time.)
Since we've added an error argument, we need to change how we call getTransitionId:
getTransitionId(ticketNumber, transition, function(error, id) {
if (error) {
console.error("There was an error fetching the transition ID:", error);
return;
}
console.log("Got the ID:", id);
}
(Since we do callback(null, id); in the code for getTransitionId, error will be null, so the code in if (error) { won't run. Of course, if there is an error, if (error) { will be run, and that's what we want!)
By adding code to handle errors passed to your callbacks, you make your code safer. Contrastingly, if you ignore error, you might end up having errors in your code that are hard to find - for example, you might have a TypeError because transitions is undefined, or see "Got the ID: undefined" in the log!

Break out of async completely if condition met

How to make a async.each loop break and return a specific value / error when a condition is met. For instance, in this case, if status exists i wish to return something from the function 'check' rather than iterating through rest of the items. Any suggestions?
const check = (condition) => {
if (condition === 100) {
async.each(items, (item, callback) => {
if (status) {
callback(Boom.conflict('Conflict'));
} else {
// some logic
callback(destroyInstance);
}
});
}
};
This looks like a duplicate of this question:
Break out of javascript nested async.each loop but continue main loop
Check out the answer, it should help you. In that case it is a nested async inside another async loop, but the answer should still work.
Edit:
The solution, as indicated in that thread but without the extra layers, is as follows:
async.each(subArray, function(item, callback) {
if (SOME_CONDITION) {
// Break out of async
var err = new Error('Broke out of async');
err.break = true;
return callback(err);
}
// continue
callback();
}, function(err) {
if (err && err.break) {
// Handle break out of async here
} else {
// Otherwise handle here
}
});
As async.each works parallely on each item in items array. It does not looks to be possible to stop the action at any condition, because the order of execution of items are not deterministic here. I guess you should use async.eachSeries instead.
So whenever the condition is met what you should do is throw an error (return callback(err)), what this will do is it will stop executing further. And if you want to send some value instead of throwing and error, You can just pretend to send an error and while checking in the main callback of async.eachSeries you just check the type of value u have got from the iteratee callback function as u can see for example:
async.eachSeries(items, function iteratee(item, callback) {
if (status) {
return callback(E/V); // error(E) or value(V) u want to send to the done function
} else {
// some logic
callback();
}
}, function done(e) {
if(e){
if(type of e === TYPE OF RETURN VALUE U WANTED) // or any other logic u can come up with
return cb(null, e);
else return cb(e);
}
else cb(null, //...);
});
Tell me if it works.

How do I run an asynchronous 'find' in a loop while incrementing the find parameter so I can generate unique custom id's?

I'm new to mongoose/mongodb and I am trying to do some sort of error handling with my document save.
I am trying to create a stub id to store into the db for easier data retrieval later on (and also to put into the url bar so people can send links to my website to that particular page more easily -- like jsfiddle or codepen).
Basically I want to search for a document with a page_id and if it exists, I want to regenerate that page_id and search until it gets to one that's unused like this:
while(!done){
Model.findOne({'page_id': some_hex}, function (err, doc) {
if(doc){
some_hex = generate_hex();
}
else
{
done = true;
}
});
}
model.page_id = some_hex;
model.save();
However, since mongoose is asynchronous, the while loop will pretty much run indefinitely while the find works in the background until it finds something. This will kill the resources on the server.
I'm looking for an efficient way to retry save() when it fails (with a change to page_id). Or to try and find an unused page_id. I have page_id marked as unique:true in my schema.
Retrying should be performed asynchronously:
var tryToSave = function(doc, callback) {
var instance = new Model(doc);
instance.page_id = generate_hex();
instance.save(function(err) {
if (err)
if (err.code === 11000) { // 'duplicate key error'
// retry
return tryToSave(doc, callback);
} else {
// another error
return callback(err);
}
}
// it worked!
callback(null, instance);
});
};
// And somewhere else:
tryToSave(doc, function(err, instance) {
if (err) ...; // handle errors
...
});

Categories

Resources