Retrieve PromiseResolve data in JavaScript - javascript

I am retrieving tab data from Google Chrome, stored in a Promise object that will be fulfilled with an array. The proper data is currently stored in a Promise object, but I want to only access the array itself.
I have tried wrapping the result in a new Promise and trying to resolve it again, but I keep getting the data in a Promise object.
export async function getTabData(){
let tabs = [];
await window.chrome.tabs.query({currentWindow: true}, function(data){
for(let i=0; i<data.length; i++){
tabs.push(data[i]);
};
});
return tabs;
}
export async function getPromise() {
const promise1 = await Promise.resolve(getTabData())
.then(i => i)
new Promise(function(res, rej){
res(promise1);
})
.then(function(result){
return Promise.resolve(promise1);
})
.then(function(result){
console.log(result);
});
}
I expect to get an array of Tabs, but I get a Promise object where that array is stored within the PromiseValue

You can't await the tabs query since it isn't returning a promise. So wrap it in a new promise and resolve it in the callback.
Currently the array is being returned empty and populated after you log it
export function getTabData() {
return new Promise((resolve, reject) => {
window.chrome.tabs.query({currentWindow: true}, resolve);
});
}
export async function doTabsStuff() {
const tabs = await getTabData();
// do something with tabs array
}

Related

Retrieving value from Mongoose Promise as a callback

