Javascript for loop Promises - javascript

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

Related

How do I make sure a function is completely executed before its value is passed to res.render?

I want to parse the address query and return the addresses and their titles such that
http://localhost:3000/I/want/title/?address=google.com&address=youtube.com shall return:
google.com - 'google', youtube.com - 'youtube'
I'm using cheerio.js to extract the title from the URLs but it takes time and the res.render line is executed before the variable titles is filled with the URL titles. How do I make sure that my code for retrieving the titles is executed before the res.render?
As of now, I'm not getting any errors but the titles[] array is sent without data to my .ejs file. I've tried solving this through callbacks, step.js, async.js but nothing seems to work. I've tried solving it using rsvp.js (promise) as shown below (from app.js) but it doesn't work either and titles[] remains empty:
app.get("/I/want/title/", function(req,res){
if (typeof req.query.address === "string"){
query = [req.query.address];
}
else {
query = req.query.address;
}
var titles=[];
var promise = new RSVP.Promise(function(resolve, reject) {
for (i=0;i<(query.length);i++){
if (!((query[i]).startsWith("https://www."))){
var url = "https://www." + query[i];
}else{
url=query[i];
}
request(url, function (err, resp, body) {
if (err) {
var title = "NO RESPONSE"
} else {
var $ = cheerio.load(body);
var title = $("title").text();
}
titles.push(title);
});
}
resolve(titles);
reject();
});
promise.then(function(titles) {
res.render("title", {url: query, siteName: titles});
}).catch(function() {
console.log("oh no");
});
});
Is there something wrong with my syntax or logic? How should I execute this with either callbacks or promises?
You have to put reject & resolve of promise inside request callback, then your code should work fine (as shown below).
app.get('/I/want/title/', function(req, res) {
if (typeof req.query.address === 'string') {
query = [req.query.address]
} else {
query = req.query.address
}
var titles = []
var promise = new RSVP.Promise(function(resolve, reject) {
for (i = 0; i < query.length; i++) {
if (!query[i].startsWith('https://www.')) {
var url = 'https://www.' + query[i]
} else {
url = query[i]
}
request(url, function(err, resp, body) {
if (err) {
var title = 'NO RESPONSE'
reject()
} else {
var $ = cheerio.load(body)
var title = $('title').text()
}
titles.push(title)
resolve(titles)
})
}
})
promise
.then(function(titles) {
res.render('title', {url: query, siteName: titles})
})
.catch(function() {
console.log('oh no')
})
})
Does this answer your question ?
Your resolve should be inside the else part of the request callback, and the reject should be inside the if (err).

Angular - For Loop HTTP Callback/Promise

I am trying to write a loop which performs a number of http requests and adds each response to a list.
However, I don't think I am going about it quite the right way.
I think I am not implementing the required promises correctly. The console log after the for loop shows myList array as empty.
Code:
var _myList = []
function getStuff() {
var deferred = $q.defer()
var url = someUrl
$http.get(url).success(function(response) {
if ( response.array.length > 0 ) {
// loop starts here
for ( var i=0; i < response.array.length; i++ ) {
getThing(response.array[i].id);
};
// check the varibale here
console.log(_myList);
deferred.resolve('Finished');
} else {
deferred.resolve('No stuff exists');
};
}).error(function(error) {
deferred.reject(error);
});
return deferred.promise;
};
function getThing(thindId) {
var deferred = $q.defer()
var url = someUrl + thingId;
$http.get(url).success(function(response) {
_myList.push(response);
deferred.resolve(response);
}).error(function(error) {
deferred.reject(error);
});
return deferred.promise;
};
You can simplify your code as follows:
var allThings = response.array.map(function(id){
var singleThingPromise = getThing(id);
//return a single request promise
return singleThingPromise.then(function(){
//a getThing just ended inspect list
console.log(_myList);
})
});
$q.all(allThings).then(function(){
//only resolve when all things where resolved
deferred.resolve('Finished');
}, function(e){
deferred.reject('Something went wrong ' + e);
});
You indeed won't be able to populate _myList array with for-loop like you set up. Instead create an array of promises - one per data item in response.array and return it as inner promise.
function getStuff() {
var url = someUrl;
return $http.get(url).then(function(response) {
if (response.data.array.length > 0) {
return $q.all(response.data.array.map(function(data) {
return getThing(data.id);
}));
} else {
return 'No stuff exists';
}
});
}
function getThing(thindId) {
var url = someUrl + thingId;
return $http.get(url).then(function(response) {
return response.data;
});
}
After that you would use getStuff like this:
getStuff().then(function(myList) {
console.log(myList);
});

