I have a process in my code where I need to get a list of technician drive times. I use the Google Maps API to get the driving time between the origin and destination. As most of you know, the API requires to have a timeout of roughly 1 second or more to work without generating errors. I have created a recursive function to retrieve the list of times that I need using a setTimeout within the method, like so:
function GetTechDriveTimes(info, destAddress) {
let techs = this.state.Techs
.filter(tech => tech.Address != "" && !tech.Notes.includes('Not'))
.map(tech => {
let techObj = {
TechName: tech.FirstName + " " + tech.LastName,
TechAddress: tech.Address + " " + tech.City + " " + tech.State + " " + tech.Zip,
KioskID: info.ID.toUpperCase(),
DriveTime: "",
};
return techObj
});
let temp = [...techs]; // create copy of techs array
const directionsService = new google.maps.DirectionsService();
recursion();
let count = 0;
function recursion() {
const techAddress = temp.shift(); // saves first element and removes it from array
directionsService.route({
origin: techAddress.TechAddress,
destination: destAddress,
travelMode: 'DRIVING'
}, function (res, status) {
if (status == 'OK') {
let time = res.routes[0].legs[0].duration.text;
techs[count].DriveTime = time;
} else {
console.log(status);
}
if (temp.length) { // if length of array still exists
count++;
setTimeout(recursion, 1000);
} else {
console.log('DONE');
}
});
}
return techs;
}
After this method is complete, it will return an array with the techs and their respective drive times to that destination. The problem here is that using setTimeout obviously doesn't stop execution of the rest of my code, so returning the array of technicians will just return the array with empty drive times.
After timeout is complete I want it to return the array within the method it was called like this:
function OtherMethod() {
// there is code above this to generate info and destAddress
let arr = GetTechDriveTimes(info, destAddress);
// other code to be executed after GetTechDriveTimes()
}
I've looked online for something like this, and it looks like I would need to use a Promise to accomplish this, but the difference from what I found online is that they weren't using it inside of a recursive method. If anyone has any ideas, that would help me a lot. Thanks!
You could use promises, but you can also create a callback with the "other code to be executed after GetTechDriveTimes" and send it to the function:
function OtherMethod() {
// there is code above this to generate info and destAddress
// instead of arr = GetTechDriveTimes, let arr be the parameter of the callback
GetTechDriveTimes(info, destAddress, function(arr) {
// other code to be executed after GetTechDriveTimes()
});
}
function GetTechDriveTimes(info, destAddress, callback) {
...
if (temp.length) { // if length of array still exists
...
} else {
console.log('DONE');
callback(techs); // send the result as the parameter
}
...
Related
I am very new to Javascript and I am trying to utilize BestBuy's api to grab data on a specific sku number every 3 seconds.
The call to the api works as expected on its own but when I move the call inside a while loop, I do not get anything and the console.log in the function is not hit.
Not sure where to go from here and I am starting wonder if this api call can only be called by itself.
Would appreciate any help.
var bby = require('bestbuy')('<Actual BestBuy Api key in original code>');
var isSoldOut = true;
while(isSoldOut)
{
console.log("HERE");
bby.products(6465789,{show:'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate'}).then(function(data){
console.log(data);
if (data['onlineAvailability'] == false)
{
isSoldOut = false;
console.log(isSoldOut);
}
});
wait(3000);
}
function wait(ms)
{
var d = new Date();
var d2 = null;
do { d2 = new Date(); }
while(d2-d < ms);
}
Your while loops are constantly blocking the thread, so your callback function can't run.
Instead of the while loops, you can use an interval:
var bby = require('bestbuy')('<Actual BestBuy Api key in original code>');
function check() {
console.log("HERE");
bby.products(6465789,{show:'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate'}).then(function(data){
console.log(data);
if (data['onlineAvailability'] == false)
{
clearInterval(loop);
console.log("In Stock");
}
});
}
const loop = setInterval(check, 3000);
Even cleaner may be using async/await to wait 3 seconds after receiving each response:
var bby = require('bestbuy')('<Actual BestBuy Api key in original code>');
const wait = require('util').promisify(setTimeout);
async function check() {
console.log("HERE");
const data = await bby.products(6465789,{show:'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate'});
console.log(data);
if (data['onlineAvailability'] == false)
{
console.log("In Stock");
return;
}
await wait(3000);
}
check();
There are several points to improve your code:
Use setInterval to execute code in regular interval, instead of using spin lock in a while loop
Use async/await to improve readability
Instead of checking condition == false, simply check !condition
Instead of doing if(!condition){x = false}, simply do x = !condition
Use dot notation instead of bracket notation to access object property if the property name is not variable. This can take advantage of intellisense of IDE if the object shape is properly documented and reduce typo.
const t = setInterval(async () =>{
const data = await bby.products(6465789, { show: 'sku,name,salePrice,onlineAvailability,inStorePickup,onlineAvailabilityUpdateDate' });
console.log(data);
isSoldOut = !data.onlineAvailability;
console.log(isSoldOut);
if(isSoldOut)
{
clearInterval(t);
}
},3000);
When I run node index.js with this code, everything works appropriately. What it's doing is getting a random coordinate with the randomGeo function and then converting that into an address with the Axios call. Lastly, this transPredtoJSON function is called in the .then of the Axios call, which takes the address and adds it to a JSON file. The problem arises when I want to execute this more than once, which I'll show in the next code snippet.
randomCoordinate = randomGeo({ latitude: originlat, longitude: originlng }, radius)
lat = randomCoordinate.latitude
lng = randomCoordinate.longitude
reverseGeoCodeURL = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${GEOCODE_API_KEY}`
// get closest address to the coordinate
axios.get(reverseGeoCodeURL).then(res => {
let ranAddr = res.data.results[0].formatted_address
let correctCity = /, Southlake,/i
if (correctCity.exec(ranAddr)) { // only Southlake addresses
transPredtoJSON(ranAddr) // are added to the output json file
}
}).catch(error => {
console.error('error: ', error);
})
Below I'm wrapping the above code in a while loop. I'll have a counter that will keep track of how many addresses were added to the output file and stop when it reaches its limit. Besides, axios.get never gets called bc the console log only outputs count: 0 infinitely. The weird thing is, when I move count += 1 right after the axios call so that the count increments whether or not the address is added, axios.get gets called and adds the addresses to the file like it's supposed to. Why doesn't Axios run in this while loop?
let enoughAddresses = 10
let count = 0
while (count < enoughAddresses) {
console.log('count', count);
randomCoordinate = randomGeo({ latitude: originlat, longitude: originlng }, radius)
lat = randomCoordinate.latitude
lng = randomCoordinate.longitude
reverseGeoCodeURL = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${key}`
axios.get(reverseGeoCodeURL).then(res => {
console.log('inside axios .then');
let ranAddr = res.data.results[0].formatted_address
let corrCity = /, Southlake,/i
if (corrCity.exec(ranAddr)) {
transPredtoJSON(ranAddr)
count += 1 // only increment count when an address has been added
console.log('count', count);
}
}).catch(error => {
console.error('error: ', error);
})
}
The problem is because the axios function is running async. You can probably use a promise to solve this.
this pseudo code might point you in the right direction:
let enoughAddresses = 10
for (i = 0; i < enoughAddresses; i++) {
addresses.push(new Promise((r, j) => {
// your async function to get addresses
});
}
// returns a promise when all your async functions are done
Promise.all(addresses).then((values) => {
// values should contain your addresses
});
also, this for reference.
I'm trying to create a JSON output (tree structure) from the recursive async calls and have come up with below -
$scope.processTree = function (mData, callback) {
_processTree.getWebCollection(mData.url).then(
function(_rdata){
// transform xml -> json
var x2js = new X2JS();
var json = x2js.xml_str2json(_rdata);
//console.log("XML DATA: " + _rdata);
//console.log("JSON DATA: " + JSON.stringify(json));
var _webs = json.Envelope.Body.GetWebCollectionResponse.GetWebCollectionResult.Webs.Web;
// if response has [] of webs - array of objects / sites
if ($(_webs).length > 0 && $.isArray(_webs)) {
$.each(_webs, function (key) {
// loop and build tree
mData.children.push({
name: "Site: " + _webs[key]._Title,
url: _webs[key]._Url,
children: []
});
// recursive loop call for each site again
$scope.processTree(mData.children[key]);
});
}
// if response has {} of webs - single object / site
else if ($.isPlainObject(_webs)) {
mData.children.push({
name: _webs._Title,
url: _webs._Url,
children: []
});
}
// if no response or response is null, do nothing
else {
}
}, function(msg){
alert("ERROR!!! \n\nERROR DATA: " + msg[0] + " \tStatus: " + msg[1]);
});
};
function callback(mData){
// do something - use mData, create tree html and display
}
The recursion gets all sites and subsites, if any for each site and stores in a variable - mData and when done, I need need to return and use this variable as JSON input to create a tree map. Each async. call returns either array of sites or single site, if any.
How can I return mData, only after the entire recursion has completed ? How to know if recursion has ended and a call can be made to desired function ?
You can use a pattern like the following using promises and angular's $q service, assuming foo.getStuff(url) returns an angular promise.
function getRec(url) {
return foo.getStuff(url).then(function(data){
var result = {} // construct your result
var promises = [] // array of promises for recursive calls.
for (x in data) {
promises.push(getRec(url).then(function(r){
// update result with r
}))
}
// wait for all recursive calls and then resolve the promise with the constructed result
return $q.all(promises).then(function(){return result})
})
}
getRec(ROOT_URL).then(callback)
I have an array of ids, and I want to make an api request for each id, but I want to control how many requests are made per second, or better still, have only 5 open connections at any time, and when a connection is complete, fetch the next one.
Currently I have this, which just fires off all the requests at the same time:
_.each([1,2,3,4,5,6,7,8,9,10], function(issueId) {
github.fetchIssue(repo.namespace, repo.id, issueId, filters)
.then(function(response) {
console.log('Writing: ' + issueId);
writeIssueToDisk(fetchIssueCallback(response));
});
});
Personally, I'd use Bluebird's .map() with the concurrency option since I'm already using promises and Bluebird for anything async. But, if you want to see what a hand-coded counter scheme that restricts how many concurrent requests can run at once looks like, here's one:
function limitEach(collection, max, fn, done) {
var cntr = 0, index = 0, errFlag = false;
function runMore() {
while (!errFlag && cntr < max && index < collection.length) {
++cntr;
fn(collection[index++], function(err, data) {
--cntr;
if (errFlag) return;
if (err) {
errFlag = true;
done(err);
} else {
runMore();
}
});
}
if (!errFlag && cntr === 0 && index === collection.length) {
done();
}
}
runMore();
}
With Bluebird:
function fetch(id) {
console.log("Fetching " + id);
return Promise.delay(2000, id)
.then(function(id) {
console.log(" Fetched " + id);
});
}
var ids = [1,2,3,4,5,6,7,8,9,10];
Promise.map(ids, fetch, { concurrency: 3 });
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.1/bluebird.min.js"></script>
<!-- results pane console output; see http://meta.stackexchange.com/a/242491 -->
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
Divide your data into as many arrays as you want concurrent connections. Schedule with setTimeout, and have the completion callback handle the rest of the sub-array.
Wrap the setTimeout in a function of its own so that the variable values are frozen to their values at the time of delayed_fetch() invocation.
function delayed_fetch(delay, namespace, id, issueIds, filters) {
setTimeout(
function() {
var issueId=issueIds.shift();
github.fetchIssue(namespace, id, issueId, filters).then(function(response) {
console.log('Writing: ' + issueId);
writeIssueToDisk(fetchIssueCallback(response));
delayed_fetch(0, namespace, id, issueIds, filters);
});
}, delay);
}
var i=0;
_.each([ [1,2] , [3,4], [5,6], [7,8], [9,10] ], function(issueIds) {
var delay=++i*200; // millisecond
delayed_fetch(delay, repo.namespace, repo.id, issueIds, filters);
});
i'd recommend using throat just for this: https://github.com/ForbesLindesay/throat
Using Bluebird
function getUserFunc(user) {
//Get a collection of user
}
function getImageFunc(id) {
//get a collection of image profile based on id of the user
}
function search(response) {
return getUsersFunc(response).then(response => {
const promises = response.map(items => return items.id);
const images = id => {
return getImagesFunc(id).then(items => items.image);
};
return Promise.map(promises, images, { concurrency: 5 });
});
}
Previously i used ES6 function Promise.all(), but it doesn't work like what i'm expecting. Then go with third party library bluebird.js and Work like a charm.
I'm racking my brain trying to figure out how to sequence this / place callbacks to get what I need.
I have a loop that checks for the existence of files. I'd like it to callback when it's done, but the for loop and the callback finish before the "fs.open" finishes... typical asynchronous problem.
I am using node v0.10.29, express v3.14.0, and am looking at using the "async" library, but again, just can't figure out the logic that I need...
Here is what I have:
Input
function checkForAllFiles(callback)
{
var requiredFiles = [];
requiredFiles[requiredFiles.length] = "../Client/database/one.js";
requiredFiles[requiredFiles.length] = "../Client/database/two.dat";
requiredFiles[requiredFiles.length] = "../Client/database/three.xml";
requiredFiles[requiredFiles.length] = "../Client/database/four.dat";
requiredFiles[requiredFiles.length] = "../Client/database/five.dat";
requiredFiles[requiredFiles.length] = "../Client/database/six.xml";
var missingFiles = [];
for(var r=0; r<requiredFiles.length; r++)
{
fs.open(requiredFiles[r], 'r', function(err, fd){
if(err)
{
missingFiles[missingFiles.length] = err.path;
console.log("found missing file = ", err.path);
}
});
console.log("r = ", r);
}
console.log("sending callback: ", missingFiles);
callback(missingFiles);
}
Output
0
1
2
3
4
5
sending callback: []
found missing file: ../Client/database/three.xml
Desired Output
0
1
found missing file: ../Client/database/three.xml
2
3
4
5
sending callback: ["../Client/database/three.xml"]
I would use the reject method in the async module (which I see you've already found). What it will do is return an array in its callback that contains any elements that don't match a specified check function. For the check function, I'd recommend just using fs.exists instead of watching for an error on fs.open.
Using those functions you can actually reduce the whole check to one line. Something like this:
function checkForAllFiles(callback)
{
var requiredFiles = [];
requiredFiles[requiredFiles.length] = "../Client/database/one.js";
requiredFiles[requiredFiles.length] = "../Client/database/two.dat";
requiredFiles[requiredFiles.length] = "../Client/database/three.xml";
requiredFiles[requiredFiles.length] = "../Client/database/four.dat";
requiredFiles[requiredFiles.length] = "../Client/database/five.dat";
requiredFiles[requiredFiles.length] = "../Client/database/six.xml";
async.reject(requiredFiles, fs.exists, callback);
}
callback will get called with an array that contains just the files that don't exist.
Use the async library and the eachSeries method. Example:
async.eachSeries(array,
function(element, next) {
// do something with element
next();
}
);
It will sequentially go through the array and process each element. Calling next goes to the next element. Series makes sure it does it in the order of the array, otherwise the order of going through the array is not guaranteed. If you have other async functions called within it, just pass the next function around and call it when done with all the needed functions and the next array element will be processed.
Maybe something like this:
var missingFiles = []
async.eachSeries(requiredFiles, function(file, nextFile){
fs.open(file, 'r', function(err, fd){
if(err)
{
missingFiles[missingFiles.length] = err.path;
console.log("found missing file = ", err.path);
}
nextFile();
});
console.log("r = ", file);
});
console.log("sending callback: ", missingFiles);
callback(missingFiles);