Having trouble pulling the data from a mongoose promise. I want to find a set of users from mongoose using an appropriate ID and passing the result along a promise chain.
The query is pulling the right data from the database. When I nest the query inside of a promise chain and then attempt to pass along the result, my console.log shows [Function (anonymous)]. Is there an appropriate structure for pulling the data from MongoDB using mongoose and then passing that data along a promise chain?
Promise chain:
addItem(guildId, items, timeCreated) {
return new Promise((resolve, reject) => {
// other functionality omitted for brevity
resolve(guildId, items, timeCreated);
}).then((guild, item, timeCreate) => {
findGroupUsers(guild).then((response) => {
return new Promise((resolve, reject) => {
console.log(response.userId);
resolve(response);
});
});
Mongoose query:
const findGroupUsers = async (guildId) => {
const members = await EngagementTracking.find({ guildId: `${guildId}` }).exec();
console.log(members);
return members;
};
The verified answer successfully pulls the data from the query inside of addItem. To continue and pass the result of the query along chain, I did the following:
addItem(guildId, items, timeCreated) {
return new Promise((resolve, reject) => {
//other functionality
resolve(guildId, items, timeCreated);
}).then((guild, items, timeCreate) => {
return findGroupUsers(guild).then((response) => {
return new Promise((resolve) => { resolve(response); });
});
}).then((data) =>{// other functionality //});
You function add item seem a bit strange you should do this instead:
addItem(guildId, items, timeCreated) {
return new Promise((resolve, reject) => {
// other functionality omitted for brevity
resolve(guildId, items, timeCreated);
}).then((guild, predict, timeCreate) => {
return findGroupUsers(guild).then((response) => {
console.log(response.userId);
return response;
});
you don't need to return a new promise inside a .then
also be warn that you should return your promise (you didn't return findGroupUser)
at the end you should have a addItem.then(/do something/) or an await

Pending promises even though awaiting Promise.all()

I'm having difficulty understanding why I still have pending promises after awaiting Promise.all().
In the example below, I'm creating an array of promises by calling an async function on each element of an array, using .map().
Now, why is the promise still showing as pending? The way I (mis)understand it right now:
then() fires once the promise from storeData() resolves
storeData()resolves once newDataArray is returned
newDataArray is returned once all promises inside the promisesArray are resolved or once the first one rejects.
storeData(OldDataArray).then(values => console.log(values))
// console shows:
// { id: 1, data: Promise { <pending> } },
// { id: 2, data: Promise { <pending> } }
const storeData = async (OldDataArray) => {
try {
const promisesArray = OldDataArray.map((item) => {
let newData = downloadMoreDetails(item.id, item.group); //async function, see below
return {
id: item.id,
data: newData,
};
});
const newDataArray = await Promise.all(promisesArray); // <-- I'm awaiting all promises to complete before assigning to newDataArray
return newDataArray;
} catch (error) {
console.log(error)
}
};
const downloadMoreDetails = async (id, group) => {
const response = await fetch(
`example.com/id/group.xml`
);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
const str = await response.text();
const json = convert.xml2json(str, {
compact: true,
spaces: 2,
});
return json;
};
newData is a promise, but you're not awaiting the promise. Instead, you're awaiting an array of objects {id: item.id, data: newData } that has the promise inside it. Promise.all() doesn't look inside those objects to find the promise and wait for that promise. It just sees an array of plain objects which means it has nothing to do. You can fix that by doing this:
const storeData = async (OldDataArray) => {
try {
const promisesArray = OldDataArray.map(async (item) => {
let newData = await downloadMoreDetails(item.id, item.group); //async function, see below
return {
id: item.id,
data: newData,
};
});
return Promise.all(promisesArray);
} catch (error) {
// log and rethrow error so the caller gets the rejection
console.log(error);
throw error;
}
};
This changes the .map() callback to be async. That does two beneficial things. First, it means the resulting array from .map() will be an array of promises since the async callback always returns a promise. And, second, it allows you to use await inside the callback so you can populate your returned object with the actual data, not with a promise.
Then, the return from inside the async callback will cause that value to become the resolved value of the promise that the async function is returning.
Note, you could have also done it without adding the async/await like this:
const storeData = (OldDataArray) => {
const promisesArray = OldDataArray.map((item) => {
return downloadMoreDetails(item.id, item.group).then(newData => {
return {
id: item.id,
data: newData,
};
});
});
return Promise.all(promisesArray).catch(error => {
// log and rethrow error so the caller gets the rejection
console.log(error);
throw error;
});
};
In this version, you directly return a promise from the .map() callback and you make sure that promise resolves to your data object.

Values coming back as 'undefined' in for loop inside ES6 promise

So I'm currently writing an inventory application, and I'm running into some issues inserting values into my database. What happens is that I get a list of item objects that contain information about that specific item. I then push those items into a list and call a socket which runs a for loop to insert those items into my database, but when I look at the table where these items should be, all of their values are set as undefined.
My item object looks something like this:
let ItemObject = {
ID: id,
Name: name,
Qty: qty
}
The promise that calls the DB sequence:
export const insertItem = async (list) => {
return new Promise(async (resolve, reject) => {
try {
console.log(list);
for(let i = 0; i < list.length; i++){
const writeSolutionData = `INSERT INTO database (ID, Name, Qty) VALUES ("${list.ID}", "${list.Name}", "${list.Qty});`;
const response = await db(writeSolutionData, "Inserting Item");
resolve(response);
}
} catch (e) {
console.log("ERROR database.insertItem: " + e);
reject(e);
}
});
};
This is the socket that gets called:
socket.on('insertItem', async (data, callback) => {
try {
const results = await insertItem(data);
callback(true);
}
catch (error) {
callback(false);
}
});
When I console log the list before the for loop, I get what I would expect the output to be, but after this promise finishes the data that's returned in my DB is undefined. I also receive no errors from the callback either. The idea of ES6 promises are still new to me, so forgive me if what I have here is not correct. This is the first time I've tried implementing a for loop within an asynchronous promise like this, so it was no surprise to me that this didn't work the first time around.
If anyone has any suggestions as to what I'm doing wrong, I would greatly appreciate it. Thanks!
The insertItem does not have to be an async function. Just return the Promise and use async it the promise callback, only in that scope the await keyword is needed.
An async function automatically returns a Promise, but you need a Promise constructor to use the resolve and reject methods. It is basically the same thing but with different syntax and options.
Also I've noticed that your loop has an error. You try to select list.ID, list.Name and list.Qty from the array, but instead should get the values from the items in the array.
I also found a missing " at the end of your query string.
export const insertItem = (list) => new Promise(async (resolve, reject) => {
try {
list.forEach(item => {
const writeSolutionData = `INSERT INTO database (ID, Name, Qty) VALUES ("${item.ID}", "${item.Name}", "${item.Qty}");`;
const response = await db(writeSolutionData, "Inserting Item");
resolve(response);
});
} catch (e) {
console.log("ERROR database.insertItem: " + e);
reject(e);
}
});
Addendum
Updated the answer with usage of Promise.all. Suggested by #wizloc.
This will loop all of your items and return promises from the db() function and stores them into a new array. Promise.all will then resolve whenever all of the promises in the array have fullfilled and return the values of each promise in a single array.
export const insertItem = (list) => new Promise(async (resolve, reject) => {
try {
const responses = list.map(item => {
const writeSolutionData = `INSERT INTO database (ID, Name, Qty) VALUES ("${item.ID}", "${item.Name}", "${item.Qty}");`;
return db(writeSolutionData, "Inserting Item");
});
const results = await Promise.all(responses);
resolve(results);
} catch (e) {
console.log("ERROR database.insertItem: " + e);
reject(e);
}
});
I'm unfamiliar with what
const writeSolutionData = `INSERT INTO database (ID, Name, Qty) VALUES ("${list.ID}", "${list.Name}", "${list.Qty}");`;
const response = await db(writeSolutionData, "Inserting Item");
actually does (aside from the obvious), but since db(writeSolutionData, "Inserting Item") is awaitable, it returns a promise. As I mentioned in comments, your original code is resolving in the first iteration of the loop, therefore if you need to access the values returned from your promises down the road, you will find you only have access to the first value. You asked why you would need the values since you can just query for them afterwards, I can't answer this since I don't know anything about your project, what you plan to do with the data after it is inserted, etc. But another benefit of promises is error handling by chaining .then() and .catch().
You could simplify your entire insertData method to the following
socket.on('insertItem', async (data, callback) => {
const promises = data.map(x =>
db(`INSERT INTO database (ID, Name, Qty) VALUES ("${x.ID}", "${x.Name}", "${x.Qty});`)
)
Promise.all(promises)
.then(values => {
// Do something with values array
callback(true)
})
.catch(error => {
// Error handling
callback(false)
})
});
Doing it this way will ensure callback(true) is only called if all items were inserted successfully, and will error (callback(false)) if any of the items failed.
I think insertItem is not an async function because it doesn't contain await...
export const insertItem = /*async <- remove it */ (list) => {
return new Promise(async (resolve, reject) => {
try {
console.log(list);
for(let i = 0; i < list.length; i++){
const writeSolutionData = `INSERT INTO database (ID, Name, Qty) VALUES ("${list.ID}", "${list.Name}", "${list.Qty});`;
const response = await db(writeSolutionData, "Inserting Item");
resolve(response);
}
} catch (e) {
console.log("ERROR database.insertItem: " + e);
reject(e);
}
});
};

Generate an array of promises to run sequentially

I'm trying to generate an array of Promises to run sequentially. I've seen lots of tips on this but can't get it to run in my use case.
export default function generateIcons(){
return new Promise((resolve, reject) => {
const containers = document.querySelectorAll('.html2CanvasTarget')
const promises = containers.map(child => processIcon(child))
promises.reduce((p, fn) => p.then(fn), Promise.resolve())
resolve()
})
}
function processIcon(child){
return new Promise((resolve, reject) => html2canvas(child).
then(canvas => uploadFromCanvas(canvas,
child.childNodes[0].className.split(' ')[1] + '.png'))
.then(resolve).catch(reject))
}
Any tips? This just rejects and I can't see why
Looks like you want to resolve the main promise when the canvases are available for all the child elements. You can use Promise.All() for this.
It should also be noted that the querySelectorAll doesn't return anything you can call the .map on. You will have to create an array from what the querySelectorAll returns - which is a NodeList.
export default function generateIcons(){
return new Promise((resolve, reject) => {
const containers = document.querySelectorAll('.html2CanvasTarget');
const promises = Array.from(containers).map(child => processIcon(child))
Promises.All(promises).then(() => resolve());
})
}
containers is a NodeList, and NodeLists don't have a .map method, which is why your code is throwing an error.
Because processIcon already returns a Promise, there's no need to use the Promise constructor again. html2canvas already returns a Promise too, so there's no need for any Promise constructor anywhere (see What is the explicit promise construction antipattern and how do I avoid it?)
If possible, just await each call of it in a for loop. Because uploadFromCanvas returns a Promise too, and you want to wait for it, return it (or await it) as well:
export default async function generateIcons() {
const containers = document.querySelectorAll('.html2CanvasTarget');
for (const container of containers) {
await processIcon(container);
}
}
function processIcon(child) {
return html2canvas(child, {backgroundColor:null})
.then(canvas => uploadFromCanvas(canvas, child.className.split(' ')[1] + '.png'))
.catch(console.log);
}
As per your question, you can use Q module module for that
You need to take an empty array and push promises into it, and just pass this array in Q method (Q.allSettled), Take a look with an example
const Q = require('q');
const promiseHolder = [];
for (let i = 0; i < 10; i += 1) {
promiseHolder.push('Your Promises');
}
Q.allSettled(promises)
.then((results) => {
results.forEach((result) => {
if (result.state === 'fulfilled') {
const value = result.value;
return value;
}
const reason = result.reason;
throw reason;
});
});
In Q.allSettled() The method you always get the result in .then(). There are 2 states. One for success and one for failure.
Success => state === 'fulfilled', value: 'Whatever your promise return'
Failure => state === 'rejected', reason: 'Whatever your promise thrown'
In this case, you have a number of successful and unsuccessful promises.
There is the second approach which is Promise.all() do the same but the issue is whenever any of promise rejected further promise never called.
const promiseHolder = [];
for (let i = 0; i < 10; i += 1) {
promiseHolder.push('Your Promises');
}
Promise.all(promiseHolder)
.then((results) => {
return results;
})
.catch((err) => {
throw err;
});
In the second approach ( Promise.all()), It consists of all your promises pushed from for loop. If any of promise rejected no more promise called and suddenly you got the state of promise rejection in Promise.all().
Promise.all(promiseHolder)
.then((results) => {
return results;
})
.catch((err) => {
console.log('Promise will reject here', err);
throw err;
});
I hope it helps, Happy Coding :)

Array undefined after resolving it after for loop

I have the problem of having an undefined array which gets resolved after a for loop where it gets filled. It looks like the following:
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
var mailArray = [];
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
for(var i = 0; i < messageIDs.length; i++) {
getMessage(oauth2Client, 'me', messageIDs[i]).then(function(r) {
// Array gets filled
mailArray.push(r);
}, function(error) {
reject(error);
})
}
// Array gets resolved
resolve(mailArray);
},
(error) => {
reject(error);
}
)
});
}
Both listMessages() and getMessage() returns a promise, so it is chained here. Any ideas why I am getting an undefined mailArray? My guess is that it is not filled yet when it gets resolved. Secondly I think this flow is not a good practice.
The Array is probably undefined because it is never defined; at least nowhere in your code. And your promise resolves before any iteration in your loop can resolve or better said, throw (trying to push to undefined).
Besides that. you can highly simplyfy your code by using Array#map and Promise.all.
And there's no point in catching an Error just to rethrow the very same error without doing anything else/with that error.
function getUnreadMails() {
//put this on a seperate line for readability
//converts a single messageId into a Promise of the result
const id2message = id => getMessage(oauth2Client, 'me', id);
return listMessages(oauth2Client)
//converts the resolved messageId into an Array of Promises
.then(messageIDs => messageIDs.map( id2message ))
//converts the Array of Promises into an Promise of an Array
//.then(Promise.all.bind(Promise));
.then(promises => Promise.all(promises));
//returns a Promise that resolves to that Array of values
}
or short:
function getUnreadMails() {
return listMessages(oauth2Client)
.then(messageIDs => Promise.all(messageIDs.map( id => getMessage(oauth2Client, 'me', id) )))
}
.then(Promise.all) won't work
I wanted to make the intermediate results more clear by seperating them into distinct steps/functions. But I typed too fast and didn't check it. Fixed the code.
In the short version, where does the mailArray then actually get filled/resolved
Promise.all() takes an an Array of promises and returns a single promise of the resolved values (or of the first rejection).
messageIDs.map(...) returns an Array and the surrounding Promise.all() "converts" that into a single Promise of the resolved values.
And since we return this Promise inside the Promise chain, the returned promise (listMessages(oauth2Client).then(...)) also resolves to this Array of values.
Just picking up on marvel308's answer, I think you need to create a new Promise that resolves when your other ones do. I haven't had a chance to test this, but I think this should work
function getUnreadMails() {
var mailArray = [];
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
var messages = [];
for(var i = 0; i < messageIDs.length; i++) {
messages.push(
getMessage(oauth2Client, 'me', messageIDs[i]).catch(reject)
);
}
Promise.all(messages).then(resolve);
},
(error) => {
reject(error);
}
)
});
}
This way, the resolve of your first promise gets called when all the messages have resolved
getMessage(oauth2Client, 'me', messageIDs[i]).then(function(r) {
// Array gets filled
mailArray.push(r);
}, function(error) {
reject(error);
})
is an asynchronous call
resolve(mailArray);
won't wait for it to push data and would resolve the array before hand
to resole this you should use Promise.all()
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
var mailArray = [];
return listMessages(oauth2Client).then(
(messageIDs) => {
for(var i = 0; i < messageIDs.length; i++) {
mailArray.push(getMessage(oauth2Client, 'me', messageIDs[i]));
}
// Array gets resolved
return Promise.all(mailArray);
},
(error) => {
reject(error);
}
)
}
Since your getMessage function is async as well you need to wait until all your calls finish.
I would suggest using Promise.all
Here you can find more info: MDN Promise.all()
The code would look something like this:
messageIDs.map(...) returns an array of Promises
use Promise.all() to get an array with all the promises responses
resolve if values are correct reject otherwise
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
return Promise.all(messageIDs.map(id => getMessage(oauth2Client, 'me', id)))
})
.then((messages) => resolve(messages))
.catch(error => reject(error))
});
}
One thing to keep in mind is that Promise.all() rejects if any of your promises failed
Hope this helps!
Explicit construction is an anti-pattern
I believe you can write that piece of code much shorter and, IMHO, cleaner
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
return listMessages(oauth2Client)
.then((messageIDs) => Promise.all(messageIDs.map(id => getMessage(oauth2Client, 'me', id)))
}

Categories