Save params from the first call of a recursive function - javascript

I have a function that searches files in folders and recursivly calles itself if a subfolder occurs.
I want to optimize the search algo in that way that i can store the returned data and it's corresponding parameters.
So if a new search is issued. I can check if an equal search was made before and return the saved result instead of doing a new search.
My approach was to push the params into the resulting array at first or last. But this has to happen only one time in the whole recursion process.
This is my function:
/**
* List all files that the matcher has hit
* #param {String} start path from where to start the search
* #param {Object} [options] search options
* #param {RegExp} [options.matcher] expression to match while searching
* #param {Boolean} [options.folders] search in subfolders, default = true
* #returns {Array} files that the matcher has hit
*/
list(start, { matcher, folders = true } = {}) {
if (!fs.existsSync(start)) throw new Error(`${start} doesn't exists.`)
const dir = fs.readdirSync(start)
const files = []
for (let iCnt = 0; iCnt < dir.length; iCnt++) {
const item = path.resolve(start, dir[iCnt])
let stat = fs.statSync(item)
switch (true) {
case stat.isDirectory() && folders:
files.push(...list(item, { matcher, folders }))
break
case matcher && matcher.test(item):
files.push(item)
break
case !matcher:
files.push(item)
break
}
}
return files
}
I thought a lot about it. But can't get my head around.
Does anyone have an idea?

When the first call in a recursive sequence is special, I usually handle it by making the recursive part a worker function, and making the main function a wrapper for it that does the special part.
In your case, that would mean renaming your existing list (perhaps to listWorker) and making a wrapper list function that does the caching. Roughly:
function list(start, { matcher, folders = true } = {}) {
let result = getFromCache(/*...*/);
if (!result) {
result = listWorker(start, {matcher, folders});
putInCache(/*...*/, result);
}
return result;
}

Related

How do I write this function without duplicating code, following the DRY principle

I want to start improving my way of writing code without duplicating it, the famous clean code, I would like to start applying it to small functions that are already used.
How could I improve this code without replicating the switch condition or even creating a class and using the polymorphism as the functions are super similar
/**
* Wait for an element and type on it
* #param {puppeteer.Page} page - Context page
* #param {string} selector - Element selector (CSS, xpath)
* #param {string} text - Text to be typed in the element
* #param {string} waitType - Element type (CSS, xpath)
*/
const waitAndType = async (page, selector, text, waitType = 'selector') => {
switch (waitType) {
case 'selector':
await page.waitForSelector(selector)
break
}
await page.type(selector, text)
}
// ===========================================
/**
* Wait for an element and click on it
* #param {puppeteer.Page} page - Context page
* #param {string} selector - Element selector (CSS, xpath)
* #param {string} waitType - Element type (CSS, xpath)
*/
const waitAndClick = async (page, selector, waitType = 'selector') => {
switch (waitType) {
case 'selector':
await page.waitForSelector(selector)
break
case 'xpath':
await page.waitForXPath(selector)
break
}
await page.click(selector)
}
// ===========================================
// ===========================================
module.exports = {
waitAndType,
waitAndClick,
}
// ===========================================
You can pass an extra parameter to your function something like actionType. It can have two or more possible values based on your needs (type and click).
You can rewrite your function as follows:
const waitAndPerformAction = async (page, selector, waitType = 'selector', actionType) => {
switch (waitType) {
case 'selector':
await page.waitForSelector(selector)
break
case 'xpath':
await page.waitForXPath(selector)
break
}
if(actionType === 'type') {
await page.type(selector)
} else if(actionType === 'click`) {
await page.click(selector)
}
}
You can take it a little further and define your action in a separate file (maybe a constants file) where you can define all your action types to avoid any typos throughout your codebase.
Something like:
const ACTION_TYPES = {
CLICK: 'click',
TYPE: 'type',
....
}
And then pass ACTION_TYPES.CLICK or ACTION_TYPES.TYPE as an argument to your function and modify your waitAndPerformAction function.
const waitAndPerformAction = async (page, selector, waitType = 'selector', actionType) => {
switch (waitType) {
case 'selector':
await page.waitForSelector(selector)
break
case 'xpath':
await page.waitForXPath(selector)
break
}
await page[ACTION_TYPES[actionType]]();
}
And, finally call your function as:
waitAndPerformAction(..other arguments, ACTION_TYPES.CLICK)