Combining two promises

I am really new to JavaScript and promises and to be honest I don't fully understand how promises work so I need some help.
I am using Google Cloud Messaging to push notifications from my site to my users. When users receive a notification and clicks on it, it opens a URL stored in a IndexedDB.
importScripts('IndexDBWrapper.js');
var KEY_VALUE_STORE_NAME = 'key-value-store', idb;
function getIdb() {
if (!idb) {
idb = new IndexDBWrapper(KEY_VALUE_STORE_NAME, 1, function (db) {
db.createObjectStore(KEY_VALUE_STORE_NAME);
});
}
return idb;
}
self.addEventListener('notificationclick', function (event) {
console.log('On notification click: ', event);
event.notification.close();
event.waitUntil(getIdb().get(KEY_VALUE_STORE_NAME, event.notification.tag).then(function (url) {
var redirectUrl = '/';
if (url) redirectUrl = url;
return clients.openWindow(redirectUrl);
}));
});
So in the code above, I know that the getIdb()...then() is a promise, but is the event.waitUntil also a promise?
The problem with the above code is that it opens a instance of Chrome every time the notification is clicked and I would prefer that it would utilize an existing instance if available. The following does just that:
self.addEventListener('notificationclick', function(event) {
console.log('On notification click: ', event.notification.tag);
event.notification.close();
event.waitUntil(
clients.matchAll({
type: "window"
})
.then(function(clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == '/' && 'focus' in client)
return client.focus();
}
if (clients.openWindow) {
return clients.openWindow('/');
}
})
);
});
However, now I have two promises, getIdb and clients.matchAll and I really have no idea how to combine the two promises and the two sets of code. Any help would be greatly appreciated. Thanks!
For reference, here is IndexDBWrapper.js:
'use strict';
function promisifyRequest(obj) {
return new Promise(function(resolve, reject) {
function onsuccess(event) {
resolve(obj.result);
unlisten();
}
function onerror(event) {
reject(obj.error);
unlisten();
}
function unlisten() {
obj.removeEventListener('complete', onsuccess);
obj.removeEventListener('success', onsuccess);
obj.removeEventListener('error', onerror);
obj.removeEventListener('abort', onerror);
}
obj.addEventListener('complete', onsuccess);
obj.addEventListener('success', onsuccess);
obj.addEventListener('error', onerror);
obj.addEventListener('abort', onerror);
});
}
function IndexDBWrapper(name, version, upgradeCallback) {
var request = indexedDB.open(name, version);
this.ready = promisifyRequest(request);
request.onupgradeneeded = function(event) {
upgradeCallback(request.result, event.oldVersion);
};
}
IndexDBWrapper.supported = 'indexedDB' in self;
var IndexDBWrapperProto = IndexDBWrapper.prototype;
IndexDBWrapperProto.transaction = function(stores, modeOrCallback, callback) {
return this.ready.then(function(db) {
var mode = 'readonly';
if (modeOrCallback.apply) {
callback = modeOrCallback;
}
else if (modeOrCallback) {
mode = modeOrCallback;
}
var tx = db.transaction(stores, mode);
var val = callback(tx, db);
var promise = promisifyRequest(tx);
var readPromise;
if (!val) {
return promise;
}
if (val[0] && 'result' in val[0]) {
readPromise = Promise.all(val.map(promisifyRequest));
}
else {
readPromise = promisifyRequest(val);
}
return promise.then(function() {
return readPromise;
});
});
};
IndexDBWrapperProto.get = function(store, key) {
return this.transaction(store, function(tx) {
return tx.objectStore(store).get(key);
});
};
IndexDBWrapperProto.put = function(store, key, value) {
return this.transaction(store, 'readwrite', function(tx) {
tx.objectStore(store).put(value, key);
});
};
IndexDBWrapperProto.delete = function(store, key) {
return this.transaction(store, 'readwrite', function(tx) {
tx.objectStore(store).delete(key);
});
};
One way to deal with multiple promises is with Promise.all
Promise.all([promise0, promise1, promise2]).then(function(valArray) {
// valArray[0] is result of promise0
// valArray[1] is result of promise1
// valArray[2] is result of promise2
});
read about promise.all - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
event.waitUntil() takes a promise - this allows the browser to keep your worker alive until you've finished what you want to do (i.e. until the promise that you gave to event.waitUntil() has resolved).
As the other answer suggests, you can use Promise.all() within event.waitUntil. Promise.all() takes an array of promises and returns a promise, so you can call then on it. Your handling function will get an array of promise results when all of the promises you've provided to Promise.all have resolved. Your code will then look something like this (I haven't actually tested this, but it should be close):
self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(Promise.all([
getIdb().get(KEY_VALUE_STORE_NAME, event.notification.tag),
clients.matchAll({ type: "window" })
]).then(function (resultArray) {
var url = resultArray[0] || "/";
var clientList = resultArray[1];
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == '/' && 'focus' in client)
return client.focus();
}
if (clients.openWindow) {
return clients.openWindow(url);
}
}));
});
Though I am very late to the party but,
I think you can use this very light, vanilla JS library that does things for you. https://www.npmjs.com/package/easy-promise-all
Here is the small code sample for it.
var { EasyPromiseAll } = require('easy-promise-all');
EasyPromiseAll({
resultFromPromise1: Promise.resolve('first'),
resultFromPromise2: Promise.reject('second'),
}).then((results) => {
const { resultFromPromise1, resultFromPromise2 } = results;
console.log(resultFromPromise1, resultFromPromise2);
});

