18
\$\begingroup\$

I have script which plays audio, shows/hides elements:

   var audio = new Audio("audio/test.mp3");

    audio.addEventListener("ended", function(){ 
             element.fadeIn(0);
             setTimeout(function() {
                element2.fadeIn(0);
                setTimeout(function() {
                    element3.fadeIn();
                    sprite.play(); // a one lib for playing sprites
                },1000)
             },1000)
        });

// this is just event and we look for the last frame
sprite.on("frame", function(){
        if(this.frame === this.frames - 1)
        {
            this.pause();           
            setTimeout(function() {
                //here we show some another elements and play another audio
            },1000)
        }
    }); 

audio.play();

As you can see there are a lot of setTimeout and spaghetti code. This is very short version, I have more than 5 audio files and a lot of elements which I have to show and hide, and, it's very difficult to understand the order audio files playing and showing/hiding elements in the end. May be you know how to organize this flow, some libraries? I am a noob at promises, is it good idea here?

\$\endgroup\$
  • 4
    \$\begingroup\$ I'm a sucker for a great title. ++ \$\endgroup\$ – RubberDuck Jun 3 '15 at 11:33
  • 1
    \$\begingroup\$ What are you using for the fadeIn/fadeOut? jQuery? \$\endgroup\$ – Flambino Jun 3 '15 at 14:37
  • \$\begingroup\$ @Flambino: Yes, it's jQuery. By the way, fadeIn/fadeOut is fsater than show/hide. \$\endgroup\$ – user348173 Jun 4 '15 at 3:23
9
\$\begingroup\$

To generate those nested, nearly identical setTimeout calls, instead of coding them all manually you could have a recursive function that builds and returns a function for you. Something like this would work:

// Accepts an array of elements and a final callback function
// Returns a function to show all elements and execute the callback
function getFunctionToShowElements(elements, callback, currentIndex) {
    var i = typeof currentIndex === "undefined" ? 0 : currentIndex;
    if (i === elements.length) {
        // after the final element, call the callback
        return function () {
            callback();
        };
    } else {
        // for any preceding element, show the element and call a timeout
        var nextMethod = getFunctionToShowElements(elements, callback, i + 1);
        return function () {
            elements[i].fadeIn();
            setTimeout(nextMethod, 1000);
        };
    }
}

Then you could set up a function for creating audio objects and attaching the event listener to fade in the given elements.

function createAudio(filename,elements){
    var audio = new Audio(filename);
    audio.addEventListener("ended",function(){
        getFunctionToShowElements(elements,function(){sprite.play();})(); // get the function and execute it immediately
    });
    return audio;
}

At this point you'll be able to create an audio object with the desired event handler like so:

var audio = createAudio("audio/test.mp3",[element1,element2,element3]);

That's just to give you an idea of how you could go about cleaning it up.

There are other improvements you'd want to make in your final code, such as using a variable for the timeout delay, and wrapping up the relevant code sections in closures as necessary. We'd need to see the full code to identify other areas for improvement.

Proof that the concept works:

var els = document.querySelectorAll(".showme");
var sprite = {
    play: function () {
        alert("sprite.play");
    }
};
createAudio("audio/test.mp3", els);

function createAudio(filename, elements) {
    var audio = document.querySelector("button"); //new Audio(filename);
    audio.addEventListener("click", function () {
        getFunctionToShowElements(elements, function () {
            sprite.play();
        })();
    });
}


function getFunctionToShowElements(elements, callback, currentIndex) {
    var i = typeof currentIndex == "undefined" ? 0 : currentIndex;
    if (i === elements.length) {
        
        return function () {
            callback();
        };
    } else {
        
        var nextMethod = getFunctionToShowElements(elements, callback, i + 1);
        return function () {
            elements[i].style.display = "inherit";
            setTimeout(nextMethod, 1000);
        };
    }
}
.showme {
    background-color:red;
    display:none;
}
<button>Click me</button>
<div class="showme">Number 1</div>
<div class="showme">Number 2</div>
<div class="showme">Number 3</div>
<div class="showme">Number 4</div>

\$\endgroup\$
  • \$\begingroup\$ It was just example, there are different variations can be, but I got some ideas. Thanks. \$\endgroup\$ – user348173 Jun 3 '15 at 17:45
5
\$\begingroup\$

May be you know how to organize this flow, some libraries?

I would recommend seeing if RxJS is suitable for your purposes. One of its features is that it allows you to write async-await style code, which can be easier to read and write than using callbacks directly.

I've taken your code as a starting point, but changed a few things for the sake of demonstration. We're going to:

  • Wait for a button to be clicked
  • Play the audio
  • Wait for the audio to stop playing
  • Display a message saying the audio has ended
  • Wait one second
  • Display a new button
  • Wait for the new button to be clicked three times
  • Hide the new button
  • Wait one second
  • Hide the message

These steps have a fairly direct translation into code: whenever a step says we wait for something to happen, in the code we yield an observable:

Rx.spawn(function* () {
  yield Rx.Observable.fromEvent($("#playButton"), "click").first();
  
  var ended = Rx.Observable.fromEvent($("#audio"), "ended");
  $("#audio").trigger('play');
  yield ended.first();
  
  $("#audioEnded").fadeIn(0);

  yield Rx.Observable.timer(1000);
  
  $("#clickMe").fadeIn(0);
  
  yield Rx.Observable.fromEvent($("#clickMe"), "click").take(3);
  
  $("#clickMe").fadeOut(0);

  yield Rx.Observable.timer(1000);
  
  $("#audioEnded").fadeOut(0);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.all.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<button id="playButton">Start</button>

<p id="audioEnded" style="display: none">Audio has ended.</p>

<button id="clickMe" style="display: none">Click three times</button>

<p><audio id="audio" controls src="data:audio/wav;base64,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"/></p>

The RxJS repository has plenty of examples of how to use the library.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.