How to know when an operation has finished - Callback? - javascript

I'd like to know when an operation has finished - It's an iteration where we don't know the size of the list be iterated over (it could be any length.)
Here's my code:
var array = [];
stripe.charges.list().autoPagingEach(function(charge) {
var post = {
chargeId: charge.id,
customerId: charge.customer,
sourceId:charge.source.id,
amount:(charge.amount/100).toFixed(2),
description:charge.description,
dateAndTime:moment(charge.created*1000).format('YYYY-MM-DD HH:mm:ss'),
chargeStatus:charge.status,
failureMessage:charge.failure_message
};
array.push(post)
});
How can I console.log(array.length) once the iteration has finished?
I have seen some examples that use a callback with Done() which would appear to be what I'm after - But I can't figure how to factor it into this code.

As per Stripe's documentation, stripe.charges.list() returns an object with the list of charges in the data property.
You can do:
let charges = stripe.charges.list();
let pending_charges = charges.data.length;
charges.autoPagingEach(function(charge) {
// do your thing
pending_charges -= 1;
if ( pending_charges == 0 ) {
// call a function after the last charge has been processed
// and be careful with failing autoPagingEach() executions
}
});

Related

node is waiting for a timeout that already killed

I have set a timeout for sending back an error. The problem is that if I need to clear the timeout with clearTimeOut() it does indeed kill it, I can see that as the value of errTimeout's _kill shows true in the debugger. But for some reason node still keeps running the script until the timeOutPeriod is over. I guess it won't really create issues in production because the calling function will receive the returned value. But it still kinda pisses me off that it keeps waiting instead of killing the script.
return new Promise((resolve,reject) => {
function checkResponse () {
//creates a timeout that returns an error if data not receieved after the specified time.
let errTimeout = setTimeout(reject, config.timeOutPeriod);
//the +1 is there because we originally reduced one, we need to use the physical number now.
if(layerKeycodes.length !== (rows+1) * (columns+1)){
//if the array is not complete, check again in 100 ms
setTimeout(checkResponse, 100);
} else {
//clear the error timeout
clearTimeout(errTimeout);
//send the layerKeycodes to the calling function
resolve(layerKeycodes);
}
}
It looks like this code is something you're trying to fit into getLayerKeycodes() from this other question to somehow know when all the data has been received from your keyboard hardware.
I'll illustrate how you can plug into that without using timers. Here's what you started with in that other question:
Your original function
const getLayerKeycodes = (keyboard, layer, rows, columns) => {
//array that stores all the keycodes according to their order
let layerKeycodes = [];
//rows and columns start the count at 0 in low level, so we need to decrease one from the actual number.
columns --;
rows --;
//loop that asks about all the keycodes in a layer
const dataReceived = (err, data) => {
if(err) {
return err;
}
// push the current keycode to the array
// The keycode is always returned as the fifth object.
layerKeycodes.push(data[5]);
console.log(layerKeycodes);
};
for (let r = 0 , c = 0;c <= columns; r ++){
//callback to fire once data is receieved back from the keyboard.
if(r > rows){
c++;
//r will turn to 0 once the continue fires and the loop executes again
r = -1;
continue;
}
//Start listening and call dataReceived when data is received
keyboard[0].read(dataReceived);
//Ask keyboard for information about keycode
// [always 0 (first byte is ignored),always 0x04 (get_keycode),layer requested,row being checked,column being checked]
keyboard[0].write([0x01,0x04,layer,r,c]);
}
console.log(layerKeycodes);
}
Manually created promise to resolve upon completion of all rows/columns
And, you can incorporate the completion detection code inside of the dataReceived() function without any timers and without reworking much of the rest of your logic like this:
const getLayerKeycodes = (keyboard, layer, rows, columns) => {
return new Promise((resolve, reject) => {
//array that stores all the keycodes according to their order
const layerKeycodes = [];
const totalCells = rows * columns;
let abort = false;
//rows and columns start the count at 0 in low level, so we need to decrease one from the actual number.
columns--;
rows--;
// function that gets with keyboard data
function dataReceived(err, data) => {
if (err) {
abort = true; // set flag to stop sending more requests
reject(err);
return;
}
// push the current keycode to the array
// The keycode is always returned as the fifth object.
layerKeycodes.push(data[5]);
// now see if we're done with all of them
if (layerKeycodes.length >= totalCells) {
resolve(layerKeycodes);
}
}
// loop that asks about all the keycodes in a layer
for (let r = 0, c = 0; c <= columns; r++) {
// stop sending more requests if we've already gotten an error
if (abort) {
break;
}
//callback to fire once data is receieved back from the keyboard.
if (r > rows) {
c++;
//r will turn to 0 once the continue fires and the loop executes again
r = -1;
continue;
}
//Start listening and call dataReceived when data is received
keyboard[0].read(dataReceived);
//Ask keyboard for information about keycode
// [always 0 (first byte is ignored),always 0x04 (get_keycode),layer requested,row being checked,column being checked]
keyboard[0].write([0x01, 0x04, layer, r, c]);
}
}
}
}
A simplified version by promisifying the read function
And, here's a bit simpler version that promisifies the read function so we can use await on it and then just use an async function and a dual nested for loop for simpler loop mechanics.
const util = require('util');
async function getLayerKeycodes(keyboard, layer, rows, columns) => {
// promisify the keyboard.read()
const readKeyboard = util.promisify(keyboard[0].read).bind(keyboard[0]);
//array that stores all the keycodes according to their order
const layerKeycodes = [];
// loop that asks about all the keycodes in a layer
for (let rowCntr = 0; rowCntr < rows; rowCntr++) {
for (let colCntr = 0; colCntr < columns; colCntr++) {
// Start listening and collect the promise
const readPromise = readKeyboard();
// Ask keyboard for information about keycode
// [always 0 (first byte is ignored),always 0x04 (get_keycode),layer requested,row being checked,column being checked]
keyboard[0].write([0x01, 0x04, layer, rowCntr, colCntr]);
// wait for data to come in
const data = await readPromise;
// push the current keycode to the array
// The keycode is always returned as the fifth object.
layerKeycodes.push(data[5]);
}
}
return layerCodes;
}
This also makes sure that we send a write, then wait for the data from that write to come back before we sent the next write which seems like a potentially safer way to handle the hardware. Your original code fires all the writes at once which might work, but it seems like the reads could come back in any order. This guarantees sequential order in the layerCodes array which seems safer (I'm not sure if that matters with this data or not).
Error handling in this last version is somewhat automatically handled by the async function and the promises. If the read returns an error, then the readPromise will automatically reject which will abort our loop and in turn reject the promise that the async function returned. So, we don't have to do the abort checking that the previous function with the manually created promise had.
Now, of course, I don't have the ability to run any of these to test them so it's possible there are some typos somewhere, but hopefully you can work through any of those and see the concept for how these work.

Promise outside of for loop

I have a for loop like so:
var currentLargest
for (var site in sites) {
// Each site is a website (www.mysite1.com, www.mysite2.com, etc.)
$.getJSON(site).then(function(data) {
if (data > currentLargest) {
currentLargest = data
}
});
}
// Here is where I want to use "currentLargest"
It checks multiple different websites to get the largest value. However, I can't extract the data I need after the for loop. I can only interact with currentLargest from within the promise, but that data is only relevant for ONE of the sites (it hasn't finished looping through all them until after the for loop).
How can I access the currentLargest value after the for loop containing the getJSON promises?
// Your code:
/*
var currentLargest
for (var site in sites) {
// Each site is a website (www.mysite1.com, www.mysite2.com, etc.)
$.getJSON(site).then(function(data) {
if (data > currentLargest) {
currentLargest = data
}
});
}
*/
// Updated:
const sitePromisesArr = sites.map((site) => $.getJSON(site));
$.when(sitePromisesArr)
.then((valArr, index) => {
// The "valArr" here will be an Array with the responses
const largest = valArr.reduce((current, next) => next > current ? next : current, 0);
// This will be the largest value.
console.log(largest);
// This will be the site that contains the largest value.
console.log(sitePromisesArr[index]);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You need to use a async loop and then execute a callback once all the async getJSON requests are finished. You could use http://caolan.github.io/async/docs.html#each to help with this.

Javascript strange behavior, asynchronism issue?

I have the next code:
exports.getCommunities = function(user, callback)
{ //I am getting the communities for a user.
community_users.find({'user':user}).sort({_id : 1 }).toArray(function(err, docs) {
docs.sort
var clas = [];
//for each community, I need to find the country of that community and add it to each docs object. To do so, i call a getCommunityByName function that finds the community document in the communities mongodb collection by a given name.
docs.forEach(function(entry,i) {
clas.push(entry);
getCommunityByName(entry.name, function(e, o){
if (o){
clas[i].country = o.country;
if (docs.length-1 == i) {callback(null,clas)}
} else { console.log('community-not-found: '+entry.name)}
});
});
});
};
I am having strange behavior. Imagine docs is a 7 object array. I am obtaining a 7 positions array but a random number of them have the country key. Sometimes only 3 of them have the country key, sometimes are 5, sometimes 6...
I think that the if statement to call the callback is not waiting for every call to getCommunityByName and i don't know really why...
I need some light in this...
Regards,
Assuming getCommunityByName performs an asynchronous request, it could be that the request for the final item is returning before some of the previous items, so it's calling the callback too soon. Rather than using i from the loop to decide when to call back, instead count down the returned requests and call the callback when they're all complete:
exports.getCommunities = function(user, callback)
{ //I am getting the communities for a user.
community_users.find({'user':user}).sort({_id : 1 }).toArray(function(err, docs) {
docs.sort
var clas = [];
//for each community, I need to find the country of that community and add it to each docs object. To do so, i call a getCommunityByName function that finds the community document in the communities mongodb collection by a given name.
//initialise counter to number of items
var counter = docs.length;
docs.forEach(function(entry,i) {
clas.push(entry);
getCommunityByName(entry.name, function(e, o) {
//request returned, decrement counter
counter--;
if (o){
clas[i].country = o.country;
} else { console.log('community-not-found: '+entry.name)}
if (counter == 0) {
//All requests returned, fire callback
callback(null, clas);
}
});
});
});
};

Run function after another function completes JavaScript and JQuery

I need a little help. I'm trying to run my second function "likeLinks();" but only after my first function "getLikeURLs();" is finished. This is because my 2nd function relies on the links Array to execute. It seems like they are trying to run at the same time.
Any help would be appreciated.
var links = [];
var url = '/' + window.location.pathname.split('/')[1] + '/' + window.location.pathname.split('/')[2] + '/'
getLikeURLs();
likeLinks();
function getLikeURLs() {
for (i = 1; i < parseInt(document.getElementsByClassName('PageNav')[0].getAttribute('data-last')) + 2; i++) {
var link = $.get(url + 'page-' + i, function(data) {
//gets the like links from current page
$(data).find('a[class="LikeLink item control like"]').each(function() {
links.push($(this).attr('href')); // Puts the links in the Array
});
});
}
}
function likeLinks() {
for (t = 0; t <= links.length; t++) {
var token = document.getElementsByName('_xfToken')[0].getAttribute('value')
$.post(links[t], {
_xfToken: token,
_xfNoRedirect: 1,
_xfResponseType: 'json'
}, function(data) {});
}
}
The link variables are actually jQuery deferred objects - store them in an array and then you can use $.when() to create a mew deferred object that only resolves when all of the previous $.get() operations have completed:
function getLikeURLs(url) { // NB: parameter, not global
var defs = [], links = []; // NB: links no longer global
for (...) {
var link = $.get(...);
defs.push(link);
}
// wait for previous `$.get` to finish, and when they have create a new
// deferred object that will return the entire array of links
return $.when.apply($, defs).then(function() { return links; });
}
Then, to start the chain of functions:
getLikeURLs(url).then(likeLinks);
Note that likeLinks will now be passed the array of links instead of accessing it from the global state. That function should also be rewritten to allow you to wait for its $.post calls to complete, too:
function likeLinks(links) {
// loop invariant - take it outside the loop
var token = document.getElementsByName('_xfToken')[0].getAttribute('value');
// create array of deferreds, one for each link
var defs = links.map(function(link) {
return $.post(link, {
_xfToken: token,
_xfNoRedirect: 1,
_xfResponseType: 'json'
});
});
// and another for when they're all done
return $.when.apply($, defs);
}
p.s. don't put that (relatively) expensive parseInt(document.getAttribute(...)) expression within the for statement - it'll cause it to be evaluated every iteration. Calculate it once outside the loop and store it in a variable. There's a few other places where you're repeating calls unnecessarily, e.g. window.location.pathname.split()
EDIT: My answer discusses the issue but see Alnitak answer for a much better solution.
The get in getLikeURLs and the put in likeLinks are both asynchronous. The calls to both of these function return immediately. When data is returned from the called server at some indeterminate time later, the callback functions are then called. The puts could return before the gets which would be a problem in your case. Also note that JavaScript is NOT multi-threaded so the two methods, getLikeURLs and likeLinks will never run at the same time. The callback functions, on the other hand, might be called at anytime later with no guarantee as to the call back order. For example, the 3rd get/put might return before the 1st get/put in your loops.
You could use $.ajax to specify that the gets and puts are synchronous but this is ill advised because the browser will hang if ANY get/put doesn't return in a reasonable amount of time (e.g. server is offline). Plus you don't have the "multi-tasking" benefit of sending out a lot of requests and having the various servers working at the same time. They would do so serially.
The trick is to simply call likeLinks form the callback function in getLikeURL. Your case is a little tricky because of the for loop but this should work:
var links = [];
var url = '/' + window.location.pathname.split('/')[1] + '/' + window.location.pathname.split('/')[2] + '/'
getLikeURLs();
//likeLinks(); // Don't call yet. Wait for gets to all return.
function getLikeURLs() {
var returnCount = 0; // Initialize a callback counter.
var count = parseInt(document.getElementsByClassName('PageNav')[0].getAttribute('data-last')) + 1;
for (i = 0; i < count; i++) {
var link = $.get(url + 'page-' + (i + 1), function(data) {
//gets the like links from current page
$(data).find('a[class="LikeLink item control like"]').each(function() {
links.push($(this).attr('href')); // Puts the links in the Array
});
// If all gets have returned, call likeLinks.
returnCount++;
if (returnCount === count) {
likeLinks();
}
});
}
}
function likeLinks() {
for (t = 0; t <= links.length; t++) {
var token = document.getElementsByName('_xfToken')[0].getAttribute('value')
$.post(links[t], {
_xfToken: token,
_xfNoRedirect: 1,
_xfResponseType: 'json'
}, function(data) {});
}
}

Delay this Javascript FOR Loop

This LOOP queries the Parse.com server & then plays with the results if any. The problem is that when nArray is greater than 100, the function exceeds the query/burst limit of Parse.com CloudCode & it fails.
One idea would be to delay the LOOP for a second after every 100 LOOPS, but I'm not sure how to do that. Any other solutions would be greatly appreciated.
Thanks in Advance,
for (var k = 1; k < nArray.length; k++) {
(function (k, mArray) { // <-- define an inline function
query2.equalTo("username", nArray[k]); // BURST LIMIT EXCEEDS
query2.find({
success: function (results) {
if (results.length !== 0) {
var object = results[0];
var compareUserEmail = object.get('email');
if (compareUserEmail !== userEmail) {
// alert("The result is equal to" + object.get('Name'));
mArray.push({
name: object.get('Name'),
email: object.get('email'),
bloxID: object.get('bloxID')
});
gameScore.set("filtered", mArray);
gameScore.save(null, {
success: function (gameScore) {
response.success("Success!");
alert('New object created with objectId: ' + gameScore.id);
},
error: function (gameScore, error) {
alert('Failed to create new object, with error code: ' + error.description);
}
});
}
};
},
error: function () {}
});
})(k, mArray);
// <-- call it after definition using (k)
};
You've got a couple of issues to deal with.
The reason Parse.com doesn't support setInterval is because that would be inviting disaster. It terminates your Cloud Code if it takes too long, so letting you add delays would just increase the chance your code is terminated before completion.
The reason Parse.com has a burst limit is that this usually suggest "you are doing it wrong (tm)". In your case you are looping through an array and running a query for each item in the array. Instead you should be using the containedIn method to get all records for the array in one go. If you are getting more than 100 items in your array you can choose to increase the record limit to 1000, but first consider carefully if this is really what you need.
Given that you are modifying a lot of objects and saving them all, consider using the saveAll method to save them all in one hit too.
You might want to consider batching these operations, but be aware of the restrictions on overall duration for Cloud Code.
You can use a setInterval:
var i = 0;
var intervalId = setInterval(function() {
if(i < nArray.length) {
... your code ...
i++;
} else {
clearInterval(intervalId);
}
}, 100); //every 100ms; change it to what you need

Categories