How to use defer() the right way?

How do I use defer correctly? I've got two functions here, in which one defer is used correctly? In which one it is used incorrectly? And why resp. why not?
First example:
getFoo1: function() {
var dfd = $q.defer();
db.allDocs({include_docs: true}, function(err, response) {
if(err) {
console.log(err);
dfd.reject(err);
} else {
var qcs = [];
for(var i=0; i < response.total_rows; i++) {
if(response.rows[i].doc.type == 'bar') {
var qc = {id: response.rows[i].doc._id,
isFoo: true
};
oneFunction(qc)
.then(anotherFunction(qc))
.then(qcs.push(qc));
}
}
dfd.resolve(qcs);
}
});
return dfd.promise;
},
Second example:
getFoo2: function() {
var dfd = $q.defer();
db.allDocs({include_docs: true}, function(err, response) {
if(err) {
dfd.reject(err);
} else {
dfd.resolve(response);
}
});
return dfd.promise
.then(function(response) {
var foo = [];
for(var i=0; i < response.total_rows; i++) {
if(response.rows[i].doc.type == 'bar') {
var qc = {id: response.rows[i].doc._id,
isFoo: true
};
return oneFunction(qc)
.then(anotherFunction(qc))
.then(foo.push(qc));
}
}
}, function(err){
console.log(err);
});
},
The oneFunction does nothing asynchronously.
The anotherFunction does something asynchronously (retrieving data from an external database).
EDIT: Thanks to #Bergi the correct function should look like this:
getFoo3: function() {
var dfd = $q.defer();
db.allDocs({include_docs: true}, function(err, response) {
if(err) {
dfd.reject(err);
} else {
dfd.resolve(response);
}
});
return dfd.promise
.then(function(response) {
var foos = [];
for (var i=0; i < response.total_rows; i++) {
if (response.rows[i].doc.type == 'bar') {
var qc = {id: response.rows[i].doc._id,
isFoo: true
};
var foo = oneFunction(qc);
foos.push(foo);
}
}
var promises = foos.map(anotherFunction); // make a promise for each foo
return $q.all(promises);
}, function(err){
console.log(err);
});
},
You've used $q.defer correctly in the second example[1] - creating a promise for the response of the db.allDocs asynchronous call (and nothing else). Altough pochdb seems to already return promises, as #Benjamin mentions in the comments, so it's unnecessary (but not wrong).
The first example is just a mess, convoluting the construction of the promise with the error logging and that ominous loop.
1: Except for dfd.promise(), which is not a function but a property. Go for dfd.promise.then(…).
However, that loop is a very different topic, and seems to be wrong in both snippets. Some points:
In the second snippet, your return from the callback function in the body of the loop, right on the first iteration that fulfills the predicate.
If oneFunction(qc) is not asynchronous, it doesn't need to (read: shouldn't) return a promise - or if it does not, that .then(…) call is wrong.
anotherFunction(qc) seems to return a promise (it's asynchronous as you say). But you must not pass that promise to .then(), a callback function is expected there!
Same thing for foo.push(qc) - it's not a callback function that you would pass to .then().
After all, you are doing something asynchronous in that loop. Here, you have to use Promise.all now!
If I had to bet, I'd say you need
.then(function(response) {
var foos = [];
for (var i=0; i < response.total_rows; i++) {
if (response.rows[i].doc.type == 'bar') {
var qc = {id: response.rows[i].doc._id,
isFoo: true
};
var foo = oneFunction(qc);
foos.push(foo);
}
}
var promises = foos.map(anotherFunction); // make a promise for each foo
return $q.all(promises);
})

How to call a function after an asynchronous for loop of Object values finished executing

I want to call a function after an asynchronous for loop iterating through values of an Javascript object finishes executing. I have the following code
for (course in courses) {
var url = '...' + courses[course];
request(url, (function (course) {
return function (err, resp, body) {
$ = cheerio.load(body);
//Some code for which I use object values
};
})(course));
}
This can be done in vanilla JS, but I recommend the async module, which is the most popular library for handling async code in Node.js. For example, with async.each:
var async = require('async');
var courseIds = Object.keys(courses);
// Function for handling each course.
function perCourse(courseId, callback) {
var course = courses[courseId];
// do something with each course.
callback();
}
async.each(courseIds, perCourse, function (err) {
// Executed after each course has been processed.
});
If you want to use a result from each iteration, then async.map is similar, but passes an array of results to the second argument of the callback.
If you prefer vanilla JS, then this will work in place of async.each:
function each(list, func, callback) {
// Avoid emptying the original list.
var listCopy = list.slice(0);
// Consumes the list an element at a time from the left.
// If you are concerned with overhead in using the shift
// you can accomplish the same with an iterator.
function doOne(err) {
if (err) {
return callback(err);
}
if (listCopy.length === 0) {
return callback();
}
var thisElem = listCopy.shift();
func(thisElem, doOne);
}
doOne();
}
(taken from a gist I wrote a while back)
I strongly suggest that you use the async library however. Async is fiddly to write, and functions like async.auto are brilliant.
A possible simple JS solution would be to do something like this.
var courses = {
lorum: 'fee',
ipsum: 'fy',
selum: 'foe'
};
var keys = Object.keys(courses);
var waiting = keys.length;
function completedAll() {
console.log('completed all');
}
function callOnCourseComplete(course, func) {
console.log('completed', course);
waiting -= 1;
if (!waiting) {
func();
}
}
var delay = 10000;
keys.forEach(function(course) {
var url = '...' + courses[course];
console.log('request', url);
setTimeout((function(closureCourse) {
return function( /* err, resp, body */ ) {
// Some code for which I use object values
callOnCourseComplete(closureCourse, completedAll);
};
}(course)), (delay /= 2));
});
Update: Probably a better Javascript solution would be to use Promises
const courses = {
lorum: 'fee',
ipsum: 'fy',
selum: 'foe',
};
function completedAll() {
console.log('completed all');
}
function callOnCourseComplete(courseName) {
console.log('completed', courseName);
}
let delay = 10000;
const arrayOfPromises = Object.keys(courses).map(courseName => (
new Promise((resolve, reject) => {
const url = `...${courses[courseName]}`;
console.log('request', url);
setTimeout((err, resp, body) => {
if (err) {
reject(err);
}
// Some code for which I use object values
resolve(courseName);
}, (delay /= 2));
}))
.then(callOnCourseComplete));
Promise.all(arrayOfPromises)
.then(completedAll)
.catch(console.error);

Categories

Resources