Wrap localForage setItem calls in Promise.all() - javascript

I want to refresh my indexeddb store with new data after a successful login. After the data refresh is complete, I want to redirect to the landing page. My problem is that I have 1000+ calls to setItem and they aren't finishing.
var app = {
Login: function () {
WebService.Login($("#username").val(), $("#password").val())
.then(function () {
// TODO: refresh data and then redirect...
UpdateData().then(function() {
window.location.href = '/Home';
});
})
.catch(function (err) {
console.log("error logging in");
});
},
UpdateData: function () {
return fetch('/api/Customer').then(function (response) {
return response.json();
})
.then(function (data) {
var customerStore = localforage.createInstance({ name: "customers" });
// Refresh data
customerStore.clear().then(function () {
data.forEach(function (c) {
// How do I know when all setItem calls are complete??
customerStore.setItem(String(c.CustomerID), c);
});
});
})
.catch(function (err) {
console.log("Data error", err);
});
}
}
I'm still relatively new to promises but there must be a way I can get all of the setItem calls into a Promise.all() that I can return. How can I do this?

I think that you need something like this:
return fetch("/api/Customer")
.then(function(response) {
return response.json();
})
.then(function(data) {
var customerStore = localforage.createInstance({ name: "customers" });
// Refresh data
return customerStore.clear().then(function() {
return Promise.all(
data.map(function(c) {
return customerStore.setItem(String(c.CustomerID), c);
})
);
});
})
.catch(function(err) {
console.log("Data error", err);
});
data.map will return an array of promises and then we also return the aggregate promise (from Promise.all).
You should also keep a reference of the customerStore for later use.
Also, if the amount of data is huge, you might want to use localForage-setItems to make the operation a bit more performant (but try to avoid a possible premature optimization).

Related

return results from .then()

Ok, so I need to connect to a MySQL database through SSH and the connection works fine. I am able to execute queries with no problem. I can also get the results and print it out. The thing is, I need something simpler since I will have to send a lot of queries to this database. Below is the code for a promise which creates a connection to this database.
const SSHConnection = new Promise((resolve, reject) => {
sshClient.on('ready', () => {
sshClient.forwardOut(
forwardConfig.srcHost,
forwardConfig.srcPort,
forwardConfig.dstHost,
forwardConfig.dstPort,
(err, stream) => {
if (err) reject(err);
const updatedDbServer = {
...dbServer,
stream
};
const connection = mysql.createConnection(updatedDbServer);
connection.connect((error) => {
if (error) {
reject(error); // Return error
}
resolve(connection); // OK : return connection to database
});
});
}).connect(tunnelConfig);
});
Then I have this piece of code that gives me access to said connection and allows me to send queries. The problem is that I need the return value of my queries and be able to use it in other modules for my project. For example, export a single function to be used to send queries like sendQuery('Enter SQL here').
function sendQuery(sql) {
SSHConnection.then(
function(connection) {
connection.query(
sql,
function(err, results, fields) {
return results; // <---------- I want to return this from the function 'sendQuery()'
}
);
},
function(error) {
console.log("Something wrong happened");
}
);
}
I can work with the results inside SSHConnection.then() but this isn't functional for me.
I would like to have something similar below to work.
// Main function
(async function() {
let res = sendQuery(`SELECT 23+2 AS Sum;`);
console.log(res); // Outputs Sum: 25
})();
So to my question. Is there a way to access the results from a query inside of a promise.then(), from the outside?
I think the problem is you need to add another return statement to your code.
function sendQuery(sql) {
return SSHConnection.then(
function(connection) {
return connection.query(
sql,
function(err, results, fields) {
return results; // <---------- I want to return this from the function 'sendQuery()'
}
);
},
function(error) {
console.log("Something wrong happened");
}
);
}
This should return the results from the query properly IF connection.query returns a promise. I'm not sure if it does. If it does then you can just execute the function like so.
// Main function
(async function() {
let res = await sendQuery(`SELECT 23+2 AS Sum;`);
console.log(res); // Outputs Sum: 25
})();
If connection.query does not return a promise then I suppose you could wrap it in a promise like so.
function sendQuery (sql) {
return SSHConnection.then(
function (connection) {
return new Promise((resolve, reject) => {
connection.query(
sql,
function (err, results, fields) {
if (err)reject(err)
resolve(results) // <---------- I want to return this from the function 'sendQuery()'
}
)
})
},
function (error) {
console.log('Something wrong happened')
}
)
}
love the name by the way...really liked that movie. As to your question, I'd suggest a couple of things:
if you have a lot of queries coming, you might consider moving the connection independent of the query, so that the connection setup and teardown isn't part of the cost of time for the query itself. If you have a single connection, single DB, etc., then you could instantiate the connection once, at startup, and then leave the connection open, and reference it in your queries.
your question:
function sendQuery(sql) {
const resValues = await SSHConnection.then(
function(connection) {
connection.query(
sql,
function(err, results, fields) {
return results; // <---------- I want to return this from the function 'sendQuery()'
}
);
},
function(error) {
console.log("Something wrong happened");
}
);
return(resValues); // or handle error cases
}
Note I added a return value from the ".then()" call, which captures your "results" return value, and then returns that from the parent function (sendQuery)