Get siblings of created file in firebase cloud function triggered by onFinalize

I have a cloud function that triggers when new file is created in firebase storage. Inside this function logic I need to collect every other file located at the same path in array. But I don't know how.
exports.testCloudFunc = functions.storage.object().onFinalize(async object => {
const filePath = object.name;
const { Logging } = require('#google-cloud/logging');
console.log(`Logged: ${filePath}`);
let obj = JSON.stringify(object);
console.log(`Logged: ${obj}`);
});
After that I will try to merge all PDFs in one new file and save it back to firebase storage by the same path. Any help is highly appretiated! Thank you in advance for your wisdom )
As per the documentation Doug Stevenson linked (2nd code sample for Node.js), you can list objects from a specified folder within your bucket using prefixes and delimiters.
Sample from the mentioned documentation:
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// const bucketName = 'Name of a bucket, e.g. my-bucket';
// const prefix = 'Prefix by which to filter, e.g. public/';
// const delimiter = 'Delimiter to use, e.g. /';
// Imports the Google Cloud client library
const {Storage} = require('#google-cloud/storage');
// Creates a client
const storage = new Storage();
async function listFilesByPrefix() {
/**
* This can be used to list all blobs in a "folder", e.g. "public/".
*
* The delimiter argument can be used to restrict the results to only the
* "files" in the given "folder". Without the delimiter, the entire tree under
* the prefix is returned. For example, given these blobs:
*
* /a/1.txt
* /a/b/2.txt
*
* If you just specify prefix = '/a', you'll get back:
*
* /a/1.txt
* /a/b/2.txt
*
* However, if you specify prefix='/a' and delimiter='/', you'll get back:
*
* /a/1.txt
*/
const options = {
prefix: prefix,
};
if (delimiter) {
options.delimiter = delimiter;
}
// Lists files in the bucket, filtered by a prefix
const [files] = await storage.bucket(bucketName).getFiles(options);
console.log('Files:');
files.forEach(file => {
console.log(file.name);
});
}
listFilesByPrefix().catch(console.error);
Does that mean that all files will be returned at first and then will
be filtered by prefix?
As I see in the code sample above, the array [files] will store the objects that already pass the filter requirements:
const [files] = await storage.bucket(bucketName).getFiles(options);

How to include js files in header of wordpress pages that are activated on-click

