I am trying to do 2 things:
- Get a list of unique conversationId from the database
- Get the latest message for each of these conversations
However, I cannot iterate over the array with messages that is returned. I already tried taking the 2 parts of the code apart, in seperate functions. Calling one from the other, now calling them both in sequence with .then statements and nothing works. I would really appreciate some help...
This is NodeJS, with SQLite3
This is what the console shows as is being returned:
This is the code to get the messages:
function getConversationsList(idList)
{
var c = [];
return new Promise((resolve) => {
idList.forEach((id => {
db.all("SELECT * FROM messages WHERE (conversationId = ? AND isBroadcast = 0 AND isWarmup = 0) ORDER BY messageTime DESC LIMIT 1",{1:id},function(err,row) {
if(err)
{
console.log(err.message);
return resolve(err);
}
row.forEach((d => {
console.log(d);
c.push(d);
}))
});
}))
resolve(c);
});
}
function convoidlist()
{
idlist = [];
return new Promise((resolve) => {
db.all("SELECT DISTINCT conversationId FROM messages",{},function(err,rows){
if(err)
{
console.log(err)
}
rows.forEach((row) => {
idlist.push(row.conversationId);
});
resolve(idlist);
})
});
}
This is the code that tries to loop through them, no errors, but the loop never gets inside...
function refreshConversations()
{
convoidlist().then((idlist) => {
getConversationsList(idlist).then((f) => {
console.info(f);
var chtml = "";
f.forEach((e,index,array) => {
console.info(e[index]);
var newc = `
<div data-id="${e.conversationId}" style="width:100%;padding:10px;margin-bottom:20px;" >
<div width="100%" style="font-size:0.8em;"><span style="width:50%;text-align:left">${e.messageFrom}</span><span style="width:40%;">${e.messageTime}</span></div>
<div width="100%" style="height:100px;overflow:hidden;">${e.messageBody}</div>
</div>
`;
chtml = chtml newc;
console.log(newc);
});
console.log(chtml);
document.getElementById('conversationlist').innerHTML = chtml;
});
});
}
I know its a bit of a mess, it was much clearer, but I tried so much already. Any help is appreciated.
CodePudding user response:
You are mixing promises, regular asynchronous callbacks and .forEach()
which just really makes it difficult to write appropriate asynchronous code. It is best to first promisify any asynchronous operations.
If you don't have access to a version of sqlite3 that supports promises, then you can promisify specific operations you need to use (as I do here). This would be better to do centrally in one place, but since you don't show that level of code context, I just do it locally in this one particular problem.
Then, avoid ever using .forEach()
with asynchronous operations in the loop because .forEach()
is not promise-aware or asynchronous-aware in any way. It just merrily runs its loop without waiting for any asynchronous operations to finish which is very hard to manage.
Here's a promisified and restructured way to do things:
const { promisify } = require("util");
async function getConversationsList(idList) {
// promisify db.all
db.allP = promisify(db.all);
const data = [];
for (let id of idList) {
const rows = await db.allP("SELECT * FROM messages WHERE (conversationId = ? AND isBroadcast = 0 AND isWarmup = 0) ORDER BY messageTime DESC LIMIT 1", { 1: id });
data.push(...rows);
}
return data;
}
async function convoidlist() {
db.allP = promisify(db.all);
const rows = await db.allP("SELECT DISTINCT conversationId FROM messages", {});
return rows.map(row => row.conversationId);
}
async function refreshConversations() {
try {
const idlist = await convoidlist();
const conversations = await getConversationsList(idlist);
console.info(conversations);
let html = conversations.map(e => {
return `
<div data-id="${e.conversationId}" style="width:100%;padding:10px;margin-bottom:20px;" >
<div width="100%" style="font-size:0.8em;"><span style="width:50%;text-align:left">${e.messageFrom}</span><span style="width:40%;">${e.messageTime}</span></div>
<div width="100%" style="height:100px;overflow:hidden;">${e.messageBody}</div>
</div>
`
});
document.getElementById('conversationlist').innerHTML = html.join("/n");
} catch (e) {
console.log(e);
// decide what to show the user here if there was an error
}
}
Notes:
- Add
db.allP()
as a promisified version ofdb.all()
. - Use only promises for asynchronous control flow.
- Get rid of any uses of
.forEach()
with asynchronous operations in the loop. Much easier to control things with promises only and a regularfor
loop withawait
. - In
refreshConversations()
, you callconvoidlist()
, but never use its results. - Centralize error reporting in
refreshConversations()
. Low level functions should return data or error. Higher level functions decide what to do with errors and when to log them.