I am trying to apply a promise to getting cookies from the browser
browserCookies = {
art_rfp : '',
art_token : ''
};
var promise = new Promise((resolve, reject) => {
chrome.cookies.getAll({"url":"https://url.com"}, function (cookies) {
for(var i=0; i<cookies.length; i++){
var name = cookies[i].name
// console.log(name)
if (name == 'sso_rfp') {
console.log(name) // line 13
browserCookies.art_rfp = cookies[i].value
resolve('cookies found')
}
else if (name == 'sso_token') {
console.log(name) // line 18
browserCookies.art_token = cookies[i].value
}
else {
reject('no cookies found')
}
}
});
})
promise.then((message) => {
console.log(message)
}).catch((message) =>{
console.log(message)
})
However it is just failing.
background.js:13 sso_rfp
background.js:18 amzn_sso_token
background.js:32 no cookies found
why isn't it resolving?
Promises can only be rejected / resolved once.
So what's likely happening inside your loop, the first cookie's name is neither sso_rfp or sso_token as such it will call reject('no cookies found'), so even if more are found later, they cannot get resolved because the reject has already been called.
So what you want to do is keep a track using a simple boolean wherever a cookie was found or not found, and then resolve / reject at the end.
eg..
var promise = new Promise((resolve, reject) => {
chrome.cookies.getAll({"url":"https://url.com"}, function (cookies) {
var found = false;
for(var i=0; i<cookies.length; i++){
var name = cookies[i].name
// console.log(name)
if (name == 'sso_rfp') {
console.log(name) // line 13
browserCookies.art_rfp = cookies[i].value
found = true;
}
else if (name == 'sso_token') {
console.log(name) // line 18
browserCookies.art_token = cookies[i].value;
found = true;
}
}
if (found) resolve("Cookies Found");
else reject("no cookies found");
});
})
Also just a coding standard, I would personally avoid using a global browserCookies, and instead resolve with these values instead.
Related
I am using Data Storage from Ionic Framework. In my code, I have my auth guard which checks user info is stored or not. Code is :
this.storage.get('user_info').then((data) => {
let user_info = JSON.parse(data);
if(user_info == null)
{
this._userIsLoggedIn = false;
} else {
this._userIsLoggedIn = user_info.isLogin;
}
});
return this._userIsLoggedIn;
I am only getting the default value that I set. How do I wait for the promise to finish before it is returning?
If you using promises in a method and want to return the result of the promise, you should also promisify your method too. You can achieve this in two way:
Return a promise
Or use async-await
Approach 1 :
return new Promise( resolve => {
this.storage.get('user_info').then((data) => {
let user_info = JSON.parse(data);
if(user_info == null)
{
this._userIsLoggedIn = false;
} else {
this._userIsLoggedIn = user_info.isLogin;
}
return resolve(this._userIsLoggedIn);
});
});
Approach 2 (Which is cleaner):
const data = await this.storage.get('user_info');
let user_info = JSON.parse(data);
if(user_info == null)
{
this._userIsLoggedIn = false;
} else {
this._userIsLoggedIn = user_info.isLogin;
}
return this._userIsLoggedIn;
Also, note that you should modify your function with async keyword in order to use await.
global.resultArr = {};
global.failedArr = [];
global.successArr = [];
const writeFile = async function (dirResult, queryResult) {
for (i = 0; i < queryResult.recordset.length; i++) {
for (file of dirResult) {
if (
file.substring(file.length - 3) == "wav" &&
global.failedArr.indexOf(queryResult.recordset[i].ip_vch) == -1
) {
try {
const writeResult = await timeout(
fs.copy(
dir + "//" + file,
"//" +
queryResult.recordset[i].ip_vch +
"//RXWaveFiles//DynamicLibraries" +
"//" +
libid +
"//" +
file
),
5000
);
if (
writeResult &&
global.failedArr.indexOf(queryResult.recordset[i].ip_vch) == -1
) {
console.log(queryResult.recordset[i].ip_vch);
global.failedArr.push(queryResult.recordset[i].ip_vch);
await sql.query`update opower..dialers set fileMoveResult_int=0 where ip_vch =${queryResult.recordset[i].ip_vch}`;
} else if (
global.successArr.indexOf(queryResult.recordset[i].ip_vch) == -1 &&
global.failedArr.indexOf(queryResult.recordset[i].ip_vch) == -1
) {
global.successArr.push(queryResult.recordset[i].ip_vch);
await sql.query`update opower..dialers set fileMoveResult_int=1 where ip_vch =${queryResult.recordset[i].ip_vch}`;
console.log("success!" + queryResult.recordset[i].ip_vch);
}
} catch (error) {
console.error(error);
if (global.failedArr.indexOf(queryResult.recordset[i].ip_vch) == -1) {
global.failedArr.push(queryResult.recordset[i].ip_vch);
await sql.query`update opower..dialers set fileMoveResult_int=0 where ip_vch =${queryResult.recordset[i].ip_vch}`;
}
}
}
}
}
global.resultArr.success = successArr;
global.resultArr.failed = failedArr;
return global.resultArr;
};
// utility function that creates a promise that rejects after a certain time
function timeoutPromise(t, errMsg = "timeout") {
// create possible error object here to get appropriate stack trace
let e = new Error(errMsg);
e.timeout = true;
return new Promise((resolve, reject) => {
setTimeout(reject, t, e);
});
}
// wrap a promise with a timeout, pass promise, time in ms and
// optional timeout error message
function timeout(p, t, errMsg = "timeout") {
return Promise.race(p, timeoutPromise(t, errMsg));
}
I am using this await function in a for loop in which from a source directory I need to copy some files to multiple network directories, however the problem here with await is that for the directories it is failing it's taking almost a minute to resolve and then gives the control back for the next iteration, is there a way we could stop the current iteration after 5 seconds.
You can add an error timeout to any promise like this:
// utility function that creates a promise that rejects after a certain time
function timeoutPromise(t, errMsg = "timeout") {
// create possible error object here to get appropriate stack trace
let e = new Error(errMsg);
e.timeout = true;
return new Promise((resolve, reject) => {
setTimeout(reject, t, e);
});
}
// wrap a promise with a timeout, pass promise, time in ms and
// optional timeout error message
function timeout(p, t, errMsg = "timeout") {
return Promise.race([p, timeoutPromise(t, errMsg)]);
}
The, you would use this with your fs.copy() like this:
const writeResult = await timeout(fs.copy(...), 5000);
So, then if the fs.copy() is taking more than 5 seconds, the promise you are awaiting will reject and you can catch it in your catch handler and act accordingly. You will be able to see that the error object has a .timeout property.
The way this works is that the timeout() function creates a race between the promise you passed in and another promise that will reject after your timeout expires. The first one to complete controls the output of Promise.race() and thus controls what you are using await on. If the timeout wins, then the promise will be rejected.
Update !!
I fixed my initial issue with the help of Dacre Denny answer below however when writing tests for my code it turned out that the changes were not being saved before the server responded therefor the company collection in my test database was empty, I fixed this issue with the following code
Companies.find({ company_name: company.company_name }).then(found => {
if (found.length !== 0) {
return res.status(400).json({ error: "Company already exists" });
}
var userForms = company.users;
company.users = [];
const finalCompany = new Companies(company);
console.log(finalCompany);
var userPromises = [];
for (var x = 0; x < userForms.length; x++) {
var user = userForms[x].user;
user.company = finalCompany._id;
userPromises.push(userCreation(user));
}
return Promise.all(userPromises).then(responses => {
for (var x in responses) {
if (!responses[x].errors) {
finalCompany.addUser(responses[x]._id);
} else {
res.status(400).json(responses[x]);
}
}
return finalCompany;
});
})
// I moved the save in here !!!
.then((finalCompany) => {
finalCompany.save().then(()=>{
res.status(200).json({signup:"Successful"});
})
},(err) => {
res.json({error: err});
});
});
Original Issue
I am trying to create a mongoose document to represent a company, this code saves the model in my db however it does not seem to be responding with a status code or reply to postman when I make a request
I've used a debugger to step through the code but I am very rusty on my JS and I am afraid I've gone into deep water with promises thats gone over my head.
router.post('/c_signup', auth.optional, (req, res, next) => {
const { body: { company } } = req;
var error_json = cbc(company);
if( error_json.errors.length > 0 ){
return res.status(422).json(error_json);
}
Companies.find({company_name: company.company_name})
.then((found) => {
if (found.length !== 0) {
return res.status(400).json({error: "Company already exists"});
}
var userForms = company.users;
company.users = [];
const finalCompany = new Companies(company);
var userPromises = [];
for (var x =0; x < userForms.length; x ++) {
var user = userForms[x].user;
user.company = finalCompany._id;
userPromises.push(userCreation(user));
}
Promise.all(userPromises).then((responses) => {
for (var x in responses){
if (!responses[x].errors){
finalCompany.addUser(responses[x]._id);
}
else {
res.status(400).json(responses[x]);
}
}
console.log("h2");
finalCompany.save(function () {
console.log("h3");
return res.status(200);
});
})
});
return res.status(404);
});
This is the output from the debug but the execution is hanging here
h2
h3
There are a few issues here:
First, the save() function is asynchronous. You'll need to account for that by ensuring the promise that save() returns, is returned to the handler that it's is called in.
The same is true with the call to Promise.all() - you'll need to add that promise to the respective promise chain by returning that promise to the enclosing handler (see notes below).
Also, make sure the request handler returns a response either via res.json(), res.send(), etc, or by simply calling res.end(). That signals that the request has completed and should address the "hanging behaviour".
Although your code includes res.json(), there are many cases where it's not guaranteed to be called. In such cases, the hanging behaviour would result. One way to address this would be to add res.end() to the end of your promise chain as shown below:
Companies.find({ company_name: company.company_name }).then(found => {
if (found.length !== 0) {
return res.status(400).json({ error: "Company already exists" });
}
var userForms = company.users;
company.users = [];
const finalCompany = new Companies(company);
var userPromises = [];
for (var x = 0; x < userForms.length; x++) {
var user = userForms[x].user;
user.company = finalCompany._id;
userPromises.push(userCreation(user));
}
/* Add return, ensure that the enclosing then() only resolves
after "all promises" here have completed */
return Promise.all(userPromises).then(responses => {
for (var x in responses) {
if (!responses[x].errors) {
finalCompany.addUser(responses[x]._id);
} else {
res.status(400).json(responses[x]);
}
}
console.log("h2");
/* Add return, ensure that the enclosing then() only resolves
after the asnyc "save" has completed */
return finalCompany.save(function() {
console.log("h3");
return res.status(200);
});
});
})
.then(() => {
res.end();
},(err) => {
console.error("Error:",err);
res.end();
});
var verifyEmail = function (thisEmail){
return new Promise(
function (resolve, reject) {
quickemailverification.verify(thisEmail, function (err, response) {
// Print response object
console.log(response.body);
if (response.body["success"] == "true"){
var validity = response.body["result"];
if (validity == "valid"){
console.log("Email Valid!");
resolve(validity);
} else {
console.log("Email Invalid!")
resolve(validity);
}
} else {
var reason = new Error("API unsuccessful");
reject(reason);
}
});
}
);
};
var saveValidity = function (validity){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
var state = true;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
}
}
);
};
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
var snap = snapshot.val();
keys = Object.keys(snap);
for (var i = 0; i < 100; i++){
var emails = snap[keys[i]]["emails"];
if (emails){
for (var x = 0; x<emails.length; x++){
var thisEmail = emails[x]["email"];
var emailVerified = emails[x]["verified"];
if (emailVerified != true || emailVerified != false){
verifyEmail
.then(saveValidity)
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error){
console.log(error.message);
});
}
}
}
}
});
Above is the code I put together. I'm not all too convinced that it will work. I'm new to promises, so I'm trying to understand how to do this right.
The verifyEmail function should take in the email address from the firebase query in the third chunk of the code. The saveValidity function should take on the validity response from verifyEmail.
But, what I'm also worried about the nested for loop I have in the firebase query block. I'm looping through each user to validate their emails, but each user sometimes also has multiple emails. I'm worried that it will loop on to the next user before finishing checking all the emails of the previous user.
I'm also not sure if I can pass data into the promise functions the way I did.
Could definitely use some help here. Really trying hard to understand how this works.
First, you need to fix saveValidity() to always resolve or reject the promise and to pass in the other variables key and thisEmail that it references:
const saveValidity = function (validity, key, thisEmail){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
let state = true;
admin.database().ref("/users_unverified/"+key+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error("Email ("+thisEmail+") verfication could not be saved" + error));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error(msg));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
}
}
);
};
Then, several changes are made to your main loop:
I assume we can run all the verifyEmail() calls in parallel since they don't appear to have anything to do with one another.
Change verifyEmail.then(...) to verifyEmail(thisEmail).then(...)` to actually call the function
Collect all the verifyEmail() promises in an array
Call Promise.all() on the array of promises to monitor when they are all done
Return value from .then() so we get the returned values in Promise.all()
rethrow in .catch() so promise stays rejected and will filter back to Promise.all(). You could eat errors here if you want to ignore them and continue with others.
Switch from var to let
Change from != to !== since it looks like your explicitly looking for a true or false value and don't want type casting.
Pass in the variables that saveValidity() needs.
Change logic when comparing emailVerified because what you had before was always true and thus probably not the right logic. I think what you want is to know when emailVerified is not yet set to true or to false which means you have to use &&, not ||.
Compare outer for loop with keys.length, not hard-coded value of 100.
And, here's the resulting code for the main nested for loop:
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
let snap = snapshot.val();
let keys = Object.keys(snap);
let promises = [];
for (let i = 0; i < keys.length; i++){
let key = keys[i];
let emails = snap[key]["emails"];
if (emails){
for (let x = 0; x < emails.length; x++) {
let currentKey = key;
let thisEmail = emails[x]["email"];
let emailVerified = emails[x]["verified"];
if (emailVerified !== true && emailVerified !== false){
promises.push(verifyEmail(thisEmail).then(validity => {
return saveValidity(validity, currentKey, thisEmail);
}).then(function (fulfilled) {
console.log(fulfilled);
return fulfilled; // after logging return value so it stays the resolved value
}).catch(function (error) {
console.log(error.message);
throw error; // rethrow so promise stays rejected
}));
}
}
}
}
return Promise.all(promises);
}).then(results => {
// all results done here
}).catch(err => {
// error here
});
If ES2017 is available in your case, you can just use the keywords await and async to do that directly. Following is an example:
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
And you can read more about async/await here.
If you want to do that without async/await to achieve better browser compatibility, you can use Babel to do the pre-compile.
If you really want a lightwight implementation, you can use a function named chainPromiseThunks, or chain for short. This chain function accepts an Array of Thunks of Promises, And returns a new Thunk of Promise, Following is an one-line-implementation of chain:
const chain = thunks => thunks.reduce((r, a) => () => r().then(a));
And here is a usage demo:
const echo = x =>
new Promise(function(resolve) {
return setTimeout((function() {
console.log(x);
return resolve(x);
}), 1000);
})
;
const pThunks = [1,2,3,4,5].map(i => () => echo(i));
chain(pThunks)();
I'm working on a app and hit a corner stone with spaghetti Promise code which baffles me. Must mention it's my first try at Promise (in a attempt to ditch callback hell).
I'm using node.js and need a module to get data from Goodreads API to other part of the application so I can use it. I made a object that I export in the module which has a function return a Promise chain.
The issue lies when I call this module in the main part of the app. If the Goodreads module API created by me ends in the .reject() from the Promise, both .then() and .catch() are called. More inline comments in the code.
This is my code:
goodreads_module.js:
let goodreads = {
getItem: function(path) {
if (typeof path == 'object') {
return path._;
} else {
return path;
}
},
// Get the book id needed for further search of book language on different api end-point
getBookId: function(word) {
let self = this;
logger.log(logger.info, 'Getting book process started!');
// Query for GoodRead search
const query_book = {
q: word,
page: '',
type: 'title'
};
logger.log(logger.info, 'Seaching by word: ' + query_book.q);
return new Promise((resolve, reject) => {
// Searching for book using
// #param: query_book
if (typeof query_book !== 'undefined') {
// directly resolve with the promise
return resolve(grclient.searchBooks(query_book));
} else {
let reason = 'No word to search for';
return reject(reason);
}
})
.then((result) => {
let search = result.search,
rating,
book_id,
random_nr,
ratings_count,
count = 0;
if(parseInt(search['total-results']) == 0){
return false;
}
if (parseInt(search['total-results']) !== 0 && !Array.isArray(search.results.work)) {
logger.log(logger.info, 'Book data is:\n' + JSON.stringify(search.results.work));
rating = self.getItem(search.results.work.average_rating)
ratings_count = self.getItem(search.results.work.ratings_count);
book_id = search.results.work.best_book.id._;
}
if (parseInt(search['total-results']) > 1 && Array.isArray(search.results.work)) {
random_nr = Math.floor(Math.random() * search.results.work.length);
logger.log(logger.info, 'Book data is:\n' + JSON.stringify(search.results.work[random_nr]));
rating = self.getItem(search.results.work[random_nr].average_rating)
ratings_count = self.getItem(search.results.work[random_nr].ratings_count);
// If the book grabed has the rating lower than 3.8 grab another book
do {
random_nr = Math.floor(Math.random() * search.results.work.length);
rating = self.getItem(search.results.work[random_nr].average_rating)
ratings_count = self.getItem(search.results.work[random_nr].ratings_count);
logger.log(logger.warn, 'New book data is:\n' + JSON.stringify(search.results.work[random_nr]));
count++;
if (count == 10) {
break;
}
} while (rating <= 3.8 && ratings_count <= 5 || rating <= 3.8 && ratings_count >= 5 || rating >= 3.8 && ratings_count <= 5)
if (count == 10) {
return false;
}
console.log(count);
logger.log(logger.info, 'Book rating / rating rount ' + JSON.stringify(rating) + "/" +JSON.stringify(ratings_count));
// Grab the book id when all rules are met
book_id = search.results.work[random_nr].best_book.id._;
}
if (rating <= 3.8 && ratings_count <= 5 || rating <= 3.8 && ratings_count >= 5 || rating >= 3.8 && ratings_count <= 5) {
return false;
}
return parseInt(book_id);
})
},
// Finally get the book using the book id and return book data in a object containing author, title and url.
getBook: function(word) {
let self = this;
return new Promise((resolve, reject) => {
self.getBookId(word).then((result) => {
if (typeof result == 'boolean') {
let reason = 'Book id invalid';
return reject(reason);
}
return resolve(result);
})
})
.then((result) => {
logger.log(logger.info, 'Check language for book id: ' + result);
return new Promise((resolve, reject) => {
grclient.showBook(result).then((response) => {
let data = response.book;
let lang = data.language_code;
logger.log(logger.info, 'Book language is ' + JSON.stringify(lang));
if (lang == 'eng' || lang == 'en-US' || lang == 'en-GB' || lang == 'English' || lang == 'english' || lang == 'en-US') {
let book = {
title: data.title,
author: data.authors.author.name ? data.authors.author.name : data.authors.author[0].name,
url: data.url
}
return resolve(book);
} else {
let reason = 'Language is not english';
return reject(reason);
}
})
})
})
},}
main.js:
goodreads.getBook(goodreads.word()).then((data)=>{
logger.log(logger.info, "Got the book! Continue with tweeting process");
// use data here
})
What am I doing wrong as the .catch() method should be the only one called in main.js without short call on .then()? How should I chain this promises?
There can be a problem regarding .then and .catch. According to mdn, the promise.then takes two callbacks, one for success and other for rejection. If you'll specify a catch after then then it might well be the case that your code is breaking in then's first callback. e.g.
someAPICall()
.then(() => {//if code breaks here it'll go in catch too})
.catch(() => {//can come here})
But if you want to write code for promise rejection. You must provide then a second callback as :
someAPICall()
.then(() => {
//success
}, () => {
//rejection
});
I'm sorry I didn't read your whole code and it might as well be the case that this isn't the primary reason. If the answer doesn't help please comment. Hope that helps.
You're kind of misusing promises, unless you're doing something asynchronous, you don't need to create a new promise every time.
Just return a value if you want to a new fulfilled promise or throw if you want a new rejected promise.
new Promise((res, reject) => {
res(true);
}).then(val => {
return false;
}).then(val => console.log(val)); // false
new Promise((res, reject) => {
res(true);
}).then(val => {
throw "foo";
}).catch(err => console.log(err)); // "foo"
Another important point about promises is that if you return a promise, the new promise generated will be resolved / rejected accordingly to the returned promise, so the beginning of your code could be simplify to
return new Promise((resolve, reject) => {
if (typeof query_book !=='undefined') {
// directly resolve with the promise
return resolve(grclient.searchBooks(query_book));
} else {
let reason = 'No word to search for';
return reject(reason);
}
});
Ended up re-writing the code as above and the problem was gone.
Spaghetti monster code is a bad practice and should be avoided.