How to build a Vue Axios service to return promise tree

I know I'm making this harder then it should be.
A vue method
methods: {
userLogin: function()
{
var loginJson = []
loginJson = JSON.stringify(this.login);
UserService.login(loginJson);
//Somehow read the promise, then and catch.
// like
// loginReturn.then(function(response){
// console.log(response);
// })
//.catch(function(error){
// console.log(error);
//})
}
}
Trying to create some service for vue in a js file.
var axios = require('axios')
export default { //I don't think this is correct?
//Set up some build variable
login(data){
let baseUrl = "http://coolwebsite.com/api/user/login";
return axios.post(baseUrl, data);
// .then(function (response) {
// console.log(response);
// })
// .catch(function (error) {
// console.log(error);
// });
}
}
I would like to return the promise back to the function. Let the login function handle the promise and figure out what needs to happen on the screen.
Based on what you've posted, it would be:
UserService.login(loginJson).then(response => {
// handle response...
}).catch(error => {
// handle error...
})
You've already setup login to return the promise from axios, so just continue the chain.

Javascript consecutive promises not being fired

I am using PhantomJS to login a website using node.js. After that I want to make an HTTP request to obtain a json with data. My code is as follows:
phantom.create()
.then(function (ph) {
this.ph = ph;
return ph.createPage();
})
.then(function (page) {
this.page = page;
return page.open('https://example.com');
})
.then(function (status) {
if ( status === "success" ) {
return this.page.evaluate(function() {
document.querySelector("input[name='j_username']").value = 'example#example.net';
document.querySelector("input[name='j_password']").value = 'example';
console.log('Submitting logging...');
document.querySelector("input[name='submit']").click();
});
} else {
return null;
}
})
.then(function() {
console.log("Logged in!");
this.page.property('onResourceRequested', function(requestData, networkRequest) {
//This event is fired 40 times, only one is interesting to me the one that has the text maps.json in it
if (requestData.url.indexOf('maps.json') !== -1) {
return {headers: requestData.headers, url: requestData.url};
}
});
});
.then(function (data) {
//This then block is only fired once, with the first call of the first url in previous then block and data is null
if (data) {
// This code block is never fired because this then is only called once with data=null
console.log(data);
request.get({
uri: data.url,
headers: data.headers
}, function(err, res, body){
if (!err) {
callback(null, res.headers);
} else {
console.log("Error getting data from URL: "+ err);
callback(true, null);
}
});
}
});
There must be something wrong with this subsequent promises because the this.page.property('onResourceRequested'... function in the penultimate then block is fired like 40 times (one per each url called inside the website after login) but the last then is only fired once (when first url is requested).
I want to obtain the data from one concrete url (the one that contains maps.json in the url) which is call number 32.
What am I doing wrong? How can I fire the last then only when my call is done?
EDIT:
Following #charlieetfl's advice I canged the code to the following, but still not working...:
phantom.create()
.then(function (ph) {
this.ph = ph;
return ph.createPage();
})
.then(function (page) {
this.page = page;
return page.open('https://example.com');
})
.then(function (status) {
if ( status === "success" ) {
return this.page.evaluate(function() {
document.querySelector("input[name='j_username']").value = 'example#example.net';
document.querySelector("input[name='j_password']").value = 'example';
console.log('Submitting logging...');
document.querySelector("input[name='submit']").click();
});
} else {
return null;
}
})
.then(function() {
console.log("Logged in!");
return new Promise(function(resolve, reject) {
this.page.property('onResourceRequested', function(requestData, networkRequest) {
//This event is fired 40 times, only one is interesting to me the one that has the text maps.json in it
if (requestData.url.indexOf('maps.json') !== -1) {
resolve({headers: requestData.headers, url: requestData.url});
}
});
});
});
.then(function (data) {
//This then block is only fired once, with the first call of the first url in previous then block and data is null
if (data) {
// This code block is never fired because this then is only called once with data=null
console.log(data);
request.get({
uri: data.url,
headers: data.headers
}, function(err, res, body){
if (!err) {
callback(null, res.headers);
} else {
console.log("Error getting data from URL: "+ err);
callback(true, null);
}
});
}
});
EDIT: 21/07/2017 10:52 UTC.
I changed the code to this new one:
phantom.create().then(function(ph) {
ph.createPage().then(function(page) {
page.open('https://example.com/').then(function(status) {
if ( status === "success" ) {
page.evaluate(function() {
document.querySelector("input[name='j_username']").value = 'example';
document.querySelector("input[name='j_password']").value = 'example';
console.log('Submitting logging...');
document.querySelector("input[name='submit']").click();
}).then(function(){
page.property('onResourceRequested', function(requestData) {
if (requestData.url.indexOf('geomaps.json') !== -1) {
console.log(JSON.stringify(datos));
request.get({
uri: requestData.url,
headers: requestData.headers
}, function(err, res, body){
if (!err) {
console.log(res.headers);
} else {
console.log("Error getting data from website: "+ err);
}
});
}
});
});
}
});
});
});
Same result. The console.log(JSON.stringify(datos)); is being fired but the request.get is never being fired.
I thing may have something to do with firing async funcions inside promises?
EDIT 21/07/2017 11:09 UTC
More tests. If I simplify the page.property('onResourceRequested' code block I see that the then() is called only once and before the requestData is received for each call...
I am a little bit confused and I don't know right now how to approach this...
I finally resolved the issue.
page.property('onResourceRequested' executes in the PhantomJS process. PhantomJS does not share any memory or variables with node. So using closures in javascript to share any variables outside of the function is not possible.
using page.on('onResourceRequested' instead fixed the issue!

Return 2 variables using bluebird promises

I am trying to write a promise function using Bluebird library for nodejs. I want to return 2 variables from my function.
I want the first function to return immediately and the second to complete its own promise chain before returning.
function mainfunction() {
return callHelperfunction()
.then(function (data) {
//do something with data
//send 200 Ok to user
})
.then(function (data2) {
//wait for response from startthisfunction here
})
.catch(function (err) {
//handle errors
});
}
function callHelperfunction() {
return anotherHelperFunction()
.then(function (data) {
return data;
return startthisfunction(data)
.then(function () {
//do something more!
})
});
}
Just like regular functions only have one return value, similarly promises only resolve with one value since it's the same analogy.
Just like with regular functions, you can return a composite value from a promise, you can also consume it using .spread for ease if you return an array:
Promise.resolve().then(function(el){
return [Promise.resolve(1), Promise.delay(1000).return(2));
}).spread(function(val1, val2){
// two values can be accessed here
console.log(val1, val2); // 1, 2
});
The only thing that appears to be wrong is the expectation that do something with data; send 200 Ok to user; should be performed in mainfunction(), part way through the promise chain in callHelperfunction().
This can be overcome in a number of ways. Here's a couple :
1. Move do something with data; send 200 Ok to user; into callHelperfunction()
function mainfunction() {
return callHelperfunction())
.catch(function (err) {
//handle errors
});
}
function callHelperfunction() {
return anotherHelperFunction()
.then(function (data1) {
//do something with data
//send 200 Ok to user
return startthisfunction(data1)
.then(function (data2) {
//wait for response from startthisfunction here
//do something more!
});
});
}
2. Dispense with callHelperfunction() altogether and do everything in mainfunction()
function mainfunction() {
return anotherHelperFunction()
.then(function (data1) {
//do something with data1
//send 200 Ok to user
return startthisfunction(data1);
})
.then(function (data2) {
//wait for response from startthisfunction here
})
.catch(function (err) {
//handle errors
});
}

AngularJS $q promises success callback gets executed in the wrong order

I have a problem with the following code:
initPromise = $q.all(arrayOfPromises)
.then(function () {
return $scope.methodWhichReturnsPromise()
.then(function (data) {
console.log("report data");
return data;
});
});
if ($scope.showCompare) {
initPromise
.then(function () {
return $q.all(anotherArrayOfPromises);
})
.then(function () {
return aMethodWhichReturnsAPromise().then(function () {
console.log("compare report data");
});
});
}
initPromise
.then(function () {
console.log("generate view data");
})
.finally(function () {
console.log("finally");
});
I'm loading a bunch of async data when loading a controller based on route parameters. And if the flag showCompare is there, I want to load something in between. But the order of the console.log messages is the following:
report data
generate view data
finally
compare report data
I was expecting that compare report data would show up exactly in the same order it was written in the code.
What am I doing wrong?
You are adding two distinct handler on the initPromise, instead of chaining all .then() calls. To do so, you would need to use
if ($scope.showCompare) {
initPromise = initPromise.then(…);
}

Categories