How to detect if all network requests are settled from a chrome extension?

I have been trying to scrap some data from DOM using my chrome extension.
But the data that should be rendered on the DOM is fetched asynchronously, so I need to wait for all network requests to settled.

 chrome.tabs.create({ url: request.url }, (tab) => {
      chrome.tabs.onUpdated.addListener(function listener(tabId, info) {
        if (tabId === tab.id && info.status === "complete") {
          chrome.tabs.onUpdated.removeListener(listener);

          chrome.scripting.executeScript(
            {
              target: { tabId: tab.id },
              func: () =>
                document.querySelector("h1")?.textContent || "No h1 tag",
            },
            (results) => {
              const auditedData = results[0];

              if (!tab.id) return;
              chrome.tabs.remove(tab.id, () => {
                if (sourceTabId) {
                  chrome.tabs.update(sourceTabId, { active: true }, () => {
                    chrome.tabs.sendMessage(sourceTabId, {
                      action: "auditResult",
                      data: auditedData,
                    });
                  });
                }
              });
            },
          );
        }
      });
 });

Relying on info.status === “complete” doesn’t gaurantee network idleness

What’s the best way to showcase 10+ videos on a website while maintaining optimal performance?

Your problem revolves around performance issues when embedding 40+ YouTube videos on a portfolio website

I’m building a portfolio website for a client who primarily showcases their work through images. However, they also requested adding videos—around 40 in total. I uploaded them to YouTube and used `<iframe> `embeds to display them, including both regular videos and YouTube Shorts. Initially, with about 10 videos, everything worked fine. But now, with all 40 embedded, the page performance has dropped significantly. It takes longer to load, and the videos flicker (appearing and disappearing) until the page fully loads. What’s the best way to handle this? Should I continue using YouTube embeds or explore alternative solutions? Any recommendations would be greatly appreciated!

Are promise handlers cleared from memory after the promise is resolved?

I have an Angular app. I have a service, which has an initialization logic – a backend call to fetch some data. Once the backend call is processed, I resolve a promise, indicating the service is ready to be called. I keep the promise exposed for the whole life of the service/app. Any newly created component using that service checks if the service was initialized (and executes its own init logic), via adding a handler – calling then() on the promise. I can guarantee only a few handlers are be added before the promise is resolved, vast majority of the handlers are added after (during the whole life of the app).

Obviously, the promise itself will never be garbage collected. Are the handlers added to the promise cleared (garbage collected) once they are executed? Or will they keep stacking until I run out of memory?

Recreate http post request with nodejs from Watchguard firebox login

I have a Watchguard Firebox T40-W. It has a web login where you can access all kinds of informations.

My goal is to duplicate that same web request with nodejs to be able to get those informations via code. Since the Firebox doesn’t have some kind of api documentation (Yes, I know watchguard cloud has an api interface), I tried to just copy everything that happens on the login page to get logged in.

The login has two parts. First the pre login and second the actual login.

When looking inside the browser when accessing the login page, I can see a login.js gets loaded. That file has everything I need to know.

The pre login does look like this:

 login : function () {
    if (!LOGIN.validateForm()) {
        return false;
    }

    $('#submit').button('loading');
    var username = $('#username').val();
    var password = $('#password').val();
    var domain = $('#domain').val();

    var obj = {};
    var agent_url = '';
    var agent_methodName = '';

    obj = {password: password, user: username, domain: domain, uitype: '2'};
    agent_url = 'agent/login';
    agent_methodName = 'login';


    $.xmlrpc({
        url: agent_url,
        methodName: agent_methodName,
        params: [obj],
        success: LOGIN.loginSuccess,
        error: LOGIN.loginError
    });

    return false;
},

This is a xmlrpc request to get needed data. That part was pretty easy to build in node:

var client = xmlrpc.createSecureClient({ host: this.host, port: this.port, path: '/agent/login'});
    var obj = {
        password: password,
        user: username,
        domain: "Firebox-DB",
        uitype: '2'
    }

    // Sends a method call to the XML-RPC server
    return new Promise<void>((res, rej) => {
        client.methodCall('login', [obj], (error: any, value: any) => {
            if (error) {
              if (error.code === 409) {
                  rej(error.faultString)
              } else {
                  rej(error);
              }
              return;
            }
            this.sid = value.sid;
            this.wga_csrf_token = value.csrf_token;
            this.privilege = value.readwrite.privilege + "";
            res();
        });
    });

