Javascript timeout loop to save multiple rows to Parse.com - javascript

I have written a beforeSave script in parse.com cloud code. I would like to save all my results to parse so they are all run through this script and their data is modified before saving.
I have tacked this in the following way.
Exported JSON from Parse.com dashboard.
use this as a local JSON in my app and run the following loop:
CODE:
$http.get('data/full_data.json')
.then(function(res) {
var counter = 0;
for (var i = 0; i < res.data.results.length; i++) {
setDelay(counter);
saveToParse(res.data.results[i]);
counter ++
};
}
});
function setDelay(i) {
setTimeout(function() {
console.log(i);
}, 1000);
}
function saveToParse(exercise) {
console.log(exercise);
ParseFactory.provider('Exercises/').edit(exercise.objectId, exercise).success(function(data) {
}).error(function(response) {
$ionicLoading.hide();
$rootScope.$emit('errorEvent', {
"message": "Please check your connection",
"errorObject": response
});
});
}
I have been trying to have a timeout function so I do not exceed the number of API calls allowed on Parse.com.
My problem is that all my API calls are done and then it run the timeouts really quickly at the end after a 1 second pause.
How can I ensure each loop iteration takes a timeout before looping again.
The answers work perfectly for the first 50 seconds and then work slowly... see this screen grab of the network activity.

You could make all your functions return promises. And to make the loop async you convert it into a recursive "loop".
$http.get('data/full_data.json')
.then(function(res) {
function loop(i) {
if(i < res.data.results.length) {
return saveToParse(res.data.results[i]).then(function() {
return delay(100);
}).then(function() {
return loop(i + 1);
});
}
else return Promise.resolve(res);
};
return loop(0);
}).then(function(res) {
console.log("finished saving");
});
function delay(time) {
return new Promise(function(resolve, reject) {
setTimeout(resolve, time);
});
}
function saveToParse(exercise) {
return new Promise(function(resolve, reject) {
console.log(exercise);
ParseFactory.provider('Exercises/').edit(exercise.objectId, exercise)
.success(resolve).error(function(response) {
var error = {
"message": "Please check your connection",
"errorObject": response
};
reject(error);
$ionicLoading.hide();
$rootScope.$emit('errorEvent', error);
});
});
}
edit:
However it might be better to do it this way. It has the advantage of returning a promise so you can keep chaining your promises.
$http.get('data/full_data.json')
.then(function(res) {
var p = Promise.resolve();
res.data.results.forEach(function(elem) {
p = p.then(function() {
return saveToParse(elem).then(function() {
return delay(100);
});
});
});
return p;
});
edit2:
Yet another solution to generalize async loops.
function asyncWhile(cond, body) {
if(cond()) {
return body().then(function() {
return asyncWhile(cond, body);
});
} else {
return Promise.resolve();
}
}
function asyncFor(start, end, body) {
var i = start;
return asyncWhile(function() {return i < end}, function() {
return body(i++);
});
}
$http.get('data/full_data.json')
.then(function(res) {
return asyncFor(0, res.data.results.length, function(i) {
return saveToParse(res.data.results[i]).then(function() {
return delay(100);
});
}).then(function() {
return res;
});
}).then(function(res) {
console.log("finished saving");
});

Related

NodeJS: promise returned by Promise.all is not resolved although the individual promises are

