tmijs / tmi.js

💬 Javascript library for the Twitch Messaging Interface. (Twitch.tv)
https://tmijs.com
MIT License
1.54k stars 216 forks source link

Multiple client.say() waits for each other and sends at the same time #489

Closed crivasr closed 2 years ago

crivasr commented 2 years ago

I'm trying to do a pyramid command, this is my code

function sendPyramid(client, channel, word = ":tf:", size = 3) {
    const colors = [
        "Blue",
        "BlueViolet",
        ...
    ];

    for (let i = 0; i < size; i++) {
        sleep(1000);
        const msg = wordTimes(word, i + 1);
        client.say(channel, `/color ${colors[i]}`);
        client.say(channel, msg);
    }
    for (let i = 1; i < size; i++) {
        sleep(1000);
        const msg = wordTimes(word, size - i);
        client.say(channel, `/color ${colors[size - i - 1]}`);
        client.say(channel, msg);
    }
}

function wordTimes(word, times) {
    let msg = "";
    for (let i = 0; i < times; i++) {
        msg += `${word} `;
    }
    return msg;
}

https://user-images.githubusercontent.com/20014462/137381507-a2d9b33e-70dc-466b-a7c5-4ec37ae4db4e.mp4

I added a sleep of 1 second betweeen each message so it doesn't spam them all at once. For some reason tmi doesn't send those messages, it caches them, only when all of them are finished they are sent without an interval between them. That sleep function is working, you can see it takes some time on the conosole and if you remove them it does it instantly

AlcaDesign commented 2 years ago

Please don't use the issues for questions.

Your sleep function probably isn't doing anything at all unless it's written to block the event loop which is a bad idea for JavaScript. tmi.js doesn't cache anything so I guess that it's whatever evil thing your sleep function is doing. You should use async/await with a promisified setTimeout, which you can get from Node's timers module. tmi.js has a color method but you need to wait for it too.

const { setTimeout: sleep } = require('timers/promises');

//...

client.on('message', async (channel, tags, message, self) => {
    if(self || !message.startsWith('!')) return;
    const args = message.slice(1).split(' ');
    const command = args.shift().toLowercase();
    if(command === 'pyramid') {
        const colors = [ '#ff9900', '#00ff99', '#9900ff' ];
        const emote = 'Kappa ';
        const height = 4;
        for(let i = 0; i < height * 2 - 1; i++) {
            const h = i >= height ? height - (i - height) - 1 : i + 1;
            const p = emote.repeat(h);
            const c = colors[i % colors.length];
            await sleep(i ? 350 : 0);
            await client.color(channel, c);
            await sleep(350);
            await client.say(channel, p);
        }
    }
});

(My code is probably bad because I'm writing from a phone but it should give you an idea of how to use async/await with a proper sleep function.)

crivasr commented 2 years ago

Thanks that worked. Sorry for using the issues for this, didn't know it was an expected behaviour. So this was my sleep function:

function sleep(delay) {
    var start = new Date().getTime();
    while (new Date().getTime() < start + delay);
}

But I still dont get why the messages appeared in the console and it waited for all of them to be sent to send them all

AlcaDesign commented 2 years ago

The call to console.log is faster than what the websocket connection can do while you're stalling the process. Never use synchronous code like a while loop to intentionally sleep the process. The runtime (v8) can't do anything else while this is happening because of the concurrency model being an event loop. Check out Node's guide on not blocking the event loop. Async/await (Promises) allow for writing synchronous code in appearance but without actually blocking the CPU, such as my example. Other tasks can still happen in the background (like handling another chat message).