I am attempting to use wordpress to build a website that integrates google maps. I am doing some overlays with the maps and use the google developers API and Python to make the appropriate javascript. I have successfully written the js files and Python necessary to accomplish this.
My website is built in Worpress and I would like add a page (not the home page) that has n links and each one would populate a box with the corresponding map. I can take care of the layout and design issues but I am at a loss on how to:
a) Include the javascript as a file that
b) gets called upon clicking the link and thus populates that map without calling a new page
That is, the javascript is HUGE because it may include thousands of lat/lon points. Therefore including n of these written into the header is unreasonable. I want to simply call it from filename.js when the link is clicked.
There is a plugin that allows me to include whatever I want in the header. So, if I can find out where to put the *.js files (or txt file) in the directory tree and how to have the corresponding file activated upon click I should be good. Thanks!
This Display different maps with onClick event - Google Maps V3. kind of helps with doing an on-click display but everyone's solution was to make one map. I cannot do that. I am overlaying vast amounts of data.
Here is a way you can get that done. (Jump down to the get started part of the script.)
For brevity, I've included a bunch of scripts in one 'file', but you'll want to break them in to individual files.
You may also need to try the html and js in jsbin js bin example, b/c SO may or may not allow the dynamic loading of js.
(function(undefined) {
/**
* #author (#colecmc)
* #method turn collection into an array
* #param {object} collection - NodeList, HTMLCollection, etc. Should have an "item" method and/or a "length" property
*/
ToArray = collection => Array.prototype.slice.call(collection);
/** \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ **/
Observer = (function(undefined) {
/**
* pub sub
*/
'use strict';
var subUid = -1;
return {
topics: {},
subscribe: function(topic, func) {
/**
* #param {string} topic
* #param {function} func
* #returns {string} - a token such as '3'
* #example Observer.subscribe('any-valid-string',function(name,resp){
console.log(resp.prop);
});
*/
if (!Observer.topics[topic]) {
Observer.topics[topic] = [];
}
var token = (++subUid).toString();
Observer.topics[topic].push({
token: token,
func: func
});
return token;
},
publish: function publish(topic, args) {
/**
* #param {string} topic
* #param {object} args
* #returns {boolean} - true if topic is valid, false otherwise
* #example Observer.publish('any-valid-string',{
prop: 'this is a test'
});
*/
if (!Observer.topics[topic]) {
return false;
}
setTimeout(function() {
var subscribers = Observer.topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
}, 0);
return true;
},
unsubscribe: function unsubscribe(token) {
/**
* #param {string} token - value should be saved from the original subscription
* #example Observer.unsubscribe('2');
* #example Observer.unsubscribe(member); - where member is the value returned by Observer.subscribe();
*/
var m,
forEachTopic = function(i) {
if (Observer.topics[m][i].token === token) {
Observer.topics[m].splice(i, 1);
return token;
}
};
for (m in Observer.topics) {
if (Observer.topics.hasOwnProperty(m)) {
Observer.topics[m].forEach(forEachTopic);
}
}
return false;
}
};
}(undefined));
/** \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ **/
SetAttributes = function(el, attrs) {
/**
* #author (#colecmc)
* #method simple for in loop to help with creating elements programmatically
* #param {object} el - HTMLElement attributes are getting added to
* #param {object} attrs - object literal with key/values for desired attributes
* #example SetAttributes(info,{
* 'id' : 'utswFormInfo'
* 'class' : 'my-class-name'
* });
*/
'use strict';
var key;
for (key in attrs) {
if (attrs.hasOwnProperty(key)) {
el.setAttribute(key, attrs[key]);
}
}
return el;
};
/** \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ **/
GetScript = function(url, fullPath) {
/**
* #author (#colecmc)
* #version 1.0.4
* #requires Swlxws.SetAttributes, Swlxws.Observer
* #method dynamically add script tags to the page.
* #param {string} url - relative path and file name - do not include extension
* #param {string} fullPath - absolute path
* #example GetScript('myLocalScript');
* #example GetScript('','https://www.google-analytics.com/analytics.js');
*/
'use strict';
function onLoad(event) {
var result;
if (event.type === 'load') {
result = 1;
} else {
result = -1;
}
Observer.publish('get-script-onload-complete', {
successful: result,
eventData: event
});
}
var JSPATH = '/js/',
/* or where ever you keep js files */
el = document.createElement('script'),
attrs = {
defer: true,
src: null,
type: 'text/javascript'
};
/** look for a string based, protocol agnostic, js file url */
if (typeof fullPath === 'string' && fullPath.indexOf('http') === 0) {
attrs.src = fullPath;
}
/** look for any string with at least 1 character and prefix our root js dir, then append extension */
if (typeof url === 'string' && url.length >= 1) {
attrs.src = JSPATH + url + '.js';
}
SetAttributes(el, attrs);
el.addEventListener('load', onLoad);
el.addEventListener('error', onLoad);
document.body.appendChild(el);
return el;
};
/** \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ **/
/**
* Get Started
*/
function onClick(event) {
GetScript('', event.target.dataset.namespaceUrl);
}
Observer.subscribe('get-script-onload-complete', function(name, resp) {
/** check to make resp is what you expect, ie: the correct script loaded */
/** then it is safe to use */
});
ToArray(document.querySelectorAll('.load-scripts')).map(script => script.addEventListener('click', onClick, false));
}(undefined));
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>How to include js files in header of wordpress pages that are activated on-click</title>
</head>
<body>
Load Google Analytics
</body>
</html>
You can use the function wp_enqueue_script() to load the necessary JS files on only the templates you want.
As for your large data set, I recommend that you cache it in an external .json file and use wp_enqueue_script() to load it only when necessary.
Well if the onclick event suggestion is pretty much what you want and you are just concerned about the large amount of data. Then there are a few ways to tackle it. I am not sure if the dataset is a js file or php/json files but i came across a similar issue on one of my projects, dont remember properly but i was doing something with maxmind's ip/location data set.
So i just splitted the large file into 3 smaller ones. Then i looped through each of the file and if the stuff that i was looking for was found in the file then i just breaked out. And definitely as Brian suggested caching and using a CDN would help a lot.

