Search code examples
javascriptecmascript-6promisees6-promise

Promise design pattern


I've created a SoundManager to deal with sounds in my application. In my app, I instantiate a SoundManager and then use it to load audio files, and return a promise once they're loaded.

I've omitted some of the code as i don't think is important in this context - I know they all load properly, for instance, the then() function in my app, which is passed the array of Promises from loadSounds() receives an object with all the sounds (not promises).

However, if from my App (at a point where the sounds are all loaded), I call playSound(), the this.sounds variable will be an array of Promises.

Why does that happen? Once the promise is fulfilled, shouldn't the variable this.sounds in SoundManager update?

Also that doesn't look like a good pattern, so any ideas on how to implement this?

import Promise from 'bluebird';
import {Howl, Howler} from 'howler';

export default class SoundManager {
  constructor(){
    this.sounds;
  }

  loadSounds(soundsArray){
    this.sounds = soundsArray.map((data) => {
      return new Promise((resolve, reject) => {        
        // ...
      })
    })
    return Promise.all(this.sounds)
  }

  playSound(id){
    console.log('this.sounds: ', this.sounds); //this is an array of promises, when I expect an array of sound objects
    let sound = this.sounds.filter((sound) => sound.id === id);
    if (!sound.length) {
      console.warn(`could not find sound with id "${id}"`)
      return false;
    }
    sound[0].play()
  }
}

Solution

  • this.sounds contains an array of promises because that's exactly what you assign to it here:

      loadSounds(soundsArray){
        this.sounds = soundsArray.map((data) => {
          return new Promise((resolve, reject) => {        
            // ...
          })
        })
        return Promise.all(this.sounds)
      }
    

    The results of your soundsArray.map() is an array of promises.

    If you wanted it to contain the eventual values, you could do that like this:

      loadSounds(soundsArray){
        let sounds = soundsArray.map((data) => {
          return new Promise((resolve, reject) => {        
            // ...
          })
        })
        return Promise.all(sounds).then(data => {
            this.sounds = data;
            return data;
        });
      }
    

    Though doing this seems a bit problematic because other than using the promise that loadSounds() returns, there's no other way to know when this.sounds contains the data you want. So, if the only way to know when the data is there is the promise that loadSounds() returns, then it's not clear how other code would be able to reliably use this.sounds.

    You could store the promise from Promise.all() in this.sounds and then any code that wanted the sounds could use this.sounds.then() to get it. We'd have to see more of what you're trying to do with this.sounds and when you're trying to do it to know what the best design would be.