In this part a xmlrpc client gets created and a request will be made to the same location as the original script of the webpage does. This does work and will get me the needed informations for the actual login.

The original webpage login will run the function LOGIN.loginSuccess if that xmlrpc request succedes. Lets take a look:

loginSuccess : function (data, textStatus, jqXHR) {
    var username = $('#username').val();
    var password = $('#password').val();
    var domain = $('#domain').val();
    var from_page = $('#from_page').val();
    var sid = '';
    var wga_csrf_token = '';
    var cp_csrf_token = $('#cp_csrf_token').val();
    var privilege = 0;
    var req_id = '';
    var mfa_options = '';

    var authServer = $('#sel_auth_server').val();
    
    if (data.length > 0) {
        sid = data[0].sid;
        wga_csrf_token = data[0].csrf_token;
        privilege = data[0].readwrite.privilege;
    }

    data = {
        username: username,
        password: password,
        domain: domain,
        sid: sid,
        wga_csrf_token: wga_csrf_token,
        cp_csrf_token: cp_csrf_token,
        privilege: privilege,
        from_page: from_page
    };

    LOGIN.postLogin(data);
},

A new object will be created an LOGIN.postLogin() gets executed:

postLogin : function (params) {
    var form = document.createElement("form");
    form.setAttribute("method", "post");
    form.setAttribute("action", "auth/login");
    

    var key, field;
    for (key in params) {
        if (params.hasOwnProperty(key)) {
            field = document.createElement("input");
            field.setAttribute("type", "hidden");
            field.setAttribute("name", key);
            field.setAttribute("value", params[key]);
            form.appendChild(field);
        }
    }

    document.body.appendChild(form);
    form.submit();
}

This basically just converts the object to a invisible form and submits it to auth/login as POST.

Building this part with nodejs would look like that:

    const params = new URLSearchParams();
    params.append('username', username);
    params.append('password', password);
    params.append('domain', this.domain);
    params.append('sid', this.sid);
    params.append('wga_csrf_token', this.wga_csrf_token);
    params.append('cp_csrf_token', this.cp_csrf_token);
    params.append('privilage', this.privilege);
    params.append('from_page', '/');
    
    const response = await fetch(`https://${this.host}:${this.port}/auth/login`, {method: 'POST', body: params, headers: this.httpHeaders });
    const data = await response.text();

I got every parameter I need with the right value. The header gets set with an session_id=xxx that is in every request on the actuall webpage.

Problem is that running the actual login will result in a 403 - forbidden error. Seems like the request from the webpage does something else so I just copied every property of that header into the nodejs request:

Header original

private httpHeaders = {
    "Cookie": '', // Will be filled by code. Will look like 'session_id=xxx'
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
    "Origin": 'https://10.10.10.1:8080',
    "Referer": 'https://10.10.10.1:8080/auth/login?from_page=/',
    "Accept": 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    "Accept-Encoding": 'gzip, deflate, br, zstd',
    "Accept-Language": 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
    "Cache-Control": 'max-age=0',
    "Connection": 'keep-alive',
    "Sec-Ch-Ua": '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
    "Sec-Ch-Ua-Mobile": '?0',
    "Sec-Ch-Ua-Platform": '"Windows"',
    "Sec-Fetch-Dest": 'document',
    "Sec-Fetch-Mode": 'navigate',
    "Sec-Fetch-Site": 'same-origin',
    "Sec-Fetch-User": '?1',
    "Upgrade-Insecure-Requests": '1'
}

Doing that will get me the response <html><head><meta http-equiv="Refresh" content="0;url=/auth/login" /></head><body></body></html>

Right click the request inside chrome from the network tab and copy => as fetch (nodejs) will copy pretty much exactly what I did. Running that copied function will also result in that kind of refresh response. But I noticed when removing the Referer from the header, the response switches back to 403 - forbidden.

What am I missing here?

Importar Componentes JavaScript React dentro de VSC

