25

I am making a Chrome Extension, in which I am using Speech Synthesis. When I type speechSynthesis.getVoices() in the console I get an Array of 21 different voices. Great!

When I console.log() the same line within my javascript code, I get an Empty Array in the console. What's the matter, I can't figure out!

Melvin Abraham
  • 2,870
  • 5
  • 19
  • 33
  • Could the internal array that speechSynthesis gets its data from be populated asynchronously? What happens when you try to run your code after a setTimeout instead? – CertainPerformance Mar 27 '18 at 07:27
  • Yeah, you are right. It works with a timeout of 1 second for me. Thanks! – Melvin Abraham Mar 27 '18 at 07:34
  • Is there a better way to do this with a promise? I tried the following but it still returns an empty array: `function set_up_speech() { return new Promise(function(resolve, reject) { var synth = window.speechSynthesis; var voices = synth.getVoices(); resolve(voices) }) }` CALLED WITH `set_up_speech().then(function(voices) { console.log(voices) });` – user1063287 Aug 23 '18 at 10:27
  • @user1063287 It returns an empty array since you **immediately** *resolved* the promise and returned the array as it is without checking if the array *voices* is populated with the voice names and is not empty. – Melvin Abraham Aug 24 '18 at 12:56
  • @MelvinAbraham , thanks, i ended up using this: https://stackoverflow.com/a/51998112 – user1063287 Aug 24 '18 at 13:02
  • 1
    @user1063287 I have posted my own version to the problem. Check it out... – Melvin Abraham Aug 24 '18 at 13:17

1 Answers1

31

As pointed out by @CertainPerformance in the comments, when a page is loaded, it takes some amount of time to populate the voices array as it does so, asynchronously. Due to which when the array is logged into the console immediately after the page loads, we see an empty array...

To fix this, we console log it after some time (say, 10 or 50 ms):

setTimeout(() => {
    console.log(window.speechSynthesis.getVoices());
}, <time_in_ms>);

If you want to achieve the same with Promises, then, here's the code:

function setSpeech() {
    return new Promise(
        function (resolve, reject) {
            let synth = window.speechSynthesis;
            let id;

            id = setInterval(() => {
                if (synth.getVoices().length !== 0) {
                    resolve(synth.getVoices());
                    clearInterval(id);
                }
            }, 10);
        }
    )
}

let s = setSpeech();
s.then((voices) => console.log(voices));    // Or any other actions you want to take...

Here, after each time interval, we check whether the voices array returned by getVoices() is empty or not. if it's not, we end up resolving the promise...

Melvin Abraham
  • 2,870
  • 5
  • 19
  • 33
  • If you go for this approach, referencing speechSynthesis may be necessary before the browser gets to loading the voices (I suppose it doesn't do that unless deemed necessary). You'll likely want to go with the onvoiceschanged event. – Herbert Van-Vliet Dec 10 '18 at 10:36
  • I'd imagine there should be some sort of "voicesReady" or "speechSynthesisReady" event, maybe in the future, and you can listen on it, rather then to check on it every 10ms... which is quite brute-force. – nonopolarity Oct 27 '19 at 08:26
  • 8
    yes! window.speechSynthesis.onvoiceschanged = () =>{ console.warn('voices are ready',window.speechSynthesis.getVoices()); }; – Plokko Jun 16 '20 at 13:40
  • 1
    It's better to use the `onvoiceschanged` event instead of setInterval as @Plokko describes. See also https://stackoverflow.com/questions/21513706/getting-the-list-of-voices-in-speechsynthesis-web-speech-api – Jezzamon Jan 05 '21 at 06:26
  • Why let s = setSpeech() first? why not just setSpeech().then((voices) => – Bryan Feb 04 '23 at 01:59