The following code is both insecure and biased in generating secure randomness for things like passwords:
function prng(range) {
  return Math.floor(Math.random() * range); // This is bad and you should feel bad.
}Why? Because Math.random() is not cryptographically secure and range is not guaranteed to divide Math.random() evenly.
What's happening exactly? Suppose you want to generate a password from all graphical ASCII characters. There are 94 such characters. Depending on your browser, Math.random() has either a 32-bit or 53-bit output space. In either case, 94 does not divide them evenly:
> console.log(2**32 / 94);
45691141.44680851
> console.log(2**32 % 94);
42
> console.log(2**53 / 94);
95821268667457.36
> console.log(2**53 % 94);
34Notice how we have a remainder in both cases. This means in the 32-bit generator, there are 52 values that will be generated one more time more than the other 42. In the case of the 53-bit generator, there are 60 values that will be generated one more time more than the other 34. This is unacceptable for secure secrets such as passwords or cryptographic keys.
Let's fix it:
function csprng(range) {
  const min = (-range >>> 0) % range;
  const rand = new Uint32Array(1);
  
  do {
    crypto.getRandomValues(rand);
  } while (rand[0] < min);
  
  return rand[0] % range;
}In this code, we are setting an absolute minimum value for the generated random number. This minimum value ensures that the random numbers we generate evenly divides range, such as 94 in our previous example. Anything less than that minimum value will be rejected, otherwise we can uniformly generate a random number. See also "Efficiently Generating a Number in a Range" by Dr. Melissa E. O'Neill.
Notice that this generator is using the Web Crypto API by utilizing crypto.getRandomValues(). This will use your operating system's cryptographically secure random number generator. This output is indistinguishable from random noise and is exactly what we want for generating secure passwords.
Let's put it in action:
function generatePassword() {
  let ascii = new Set();
  let password = "";
  
  for (let i = 33; i < 127; i++) {
    ascii.add(String.fromCharCode(i));
  }
  for (let i = 0; i < 12; i++) {
    password += [...ascii][csprng(ascii.size)];
  }
  
  return password;
}> console.log(generatePassword());
'Aw?^&]8Yl"J3'