A singleton for me is just making sure you define a single instance of something at the entry point of your app and passing it down via constructor arguments, which is normally what I do.
I understand you can override modules in tests, and if you have a version of your app that runs in a different environment where you might replace a HTTP service with a different variant (like if an RPC service that normally performs HTTP requests, but in one case maybe you want to talk to specific process, worker or window using message passing).
I really really feel using constructor args is just a lot simpler in most cases. But I think there's a reason why people don't always do this:
1. Sometimes someone is using a framework that imposes a high level of inversion of control where you don't get to control how your dependencies are initialised.
2. (this was me when I was much younger) Someone dogmatically avoided using classes and thought they could get away with just data and functions, and then they realise there is some value in some shared instance of something and then they just undermine any functional purity. Don't get me wrong, functional purity is great, but there are parts of your apps that just aren't going to be functionally pure. And you don't need to go full OOP when you use classes.
----------------------------------------
Here are some examples of what I mean.
Here's one example, where I have an app defined in web components and I define the components for each part of the app before passing them into the skeleton. It's a more simple app and I avoid some shared state by different landmark components communicating by events.
https://github.com/AKST/analysis-notebook/blob/b3f082fc63c9b...
----------------------------------------
Here's another example, this app is built of multiple command line instructions that sometimes have their own version of services (singletons) with specific configuration, sometimes they are initialised in child processes.
https://github.com/AKST/Aus-Land-Data-ETL/blob/672280a8ded69...
Because this app is doing a lot of scrapeing and I wanted to also child worker processes to do their own HTTP I was going to make a variant of my HTTP service which talked to a daemon process that tracked state for open connections to specific domains to avoid opening too many simultaneously and getting blocked by that host.
Updating the code that uses HTTP will be effortless, because it will continue to conform to the same API. I know this because I've already done this multiple times with HTTP scrapping in the app.
https://github.com/AKST/Aus-Land-Data-ETL/blob/672280a8ded69...