Home > Software design >  Cannot forEach over array in Javascript with promises and console shows weird array result
Cannot forEach over array in Javascript with promises and console shows weird array result

Time:10-01

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:

Console

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:

  1. Add db.allP() as a promisified version of db.all().
  2. Use only promises for asynchronous control flow.
  3. Get rid of any uses of .forEach() with asynchronous operations in the loop. Much easier to control things with promises only and a regular for loop with await.
  4. In refreshConversations(), you call convoidlist(), but never use its results.
  5. 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.
  • Related