Promises and the async/await syntax sugar on top of them are another JS construct that borrow quite a bit from functional literature.
Purity helps quite a bit with narrowing down bugs; you simply check that your inputs and outputs are correct without having to ever worry about state. We use effects to narrow the types of functions that can be legally called inside specific domain logic, which also narrows down a large class of bugs that we've seen creep into other code bases.
However, it's not a magic bullet. Time to getting someone unfamiliar with Haskell up to speed can be costly. Discovering time-saving idioms in a sea of bad documentation is frustrating. Smaller ecosystem than other popular languages means less blogs, and in Haskell's particular case the information seems either too low or high level with no in between.
Still, with 250k of production lines running as stable as can be, we have no complaints.
Unfortunately it's very tempting to intorduce side-effects 'just this once', which can make those techniques less useful, and takes some discipline to avoid. For example, in Scala it's easier to just throw an exception instead of wrapping results in a 'Try' type; or likewise for null/'Option'; etc. mostly since those results then require map/mapN/flatMap/traverse/etc. to handle, rather than giving us values directly.
However, I think it's usually worth the effort. For example, those map/mapN/flatMap/traverse functions are essentially 'setting policies' for how effects should interact; whilst the 'core logic' can remain completely agnostic.
As a very simple example, if we have 'l: List[A]' and 'f: A => Option[B]', we can combine these in multiple ways, e.g.
l.map(f) : List[Option[B]] // Run f on all elements, keeping all results
l.flatMap(f) : List[B] // Run f on all elements, discarding empty results
l.traverse(f): Option[List[B]] // Run f on elements in sequence; abort if any are empty
We can replace 'Option' with 'Future' to talk about concurrency rather than emptiness. We can replace 'Option' with 'Try' to talk about exceptions rather than emptiness. All of those expressions remain identical.More esoterically, we can replace 'Option[T]' with 'Reader[X, T]' for dependency injection of an 'X'; etc.
I've used these techniques commercially in PHP, Python, Javascript, Haskell and Scala (and academically in Racket, StandardML, Coq, Idris, and Agda too)
Hell, Java may actually be more referentially transparent than Haskell, due to it not having macros - the one thing that can break this functionality.
Again, please correct me if I'm wrong since I've been industry for barely 2 years: back in school functional programming looked cool, but when I entered industry(and even in my exploration through personal/OSS projects), I have found it to be nothing more for the most part than a cool toy to play with. I can somewhat see the benefit when it comes to parallelizing Big Data, but that seems to be a very special case.
Again if I'm just being ignorant, please educate me :)