Collecting paginated data of unknown size using promises - javascript

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 ;)

Related

Is it possible to resolve a promise while returning?

I'm trying to resolve a promise, however it seems that when I do this:
if (o.TotalCollectionSize - 20 <= index) {
var selectedserver = games.gameservers[Math.floor(Math.random() * games.gameservers.length)]
console.log(selectedserver)
resolve(selectedserver)
return;
}
The promise doesn't resolve, however if the resolve is outside of this if statement, the resolve will work.
(o.TotalCollectionSize - 20 <= index)
this statement will always be true, I need it to stop once it gets to this point, that is why i'm having it resolve here.
The console.log(selectedserver) works fine, and shows me what I need resolved. My issue is that once it gets to the point it needs to resolve at, it doesn't.
getPing(game.placeId, 0).then(function (r) {
console.log(r)
res.end(JSON.stringify(r))
})
getPing is a function that returns a new promise, the (r) value be what is resolved. As mentioned before, my resolve() works outside of the if statement, and I'm not sure why it doesn't work inside. I'm new to promises, so it could be a minor issue.
Edit: This is the entire function for those who need to see,
var getPing = function (id,index) {
return new Promise(function (resolve, reject) {
options.agent = keepAliveAgent
index = index || 0;
var r = https.request(options, function (res) {
var data = []
res.on('data', function (d) {
data.push(d)
}).on('end', function () {
var buf = Buffer.concat(data)
var encodingheader = res.headers['content-encoding']
if (encodingheader == 'gzip') {
zlib.gunzip(buf, function (err, buffer) {
var o = JSON.parse(buffer.toString())
// o is what is returned
if (o.TotalCollectionSize - 20 <= index) {
console.log(o.TotalCollectionSize - 20, '<=', index)
var selectedserver = games.gameservers[Math.floor(Math.random() * games.gameservers.length)]
console.log(selectedserver)
resolve(selectedserver)
return;
}
if (index < o.TotalCollectionSize) {
index = index + 10;
console.log(index, o.TotalCollectionSize)
o.Collection.sort(function (a, b) {
return a.Ping > b.Ping
})
if (typeof (o.Collection[0]) != "undefined") {
var playerscapacity = o.Collection[0].PlayersCapacity.charAt(0)
if (playerscapacity != o.Collection[0].Capacity) {
games.gameservers.push(o.Collection[0])
}
}
getPing(id, index)
}
})
}
})
})
r.end()
//reject('end of here')
})
}
Like I mentioned, all of those code works perfectly fine up until it comes time to resolve the promise.
I'm going to attempt to answer this without really knowing what your function is supposed to do or how it's supposed to work (because you haven't told us), so bear with me.
One rule of thumb with promises is that new Promise should be used sparingly, and when it is used, it should be kept as simple as possible. It should contain only the non-Promise asynchronous functionality that you're trying to promisify, and the rest of your logic should be working on the promises that you obtain from that. It shouldn't be a giant container for a callback-laden mess.
In your case, you have two asynchronous operations: a buffered HTTP request and GZIP extraction, so let's create separate functions for those:
function requestBufferedData(options) {
return new Promise(function (resolve, reject) {
// TODO: Needs to reject() in situation where request fails or else
// this promise will never complete when there's an error
var r = https.request(options, function (res) {
var data = []
res.on('data', function (d) {
data.push(d);
}).on('end', function () {
resolve({
data: Buffer.concat(data),
encoding: res.headers['content-encoding'],
});
});
});
r.end();
});
}
function extractGzip(data) {
return new Promise(function (resolve, reject) {
zlib.gunzip(data, function (err, buffer) {
if (err) { reject(err); }
else { resolve(buffer); }
});
});
}
Now that we have these, we can breathe a little more easily. After about 10 minutes of looking at the rest of your code, I still can't make heads or tails of it, so I'm going to have to power through it. What's clear is that you have a recursive process that retries your HTTP requests until it finds the value it wants, so we'll go with that:
function getPing(id, index) {
options.agent = keepAliveAgent;
return requestBufferedData(options)
.then(function (result) {
if (result.encoding !== 'gzip') {
throw new Error('Response is not gzip');
}
return extractGzip(result.data);
})
.then(JSON.parse)
.then(function (o) {
if (o.TotalCollectionSize - 20 <= index) {
console.log(o.TotalCollectionSize - 20, '<=', index)
var selectedserver = games.gameservers[Math.floor(Math.random() * games.gameservers.length)]
console.log(selectedserver)
return selectedServer;
}
if (index < o.TotalCollectionSize) {
var nextIndex = index + 10;
console.log(nextIndex, o.TotalCollectionSize)
o.Collection.sort(function (a, b) {
return a.Ping > b.Ping
});
if (typeof (o.Collection[0]) != "undefined") {
var playerscapacity = o.Collection[0].PlayersCapacity.charAt(0);
if (playerscapacity != o.Collection[0].Capacity) {
games.gameservers.push(o.Collection[0])
}
}
return getPing(id, nextIndex);
}
throw new Error("Didn't match either condition");
});
}
I believe this should correctly resolve the promise when the condition is met, but I have no way of testing this, so let me know if you still have trouble with this.

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.

How to reuse promises?

I am trying to reuse the the data returned from promise here. But, the problem is, after the first call to checkPromise function, it immediately calls the second function, and the promise for the first function is not fulfilled, so it never returns any data, and hence it never enters the if clause. How do I reuse a promise?
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return new Promise(function (resolve, reject) {
request(url).spread(function(response, body) {
return resolve(body);
}).catch(function(err) {
console.error(err);
return reject(err);
});
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
}
checkPromise(url);
checkPromise(url);
You likely have a timing issue. Your apiCall() function is asynchronous. That means it finishes sometime later. As such, each time you call checkPromise(), all you're doing is starting a request and it finishes sometime later. So, you call it the first time and it starts a request (that has not finished yet). Then, your next call to checkPromise() gets called and it does it's if check before the first call has completed. Thus, it finds nothing in the cache yet.
Your code is running two requests in parallel, not one after the other.
If you actually want to wait until the first request is done before executing the second one, then you will have to actually structure your code to do that. You would need to make checkPromise() return a promise itself so code using it could known when it was actually done in order to execute something after it was done.
FYI, I don't see anything in your code that is actually related to reusing promises (which is something you cannot do because they are one-shot objects).
Here's one possible implementation:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
return Promise.resolve(rp);
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
}
}
checkPromise(url).then(function() {
checkPromise(url);
});
Significant changes:
Return the promise returned by request() rather than create yet another one.
Change checkPromise() so it always returns a promise whether the value is found in the cache or not so calling code can always work consistently.
Sequence the two checkPromise() calls so the first can finish before the second is executed.
A very different approach would be to actually wait on the cache if a result you are interested in is already being loaded. That could be done like this:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
// If it's a promise object in the cache, then loading
// If it's a value, then the value is already available
// Either way, we wrap it in a promise and return that
return Promise.resolve(obj[url]);
} else {
var p = apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
obj[url] = p;
return p;
}
}
checkPromise(url).then(function(result) {
// use result
});
checkPromise(url).then(function(result) {
// use result
});
few problems with your code, first in apiCall, you are doing a promise ant-pattern( no need for that new promise), second your checkPromise is doing a sync operation, so it must either return a promise or have a callback argument, so you code can be changed into:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
}).catch(function(err) {
console.error(err);
throw err;
});
}
function checkPromise(url) {
var promise = Promise.resolve();
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
return promise;
}
checkPromise(url).then(function(){
return checkPromise(url);
});
Given the way you are globally storing the result in 'obj[url]', it'd probably be easiest to do
function checkPromise(url) {
if (!obj[url]) obj[url] = apiCall(url);
obj[url].then(function(result) {
//do something
});
}
to basically make the request, if it hasn't already started, then attach a listener to the promise for when the result has loaded.
Here is the simplest example of how to prevent multiple API calls if there are multiple similar request for something (cache check for example)
var _cache = {
state: 0,
result: undefined,
getData: function(){
log('state: ' + this.state);
if(this.state === 0 ){ // not started
this.state = 1; // pending
this.promise = new Promise(function(resolve, reject) {
return (apiCall().then(data => { _cache.result = data; _cache.state = 2; resolve(_cache.result) }));
})
return this.promise;
}
else if(this.state === 1){ // pending
return this.promise;
}
else if(this.state === 2){// resolved
return Promise.resolve(this.result);
}
},
};
Simulating api call
function apiCall(){
return new Promise(function(resolve, reject) {
log('in promise')
setTimeout(() => {
log('promise resolving')
resolve(1);
}, 1000);
})
}
Making simultaneous requests.
_cache.getData().then(result => { log('first call outer: ' + result);
_cache.getData().then(result => { log('first call inner: ' + result); });
});
_cache.getData().then(result => { log('second call outer: ' + result);
_cache.getData().then(result => { log('second call inner: ' + result); });
});
Only one API call is maden. All others will wait for completion or use the resolved result if it already completed.

Javascript timeout loop to save multiple rows to Parse.com

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");
});

Categories

Resources