How do I generalize this set of Javascript methods, including promises?

I have the following code setup to initialize a single field's autosuggest feature using jQuery and MagicSuggest. It's relatively straight forward. I have modularized a bit of it because I intend on using it to initialize other fields as well with MagicSuggest. One extraneous part is the canonical name conversion, but it's a necessary function for this particular data set I'm working with. (Problem I'm having trouble with explained below ...)
/**
* Initialize Flights From autosuggest feature
* #return {void}
*/
function initFlightsFromAutosuggest() {
// Flights From Typeahead *************************************
var msField = $('#magicsuggest.direct_flights_from');
var ms = msField.magicSuggest({
id : 'direct_flights_from',
name : 'direct_flights_from',
minChars : 1,
highlight : false,
valueField : 'id',
displayField : 'name',
placeholder : getMSPlaceholder(msField, 'City'),
resultAsString: true,
useTabKey : true,
useCommaKey : true,
useZebraStyle : true,
hideTrigger : true,
sortOrder : 'canonical_name',
maxDropHeight : 500,
data : '/api/v1/cities',
defaultValues : msField.attr('data-default').split(','),
renderer : function(data) { return convertCanonical(data.canonical_name) }
});
// Once loaded, add pre-selected values if there are any
$(ms).on('load', addDefaults(ms, msField));
}
/**
* Gets placeholder value for MagicSuggest instances
* #param {element} el DOM element
* #param {string} defaultString Default string to use
* #return {string}
*/
function getMSPlaceholder(el, defaultString) {
if (el.attr('data-default').length > 0) {
return '';
}
return defaultString;
}
/**
* Converts canonical name into city, state string (dropping country, fixing spacing)
* #param {string} canonical_name Full canonical name
* #return {string} Short name, without country
*/
function convertCanonical(canonical_name) {
if (typeof canonical_name !== 'undefined') {
canonical_name = canonical_name.replace(',United States', '');
canonical_name = canonical_name.replace(',', ', ');
return canonical_name;
}
// Not sure what to do if it's undefined
return;
}
That all said, below is what I have to do to pre-populate this one field with data previously submitted.
/**
* Adds pre-selected values (ids) loaded into the 'data-default' attribute into the input field
* #param {object} ms MagicSuggest instantiation
* #param {element} msField DOM element used by MagicSuggest
*/
function addDefaults(ms, msField) {
// Get the default attribute value as an array
var defaultIds = msField.attr('data-default').split(',');
// Setup array of requests
var requests = [];
// Push all the requests into an array
$.each(defaultIds, function(index, id) {
requests.push($.getJSON('/api/v1/cities/' + id));
});
// Create a promise, and when all the requests are done (promises fulfilled)
// Send the args (json) to the .done callback
var promise = $.when.apply($, requests).then(function () {
var args = Array.prototype.slice.call(arguments);
return args.map(function(arg) { return arg[0] });
});
// Setup the callback function for 'done'
promise.done(function(json) {
// Setup results array
var results = [];
// Got an auth error from the API, so return early. No results.
if (typeof(json[0].auth) === 'object') {
return false;
}
// For each item, add the proper structure to the results array
$.each(json, function (index, id) {
results.push({
value: json[index][0]['city']['id'],
name: json[index][0]['city']['name']
});
});
var resultPromise = $.when.apply($, results).then(function () {
var args = Array.prototype.slice.call(arguments);
return args.map(function(arg) { return arg });
});
resultPromise.done(function(results) {
ms.setValue(results);
ms.setDataUrlParams({});
$('.input')
});
});
}
There has to be a way to generalize this, but I'm new at promises and $.Deferred so I've been hitting a wall of understanding.
The other fields I'll be instantiating with MagicSuggest will be using different URLs for the $.getJSON() method (probably all using IDs though) (used for finding what the user had previously chosen, thus what to pre-populate the field with), and will obviously have different JSON responses for those calls. So, the trouble spots for me are how to get this all to work together and still DRY.
As soon as I start breaking apart addDefaults() I hit problems because ms is undefined in resultPromise.done, the URLs with the IDs in them, and the json structure inside the $.each command.
How would you refactor this to be more re-usable? Comments/explanations on promises and deferred are always helpful too.
With a fresh head after a few days rest, and with the insight of this post I realized I didn't need to do all this just to add default values. Thankfully, just adding the following to the init worked perfectly: value: msField.attr('data-default').split(','), (I'm adding the values into the HTML under the data-default attribute via PHP.
Code: deleted.
Problem: solved.

Determine when recursive function is finished

I have a function that does a breadth first search over a big graph.
Currently the App runs and is finished after some time.
I want to add a finished Event to the EventEmitter.
My first idea was to implement a counter for each Recursive process.
But this could fail if some Recursive process does not call the counter-- method.
var App = function(start, cb) {
var Recursive = function(a, cb) {
// **asynchronous** and recursive breadth-first search
}
var eventEmitter = new EventEmitter();
cb(eventEmitter);
Recursive(start);
};
How can I emit the finished message if all Recursive functions are finished.
Edit App is not searching something in the graph, it has to traversal the complete graph in order to finish. And it is not known how many elements are in the graph.
Edit2 Something like computational reflection would be perfect, but it does not seem to exist in javascript.
The Graph is very unstable and I am doing a few nested asynchronous calls which all could fail.
Is there a way to know when all asynchronous recursive calls are finished without using a counter?
JavaScript is single threaded.
So unless Recursive(start); has asynchronous calls in it like setTimeout or ajax it's safe to just trigger your finished event after calling the recursive function.
General asynchronous APIs pass around a done function.
So you would have
Recursive(start, function() {
// trigger finished.
});
var Recursive = function(a, done) {
...
};
And it's upto users to call done when they are done.
Try something like this :
var App = function(start, cb) {
var pendingRecursive = 0;
var eventEmitter = new EventEmitter();
cb(eventEmitter);
var Recursive = function(a) {
// breadth-first search recursion
// before each recursive call:
pendingRecursive++;
Recursive(/*whatever*/);
// at the end of the function
if (--pendingRecursive == 0){
eventEmitter.emit('end');
}
}
pendingRecursive = 1;
Recursive(start);
};
Basically, you just increment a counter before each recursive call, and decrement it at the end of the call, so you're effectively counting the number of unfinished calls, when it's zero, you can then emit your event.
Try something like this based on Adriens answer
/**
* Function to search for a file recursively from a base directory
* returns an array of absolute paths for files that match the search
* criteria
*/
let recursiveFileSearch = ( baseDir, fileId ) => {
let pathsArray = [];
pendingRecursive = 1;
//recursive funcion to get all config paths
let getFilePaths = ( baseDir ) => {
//require inbuilt path and filesystem modules
let path = require ( 'path' );
let fs = require ( 'fs' );
//read the files in the base directory
let files = fs.readdirSync ( baseDir );
//fetch all config files recursively
for ( let i = 0 ; i < files.length; i ++ ) {
let file = files [ i ];
let filePath = path.resolve ( baseDir, file );
//get file stats
let fileStats = fs.lstatSync ( filePath );
let isFile = fileStats.isFile ( );
let isDir = fileStats.isDirectory ( );
if ( isFile && file === fileId ) {
pathsArray.push ( filePath );
}
if ( isDir ) {
pendingRecursive++;
getFilePaths( filePath );
}
}
//decrement the recursive flag
if (--pendingRecursive == 0){
return pathsArray;
}
};
return getFilePaths ( baseDir );
};
//Testing the recursive search
let baseDir = __dirname;
let filePaths = recursiveFileSearch ( baseDir, "your file name" );
Can you use a boolean outside of the function as a flag and change it's value upon reaching your targetted node? Perhaps your recursive cases can be within a case on the boolean, and when the node is found you can update it's value... Or are you asking what the base case is for your recursive function to complete?

Categories

Resources