I have the following discovery code using the mdns-js package.
in ./lib/deviceDiscovery.js:
var mdns = require('mdns-js');
const browsers = new Map();
const INFINITE = -1;
function createABrowser(theServiceType, timeout) {
if (browsers.has(theServiceType)) {
return;
}
return new Promise(function(resolve, reject) {
var browser = mdns.createBrowser(theServiceType);
browser.on('ready', function() {
browsers.set(theServiceType, browser);
resolve(browser);
});
if (timeout != INFINITE) {
setTimeout(function onTimeout() {
try {
browser.stop();
browsers.delete(browser.serviceType);
} finally {
reject('browser ' + browser.toString() + ' timed out.');
}
}, timeout);
}
});
}
module.exports.startService = function(services, timeout) {
timeout = timeout || INFINITE;
promises = [];
services.forEach(function(service) {
promises.push(createABrowser(service, timeout));
});
return Promise.all(promises);
}
module.exports.stopService = function() {
browsers.values().forEach(function(browser) {
browser.stop();
});
browsers.clear();
}
module.exports.getDevices = function() {
if (browsers.size == 0) {
reject('service was stopped');
} else {
const promises = [];
for (let browser of browsers.values()) {
promises.push(new Promise(function(resolve, reject) {
try {
browser.discover();
browser.on('update', function(data) {
mfps = new Set();
const theAddresses = data.addresses;
theAddresses.forEach(function(element) {
mfps.add(element);
});
resolve(mfps);
});
} catch(err) {
reject(err);
}
}));
};
return Promise.all(promises).then(function(values) {
return new Set(values);
}, function(reason) {
return reason;
});
}
}
and use it in another file like this:
const DeviceDiscoveryService = require('./lib/deviceDiscovery');
var co = require('co');
co(function *service() {
yield DeviceDiscoveryService.startService([internetPrinter, pdlPrinter, unixPrinter], TIMEOUT);
yield DeviceDiscoveryService.getDevices();
}).catch(onerror);
function onerror(err) {
// log any uncaught errors
}
The problem is that the second yield hangs; it seems that the promise returned by getDevices function isn't resolved indefinitely, although I see that the individual promises are resolved.
startService uses a similar Promise.all(...) but it works ok.
Another related question is about the mdns-js: it seems that for each (input) service, the browser receives multiple updates.
But I resolve the promise for each browser after the first update event... do I need to wait for multiple updates and how?
Any hints will be greatly appreciated! Thanks.
I believe that you share update be returning a promises from createABrowser at ALL times (instead of returning undefined if the service already exists). Without returning a promise, I think Promise.all() won't resolve.
Instead, create a promise at the top and resolve if it the service exists already, and return THAT promise.
For the getDevices() call, you're running a reject without returning a promise there as well. Would this work?
module.exports.getDevices = function() {
if (browsers.size == 0) {
// Create a new promise, return it, and immediately reject
return new Promise(function(resolve, reject) { reject('service was stopped') };
// reject('service was stopped'); <- There wasn't a promise here
} else {
const promises = [];
for (let browser of browsers.values()) {
promises.push(new Promise(function(resolve, reject) {
try {
browser.discover();
browser.on('update', function(data) {
mfps = new Set();
const theAddresses = data.addresses;
theAddresses.forEach(function(element) {
mfps.add(element);
});
resolve(mfps);
});
} catch(err) {
reject(err);
}
}));
};
return Promise.all(promises).then(function(values) {
return new Set(values);
}, function(reason) {
return reason;
});
}
}

Using Promises to defer continuation within a forEach loop

My goal with the below is to:
Capture a list of resolutions
Scan through each of them (in order) to find the first one that results in a successful stream
To test this, I have testVideoPresence:
var testCounter = 0;
function testVideoPresence(videoElement) {
testCounter++;
if (testCounter >= 5) {
testCounter = 0;
return false;
}
if (!videoElement.videoWidth || videoElement.videoWidth < 10) { // check to prevent 2x2 issue
setTimeout(function() {
testVideoPresence(videoElement); // try again
}, 500);
} else if (video.videoWidth * video.videoHeight > 0) {
return true;
}
}
As you can see, I'm using a setTimeout to recurse at most 5 times. This is where things get tricky:
resolutionTestBuilder.buildTests().then(function (resolutionTests) {
// at this point, I have a set of resolutions that I want to try
resolutionTests.forEach(function (resolutionTest) {
// then I want to iterate over all of them until I find one that works
performTest(resolutionTest).then(function (result) {
video.srcObject = result.mediaStream; // start streaming to dom
if (testVideoPresence(video)) { // here is the pain point - how do I await the result of this within the forEach?
// return the dimensions
} else {
// continue scanning
}
}).catch(function (error) {
logger.internalLog(error);
});
// wait to continue until we have our result
});
}).catch(function (error) {
logger.internalLog(error);
});
function performTest(currentTest) {
return streamHelper.openStream(currentTest.device, currentTest.resolution).then(function(streamData) {
return streamData;
}).catch(function (error) {
logger.internalLog(error);
});;
};
streamHelper.openStream = function (device, resolution) {
var constraints = createVideoConstraints(device, resolution);
logger.internalLog("openStream:" + resolution.label + ": " + resolution.width + "x" + resolution.height);
return navigator.mediaDevices.getUserMedia(constraints)
.then(function (mediaStream) {
streamHelper.activeStream = mediaStream;
return { stream: mediaStream, resolution: resolution, constraints: constraints };
// video.srcObject = mediaStream; // push mediaStream into target element. This triggers doScan.
})
.catch(function (error) {
if (error.name == "NotAllowedError") {
return error.name;
} else {
return error;
}
});
};
I'm trying to wait for the result within the forEach before continuing through the array of resolutions. I know I can use some advanced techniques like async/await if I want to transpile - but I'm stuck with vanilla JS and promises / bluebird.js for now. What are my options? Disclaimer - I am new to promises so the above code could be very malformed.
Update:
Tests are defined in order of importance - so I do need resolutionTests[0] to resolve before resolutionTests[1].
If the order of trials isn't important, you can simply use a map combined with Promise.race to make sure the first promise of a list that resolves resolves the whole list. You also need to make sure your promises return other promises inside then.
resolutionTestBuilder.buildTests().then(function (resolutionTests) {
return Promise.race(resolutionTests.map(function (resolutionTest) {
return performTest(resolutionTest).then(function (result) {
video.srcObject = result.mediaStream; // start streaming to dom
return testVideoPresence(video);
}).catch(function (error) {
logger.internalLog(error);
});
}));
}).catch(function (error) {
logger.internalLog(error);
});
This of course assumes that testVideoPresence does NOT resolve when you the dimensions are not available.
If the order of trial is important then a reduce approach might work.
This will basically result in a sequential application of the promises and the resulting promise will until all of them are resolved.
However, once the solution is found we attach it to the collector of the reduce so that further trials simply return that as well and avoid further tests (because by the time this is found the chain is already registered)
return resolutionTests.reduce(function(result, resolutionTest) {
var nextPromise = result.intermPromise.then(function() {
if (result.found) { // result will contain found whenver the first promise that resolves finds this
return Promise.resolve(result.found); // this simply makes sure that the promises registered after a result found will return it as well
} else {
return performTest(resolutionTest).then(function (result) {
video.srcObject = result.mediaStream; // start streaming to dom
return testVideoPresence(video).then(function(something) {
result.found = something;
return result.found;
});
}).catch(function (error) {
logger.internalLog(error);
});
}
);
return { intermPromise: nextPromise, found: result.found };
}, { intermPromise: Promise.resolve() }); // start reduce with a result with no 'found' and a unit Promise
At first , your testVideoPresence returns undefined. It wont work that way. May do:
function testVideoPresence(videoElement,callback,counter=0) {
if(counter>10) callback(false);
if (!videoElement.videoWidth || videoElement.videoWidth < 10) {
setTimeout(testVideoPresence, 500,videoElement,callback,counter+1);
} else if (video.videoWidth * video.videoHeight > 0) {
callback(true);
}
}
SO you can do:
testVideoPresence(el, console.log);
Now to the forEach. You cannot yield the forEach in any way. However you could write your own recursive forEach:
(function forEach(el,index) {
if(index>=el.length) return false;
performTest(el[index]).then(function (result) {
video.srcObject = result.mediaStream; // start streaming to dom
testVideoPresence(video,function(success){
if(!success) return alert("NOO!");
//do sth
//proceed
setTimeout(forEach,0,el,index+1);
});
}).catch(function (error) {
logger.internalLog(error);
});
})(resolutionTests,0);//start with our element at index 0
function raceSequential(fns) {
if(!fns.length) {
return Promise.resolve();
}
return fns.slice(1)
.reduce(function(p, c) {
return p.catch(c);
}, fns[0]());
}
// "Resolution tests"
var t1 = function() { return new Promise(function(_, reject) { setTimeout(() => reject('t1'), 1000); })};
var t2 = function() { return new Promise(function(resolve) { setTimeout(() => resolve('t2'), 1000); })};
var t3 = function() { return new Promise(function(resolve) { setTimeout(() => resolve('t3'), 1000); })};
var prom = raceSequential([t1, t2, t3])
.then(function(result) { console.log('first successful result: ' + result); });
Scanning your code indicates you have other async-related problems.

Cleaning up Spaghetti Code w/ Promises

In one of my beforeSave functions, I need to check a number of conditions before responding with success or error.
The problem is, my code seems really messy and sometimes success/error isn't called:
Parse.Cloud.beforeSave("Entry", function(request, response) {
var entry = request.object;
var contest = request.object.get("contest");
entry.get("user").fetch().then(function(fetchedUser) {
contest.fetch().then(function(fetchedContest) {
if ( fetchedUser.get("fundsAvailable") < fetchedContest.get("entryFee") ) {
response.error('Insufficient Funds.');
} else {
fetchedContest.get("timeSlot").fetch().then(function(fetchedTimeSlot) {
var now = new Date();
if (fetchedTimeSlot.get("startDate") < now) {
response.error('This contest has already started.');
} else {
contest.increment("entriesCount");
contest.increment("entriesLimit", 0); //have to do this, otherwise entriesLimit is undefined in save callback (?)
contest.save().then(function(fetchedContest) {
if (contest.get("entriesCount") > contest.get("entriesLimit")) {
response.error('The contest is full.');
} else {
response.success();
}
});
}
});
}
});
});
});
I've been trying to learn promises to tidy this up, and here was my (failed) attempt:
Parse.Cloud.beforeSave("Entry", function(request, response) {
var entry = request.object;
var contest = request.object.get("contest");
entry.get("user").fetch().then(function(fetchedUser) {
contest.fetch().then(function(fetchedContest) {
if ( fetchedUser.get("fundsAvailable") < fetchedContest.get("entryFee") ) {
response.error('Insufficient Funds.');
}
return fetchedContest;
});
}).then(function(result) {
result.get("timeSlot").fetch().then(function(fetchedTimeSlot) {
var now = new Date();
if (fetchedTimeSlot.get("startDate") < now) {
response.error('This contest has already started.');
}
});
}).then(function() {
contest.increment("entriesCount");
contest.increment("entriesLimit", 0);
contest.save().then(function(fetchedContest) {
if (contest.get("entriesCount") > contest.get("entriesLimit")) {
response.error('The contest is full.');
}
});
}).then(function() {
response.success();
}, function(error) {
response.error(error);
});
});
Any help or examples on how to use promises for this would be much appreciated. Clearly I do not fully understand how they work syntactically yet.
I cleaned it up a bit by getting the fetched variables assembled first using a chain of promises. This follows a couple style rules that make it more readable (for me anyway)...
Parse.Cloud.beforeSave("Entry", function(request, response) {
var entry = request.object;
var contest = request.object.get("contest");
var fetchedUser, fetchedContest;
var errorMessage;
entry.get("user").fetch().then(function(result) {
fetchedUser = result;
return contest.fetch();
}).then(function(result) {
fetchedContest = result;
return fetchedContest.get("timeSlot").fetch();
}).then(function(fetchedTimeSlot) {
// now we have all the variables we need to determine validity
var now = new Date();
var hasSufficientFunds = fetchedUser.get("fundsAvailable") >= fetchedContest.get("entryFee");
var contestNotStarted = fetchedTimeSlot.get("startDate") >= now;
if (hasSufficientFunds && contestNotStarted) {
contest.increment("entriesCount");
contest.increment("entriesLimit", 0); //have to do this, otherwise entriesLimit is undefined in save callback (?)
return contest.save();
} else {
errorMessage = (hasSufficientFunds)? 'This contest has already started.' : 'Insufficient Funds.';
return null;
}
}).then(function(result) {
if (!result) {
response.error(errorMessage);
} else {
if (contest.get("entriesCount") > contest.get("entriesLimit")) {
response.error('The contest is full.');
} else {
response.success();
}
}
}, function(error) {
response.error(error);
});
});
Note how we never leave a response.anything() dangling, we always make clear what's flowing to the next promise via a return.
You are doing the rookie mistake of not returning promises inside .then while chaining them, other than that your chain would still continue even when you call response.error(.., simplest way to break chain is throwing an error, your code can be flattened as:
Parse.Cloud.beforeSave("Entry", (request, response) => {
var entry = request.object,
, contest = request.object.get("contest")
, fetchedUser
, fetchedContest;
entry.get("user").fetch()
.then(_fetchedUser => {
fetchedUser = _fetchedUser;
return contest.fetch();
}).then(_fetchedContest => {
fetchedContest = _fetchedContest;
if ( fetchedUser.get("fundsAvailable") < fetchedContest.get("entryFee") )
return Promise.reject('Insufficient Funds.');
return fetchedContest.get("timeSlot").fetch();
}).then(fetchedTimeSlot => {
var now = new Date();
if (fetchedTimeSlot.get("startDate") < now)
return Promise.reject('This contest has already started.');
contest.increment("entriesCount");
contest.increment("entriesLimit", 0); //have to do this, otherwise entriesLimit is undefined in save callback (?)
return contest.save();
}).then(fetchedContest => {
if (contest.get("entriesCount") > contest.get("entriesLimit"))
return Promise.reject('The contest is full.');
response.success();
}).catch(response.error.bind(response));
});

Collecting paginated data of unknown size using promises

I'm querying a REST-API to get all groups. Those groups come in batches of 50. I would like to collect all of them before continuing to process them.
Up until now I relied on callbacks but I'd like to use promises to chain the retrieval of all groups and then process the result-array further.
I just don't quite get how to replace the recursive functional call using promises.
How would I use A+ promises to escape the callback hell I create with this code?
function addToGroups() {
var results = []
collectGroups(0)
function collectGroups(offset){
//async API call
sc.get('/tracks/'+ CURRENT_TRACK_ID +'/groups?limit=50&offset=' + offset , OAUTH_TOKEN, function(error, data){
if (data.length > 0){
results.push(data)
// keep requesting new groups
collectGroups(offset + 50)
}
// finished
else {
//finish promise
}
})
}
}
Using standard promises, wrap all of your existing code as shown here:
function addToGroups() {
return new Promise(function(resolve, reject) {
... // your code, mostly as above
});
}
Within your code, call resolve(data) when you're finished, or reject() if for some reason the chain of calls fails.
To make the whole thing more "promise like", first make a function collectGroups return a promise:
function promiseGet(url) {
return new Promise(function(resolve, reject) {
sc.get(url, function(error, data) {
if (error) {
reject(error);
} else {
resolve(data);
}
});
}
}
// NB: promisify-node can do the above for you
function collectGroups(offset, stride) {
return promiseGet('/tracks/'+ CURRENT_TRACK_ID +'/groups?limit=' + stride + '&offset=' + offset , OAUTH_TOKEN);
}
and then use this Promise in your code:
function addToGroups() {
var results = [], stride = 50;
return new Promise(function(resolve, reject) {
(function loop(offset) {
collectGroups(offset, stride).then(function(data) {
if (data.length) {
results.push(data);
loop(offset + stride);
} else {
resolve(data);
}
}).catch(reject);
)(0);
});
}
This could work. I am using https://github.com/kriskowal/q promises.
var Q = require('q');
function addToGroups() {
var results = []
//offsets hardcoded for example
var a = [0, 51, 101];
var promises = [], results;
a.forEach(function(offset){
promises.push(collectGroups(offset));
})
Q.allSettled(promises).then(function(){
promises.forEach(function(promise, index){
if(promise.state === 'fulfilled') {
/* you can use results.concatenate if you know promise.value (data returned by the api)
is an array */
//you also could check offset.length > 0 (as per your code)
results.concatenate(promise.value);
/*
... do your thing with results ...
*/
}
else {
console.log('offset',index, 'failed', promise.reason);
}
});
});
}
function collectGroups(offset){
var def = Q.defer();
//async API call
sc.get('/tracks/'+ CURRENT_TRACK_ID +'/groups?limit=50&offset=' + offset , OAUTH_TOKEN, function(error, data){
if(!err) {
def.resolve(data);
}
else {
def.reject(err);
}
});
return def.promise;
}
Let me know if it works.
Here's complete example, using spex.sequence:
var spex = require("spex")(Promise);
function source(index) {
return new Promise(function (resolve) {
sc.get('/tracks/' + CURRENT_TRACK_ID + '/groups?limit=50&offset=' + index * 50, OAUTH_TOKEN, function (error, data) {
resolve(data.length ? data : undefined);
});
});
}
spex.sequence(source, {track: true})
.then(function (data) {
// data = all the pages returned by the sequence;
});
I don't think it can get simpler than this ;)

Javascript for loop Promises

I have an array of urls like this
var urls = ["www.google.com", "www.yahoo.com"];
And I want to loop though the urls and perform an async task inside the loop and not move on to the next item until the async task has finished. I know you can do this with promises but I have having some trouble with it. Here what I have
var xmlReader = require('cloud/xmlreader.js');
function readResponse_async(xlmString) {
var promise = new Parse.Promise();
xmlReader.read(xlmString, function (err, res) {
if(err) {
promise.reject(err);
} else {
promise.resolve(res);
}
});
return promise;
}
for (i = 0; i < urls.length; i++) {
Parse.Cloud.httpRequest({
url: unionUrls[i],
}).then(function(httpResponse) {
try {
// console.log(httpResponse.text)
return readResponse_async(httpResponse.text)
} catch (e) {console.log(e)}
}
But right now it doesn't wait for the readResponse_async to finish, how can I have it wait for that?
Thanks
EDIT
After reading the response I make a save to my database and I have another array like this
var location = ['USA', 'England'];
And I make the save like this
function saveLoc_async(data, location) {
var i3, i4, i5, m,
TestItem = Parse.Object.extend("TestItem"),//can be reused within the loops?
promise = Parse.Promise.as();//resolved promise to start a long .then() chain
for (i3 = 0; i3 < data.count(); i3++) {
(function(testItem) {
testItem.set("item", data.at(i));
testItem.set("location", location);
//build the .then() chain
promise = promise.then(function() {
return testItem.save();
});
})(new TestItem());
//************************
//CALL retry(); here?
//**************************
}
Because with your answer I have
function retry() {
if (urlsUnion.length > 0) {
var nextUrl = urlsUnion.pop();
//********** ADDED LINE
var nextLoc = location.pop();
Parse.Cloud.httpRequest({
url: nextUrl,
}).then(function(httpResponse) {
xmlReader.read(httpResponse.text, function (err, res) {
if(err) {
// show an error
} else {
//********** ADDED LINE
saveLoc_async(res, nextLoc);
retry();
}
});
});
}
}
SO where should retry(); go because right now with the save sometimes it puts the second location with one of the first items url? why would that happen?
I did something similar to this for an animation.
var actions = [drawXXX, fadeOutYYY, drawXYZ];
this.startAnimation = function () {
actions.reduce(function (previousAction, nextAction) {
return previousAction.then(nextAction)
}, $.when());
}
Your code fires both urls immediately, and does not wait in-between.
What you would have to do is to remove the first url from the array and fire it. In the 'then' branch check if you still have url's in the array and repeat.
Like this (untested, edited to make the code clean again):
var xmlReader = require('cloud/xmlreader.js');
function readResponse_async(xlmString) {
xmlReader.read(xlmString, function (err, res) {
if(err) {
// show an error
} else {
readFirstUrl();
}
});
}
function readFirstUrl() {
if (urlsUnion.length == 0) {
return;
}
var url = urlsUnion.pop();
Parse.Cloud.httpRequest({
url: url,
}).then(function(httpResponse) {
readResponse_async(httpResponse.text);
});
}
readFirstUrl();
Not sure I understand your use of unionUrls array, but if you have your URL's in a urls array, I think this is pretty clean:
function getUrl(url) {
return Parse.Cloud.httpRequest(url)
.then( function(httpResponse) {
return readResponse_async(httpResponse.text);
});
}
urls.reduce( function(prev, url) {
return prev ? prev.then( function() { getUrl(url); }) : getUrl(url);
}, null);

Categories

Resources