Hola a todos, probablemente sea una mala configuración mía, pero estoy teniendo problemas con la importación de componentes a mi APP de JavaScript, resulta que al querer simplificar la función con Ejemplo: <Componente1 /> y me obliga a hacerlo manualmente, todo esto dentro de las mismas carpetas dentro del VSC. Quisiera saber si hay alguna configuración para esto?

Al querer importar un componente ya creado con react, no lo importa en mi código principal de la app.

groupBy: Modify existing group based on incoming values

Using RxJS, I have the code below:

const { Subject, groupBy, mergeMap, toArray, timer, map } = require('rxjs');

const waitTime = 1000

const mySource = new Subject();

mySource
  .pipe(
    groupBy(
      (val) => {
        return JSON.stringify(val.ids?.sort())
      },
      null,
      () => timer(waitTime)
    ),
    mergeMap(group$ => group$.pipe(
        toArray(), // Makes whole thing wait till time is up
         map(arr => arr[0]) // Only use first item (they will be equal anyway)
    ))
  )
  .subscribe(data => console.log(new Date().toISOString(),"subscriber GOT:",data));
  
 // Code below is just for testing 

async function sendToObs(data,delay) {
    await new Promise(resolve => setTimeout(resolve,delay))
    console.log(new Date().toISOString(), "Sending", data)
    mySource.next(data);
}

async function myMain() {
  await sendToObs({ ids: ['a','b'] }, 100);
  await sendToObs({ ids: ['c','d'] }, 100);
  await sendToObs({ ids: ['a','b'] }, 100);
  await sendToObs({ ids: ['b','a'] }, 100);
  await sendToObs({ ids: ['a','b'] }, 100);
  await sendToObs({ ids: ['e','f'] }, 100);
  await sendToObs({ ids: ['a','b'] }, waitTime + 500);
}

myMain();

What this should do is:

  • De-duplicate incoming values (based on their ids property, not
    regarding order)
  • Keep them on hold for a second
  • Afterwards, send it to the subscriber.

That seems to work, and the subscriber gets as expected:

{ ids: [ 'a', 'b' ] }
{ ids: [ 'c', 'd' ] }
{ ids: [ 'e', 'f' ] }
{ ids: [ 'a', 'b' ] }

Now for the problem: If at any moment, a value like { mergeMeWith: ['e'], foo1: '7_e' } or { mergeMeWith: ['f'], foo2: '42_f' } comes in, it should be merged together with { ids: [ 'e', 'f' ] } resulting an output like:

{ ids: [ 'a', 'b' ] }
{ ids: [ 'c', 'd' ] }
{ ids: [ 'e', 'f' ], mergeMeWith: ['e', 'f'], foo1: '7_e', foo2: '42_f' }
{ ids: [ 'a', 'b' ] }

How do I do this?

@IonChange does not fire in ion-datetime component (vue.js)

Problem

I’m using Ionic v8 with Vue 3 and have a component that wraps an inside an . The modal is triggered by a button in the parent component using a unique id passed via props. The is supposed to emit an ionChange event when the user selects a different date and taps the confirmation button. However, the event never fires (the console.log(“here”) inside handleEmit is never logged).

Versions (for reference):

Ionic Vue: 8.3.3
Vue: 3.3.0

Code

Child Component (DateTimeModal.vue)

<script setup>
import {IonDatetime, IonModal} from "@ionic/vue";
import {computed, ref} from "vue";

const props = defineProps([
  'time',
  'modalTrigger'
])

const emit = defineEmits([
  'emitDateChange'
])

const dateTime = ref(props.time)

const dateTimeComputed = computed(() => {
  return dateTime.value
})

function handleEmit(event) {
  console.log("here")
  dateTime.value = event.detail.value
  console.log(dateTime.value)
  emit('emitDateChange', dateTime.value)
}

</script>

<template>
  <ion-modal :trigger="props.modalTrigger" :initial-breakpoint="1" :breakpoints="[0, 1]"
             class="text-gray-500">
    <div class="flex justify-center">
      <ion-datetime @ionChange="handleEmit($event)" presentation="date" :value="dateTimeComputed" :show-default-buttons="true"></ion-datetime>
    </div>
  </ion-modal>
</template>

Parent Component (simplified)

<button v-if="formattedStartDate === formattedEndDate" class="bg-transparent"
        :id="`open-modal${props.modalIndex}`">
  <ion-icon :icon="calendarOutline" class="text-2xl lg:text-lg"></ion-icon>
</button>

<DateModalComponent
    v-if="formattedStartDate === formattedEndDate"
    :modal-trigger="`open-modal${props.modalIndex}`"
    :time="localStartTime"
    @emitDateChange="changeOneDate"
>
</DateModalComponent>

I’m not seeing any errors in the console, but the event handler just doesn’t run. Any ideas on what might be causing ionChange not to fire, or how I can get the selected date reliably once the user changes it?

Interestingly, it was working fine two days ago, but it suddenly stopped without any code changes.

Any help or suggestions would be greatly appreciated!

  • The modal opens correctly when I click the parent button, and the UI appears.
  • The onMounted log in the child confirms that props.modalTrigger is set.
  • However, changing the date (and tapping the confirmation button) never triggers the ionChange event — the console.log(“here”) inside handleEmit is never called.
  • I’m expecting ionChange to fire after the user selects a date and confirms it.

we are integrating diagram.net with plantuml on private instance on GCP but it does not work since authnication is not working for container

we are integrating diagram.net with plantUML on private instance on GCP but it does not work since authentication is not working for container(There are 2 containers app and plantUML). We tried to call the authentication code from the Javascript(here is the link that we refer-https://cloud.google.com/run/docs/authenticating/service-to-service) but it doesn’t authenticate, also it gives CORS policy error in console screen, we tried to set the header to ‘Access-Control-Allow-Origin’ as well.

Can you please give some suggestion how we can proceed with the solution?

Set ESLint higher priority than Prettier

I like using my custom rules for linting and prefer to use both ESLint and Prettier (has auto format in VS Code). Some of the rules conflict with each other e.g. @stylistic/js/array-bracket-spacing rule conflicts with Prettier. In code line const t = ['t'];, if I do not complete the array-bracket-spacing rule then VS Code throws error A space is required after '['. eslint(@stylistic/js/array-bracket-spacing) and if I do, then it throws error Delete '·' eslint(prettier/prettier). How to set ESLint rules overrule Prettier rules?

prettier.config.mjs:

/** @type {import("prettier").Options} */
const config = {
    arrowParens: "avoid",
    bracketSameLine: false,
    bracketSpacing: true,
    endOfLine: "auto",
    printWidth: 110,
    proseWrap: "preserve",
    semi: true,
    singleAttributePerLine: false,
    singleQuote: false,
    tabWidth: 2,
    trailingComma: "none",
    useTabs: true
};

export default config;

eslint.config.mjs:

import eslint from "@eslint/js";
import json from "@eslint/json";
import pluginJs from "@stylistic/eslint-plugin-js";
import pluginJsx from "@stylistic/eslint-plugin-jsx";
import pluginTs from "@stylistic/eslint-plugin-ts";
import eslintConfigPrettier from "eslint-config-prettier";
import deprecation from "eslint-plugin-deprecation";
import pluginImport from "eslint-plugin-import";
import jest from "eslint-plugin-jest";
import preferArrow from "eslint-plugin-prefer-arrow";
import eslintPluginPrettier from "eslint-plugin-prettier";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import tseslint from "typescript-eslint";

import pretterConfig from "./prettier.config.mjs";

export default tseslint.config(
    eslintConfigPrettier,
    eslintPluginPrettierRecommended,
    eslint.configs.recommended,
    tseslint.configs.eslintRecommended,
    ...tseslint.configs.recommended,
    { ignores: ["build", "coverage", "dist", "node_modules"] },
    {
        languageOptions: {
            parser: tseslint.parser,
            parserOptions: {
                ecmaFeatures: {
                    jsx: true
                },
                ecmaVersion: 2020,
                project: ["./tsconfig.json"],
                sourceType: "module"
            }
        },
        plugins: {
            prettier: eslintPluginPrettier,
            "@stylistic/js": pluginJs,
            "@stylistic/jsx": pluginJsx,
            "@stylistic/ts": pluginTs,
            "@typescript-eslint": tseslint.plugin,
            deprecation: deprecation,
            import: pluginImport,
            jest: jest,
            json: json,
            "prefer-arrow": preferArrow
        }
    },
    {
        files: ["**/*.js", "**/*.mjs", "**/*.ts", "**/*.tsx"],
        rules: {
            "prettier/prettier": ["error", pretterConfig],

            "@stylistic/js/array-bracket-spacing": [ "error", "always"],
            ...
        }
    },
    ...
);

package.json:

{
    ...
    "devDependencies": {
        "@eslint/js": "8.57.0",
        "@eslint/json": "0.5.0",
        "@stylistic/eslint-plugin-js": "2.8.0",
        "@stylistic/eslint-plugin-jsx": "2.9.0",
        "@stylistic/eslint-plugin-ts": "2.8.0",
        "@typescript-eslint/eslint-plugin": "8.5.0",
        "@typescript-eslint/eslint-plugin-tslint": "7.0.2",
        "@typescript-eslint/parser": "8.5.0",
        "eslint": "8.57.0",
        "eslint-config-prettier": "8.10.0",
        "eslint-plugin-deprecation": "3.0.0",
        "eslint-plugin-import": "2.31.0",
        "eslint-plugin-jest": "28.8.3",
        "eslint-plugin-prefer-arrow": "1.2.3",
        "eslint-plugin-prettier": "5.2.1",
        "prettier": "3.0.0",
        "prettier-eslint": "15.0.1",
        "stylelint": "16.9.0",
        "stylelint-config-standard": "36.0.1",
        "stylelint-scss": "6.7.0",
        "typescript": "4.9.5",
        "typescript-eslint": "8.5.0",
    },
    ...
}

Facing a ‘socket hang up’ issue while executing a query on ClickHouse in a Node.js application

I’m experiencing a recurring “socket hang up” error when using ClickHouse with Node.js, and I’m hoping to understand how to reproduce and fix it. Here are the details:

ClickHouse version: 23.6.2.18
Node.js version: v20.12.2


Logs:

48|pg2ch  | 06-01-2025 15:51:52: BATCH_SIZE 100000 Table
48|pg2ch  | 06-01-2025 15:51:52: err OPTIMIZE...................... Error: socket hang up
48|pg2ch  | 06-01-2025 15:51:52:     at connResetException (node:internal/errors:787:14)
48|pg2ch  | 06-01-2025 15:51:52:     at Socket.socketCloseListener (node:_http_client:468:25)
48|pg2ch  | 06-01-2025 15:51:52:     at Socket.emit (node:events:530:35)
48|pg2ch  | 06-01-2025 15:51:52:     at TCP.<anonymous> (node:net:337:12) {
48|pg2ch  | 06-01-2025 15:51:52:   code: 'ECONNRESET'
48|pg2ch  | 06-01-2025 15:51:52: }

This error repeats multiple times and then the process resumes.


What I suspect:

  • The error is happening during an OPTIMIZE query to ClickHouse.
  • ECONNRESET typically indicates that the server closed the connection abruptly, but I can’t identify why.

Any insights or troubleshooting tips would be greatly appreciated! Thank you!


Code:

async.each(resp.rows, function(row, callback) {
    let PId = row.id;
    let optimizeQuery = `OPTIMIZE TABLE ${table} PARTITION ${PId} FINAL`;
    ch.query(optimizeQuery, (errOptimize, optimizeDone) => {
        if (errOptimize) {
            return callback(errOptimize);
        } else {
            callback();
        }
    });
}, function(err) {
    if (err) {
        reject({'ack': 'Failure'});
    } else {
        resolve({'ack': 'Success'});
    }
});

What I’ve tried:

  • Verified that ClickHouse is up and running during the error.
  • Checked network stability.
  • Increased connection and query timeouts.
  • Reduced batch sizes (though it’s still happening even at 100k rows).

Questions:

  1. How can I reliably reproduce this socket hang-up error with ClickHouse and Node.js?
  2. What could cause ClickHouse to abruptly terminate the connection during an OPTIMIZE query?
  3. Are there best practices for keeping ClickHouse connections stable with large batch operations?

Any help or pointers would be amazing!

How to perform polling using angular signals and resource API

I have this scenario, where the database is updated regularly, for example say stock market prices (I do not need immediate updates a poll every 2 minutes is good enough).

Using short intervals like 2 or 5 seconds will lead to performance issues so I am choosing 2 minutes as my threshold for polling.

I want to implement a polling mechanism to update the values at the set interval.

Short and long polling

I know it can be done with push based systems, but I want to achieve this with push based system.

Below it the minimal reproducible code and working stackblitz for reference:

@Component({
  selector: 'app-root',
  imports: [CommonModule],
  template: `
    <div>{{rxResource.value() | json}}</div>
    <hr/>
    <div>{{resource.value() | json}}</div>
  `,
})
export class App {
  http = inject(HttpClient);
  serviceIdSignal: WritableSignal<number> = signal(1);
  rxResource = rxResource({
    request: () => this.serviceIdSignal(),
    loader: ({ request: id }) => {
      return this.http.get(`https://jsonplaceholder.typicode.com/todos/${id}`);
    },
  });

  resource = resource({
    request: () => this.serviceIdSignal(),
    loader: ({ request: id }) => {
      return fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(
        (res: any) => res.json()
      );
    },
  });
}

The above code does not react/poll, my input signal stays the same, but I need to poll using resource API.

Stackblitz Demo

Change functions to use arrow [closed]

For the following functions that aren’t using fat arrows, how would I change them to use arrow functionality?

function function1(...arguments) {
    return arguments.filter(num => num >= 4)
}

const function2 = (start, end) =>{
    const result = []
    for (let i = start; i <= end; i++){
        result.push(i)
    }
    return result
};

function function3(myArray) {
    return myArray.sort((a, b) => b.length - a.length);
}

const function4 = (array) => array.sort()

const function5 = (array) => array.reduce((sum, item) => sum + (typeof item === 'number' ? parseInt(item, 10) : 0))

function function6(arr,value) {
    return arr.indexOf(value) !== -1 ? arr.indexOf(value) : -1

}

function function7(arr, index) {
    if (index >= arr.length || index < 0) { return -1 }
    if (typeof arr[index] === 'string' && arr[index].includes('mat')) { return 'mat' }
    return arr[index] === 5 ? 5 : arr[index]
}

jQuery.getScript doesn’t retrieve a script in Firefox

We have a legacy solution that is based on running a huge (6000+lines) Javascript code. It’s started by executing a small Javascript code as a bookmarklet in Firefox (a bookmark containing a code instead of an URL). The initial code downloads the main script from a web server and initiates its execution. It’s important that the certain web page should be opened at that moment in the web browser, otherwise the main script won’t work.

Currently, we’re using the below code in the bookmarklet:

javascript:(
function(){
    if(window.location.href.indexOf('BaseURL')==-1){
        if(confirm("You are not on the web page - go to it?")==true){
            window.location.href=' BaseURL ';
        }
    }
    else{
        jQuery.getScript("ScriptURL");
    }
}
)()

The drawback is, users should click OK each time they start the app and then click the same bookmark again. I’m trying to simplify the process by immediately opening the web page. However, the below code doesn’t work. The BaseURL is opened, but the script is not executed:

javascript:(
function(){
  window.location.href='BaseURL';
  jQuery.getScript("ScriptURL");
}
)()

When checking the debugger in Firefox, I see that there is a request to retrieve the script from the ScriptURL, but it’s completely empty, and the web site doesn’t return anything. Seems the getScript command is not executed correctly.

I’m not a Javascript / webdev specialist. I have a programmer background in different scripting languages, but I don’t know almost anything about Javascript. My apologies if I ask a trivial question, but my attempts to search for a solution on my own have failed.

Unexpected Output When Adding an Empty Object {} to an Empty Array [] in JavaScript [duplicate]

I’m working on a JavaScript project where I need to concatenate an empty object {} and an empty array []. However, I’m getting an unexpected result.
Here’s a simplified version of my code:

console.log({} + []);

I expected this to either throw an error or return something meaningful, but instead, it prints 0.

I tried:

  • Wrapping {} in parentheses:

      console.log(({} + [])); // Output: "[object Object]"
    

    This gives a different result, which confuses me further.

  • Checking the behavior of + with different types:

      console.log([] + {}); // "[object Object]"
    

    This behaves differently compared to {} + [].

Why does {} + [] return 0 instead of [object Object] like [] + {}?

Is this a bug, or is there an internal JavaScript rule that causes this behavior?

How can I safely concatenate an empty object and an empty array without getting unexpected results?