How to reduce nested callbacks in javascript / jquery

I have been instructed to rewrite this terrible piece of code, which is intended for the gradual disappearance of layers on the map (they are all transparent png) on ​​the web page. It should work in sequence, then return to the beginning, where no layer is displayed, and gradually disappears one at a time. This sequence should be repeated forever.

I am not sure what the most recommended way to do this is in javascript and is interested in what the stack overflow has to say.

There must be a better way than that! I am interested in the pros and cons of any methods.

setInterval(function(){ $("#layer-1").fadeIn(1000, function() { $("#layer-2").fadeIn(1000, function() { $("#layer-3").fadeIn(1000, function() { $("#layer-4").fadeIn(1000, function() { $("#layer-5").fadeIn(1000, function() { $("#layer-6").fadeIn(1000, function() { $("#layer-7").fadeIn(1000, function() { $("#layer-8").fadeIn(1000, function() { // pause for 2 seconds, then reset and hide all layers: $("#home-map .layer").delay(2000).fadeOut(); }); }); }); }); }); }); }); }); }, 10000) 

Edit: the reason I think this is different from the other answers is because I tried to set things up in an infinite loop, as well as animation chains. There are many approaches to solving hell callback in javascript and its very common point, so there will undoubtedly be similar questions.

+5
source share
5 answers

The call is accepted ... using a recursive approach.

 (function main(index){ if(index>=9){ return $.when($("#home-map .layer").delay(2000).fadeOut()).then(function(){ main(1);//restart }); } $("#layer-"+index).fadeIn(1000,function(){ main(index+1); }); })(1); 

http://jsbin.com/rurokipipi/1/edit?output

+5
source

use class instead of ids, then skip them and add index based delay

 var layers = $(".layer").length; function foreverLoop() { $(".masterLayer").show(); $(".layer").hide(); $(".layer").each(function(index) { $(this).delay(1000*index).fadeIn(1000); }); $(".masterLayer").delay(1000*layers + 2000).fadeOut(1000); setTimeout("foreverLoop()", 1000*layers + 3000 + 500); } foreverLoop(); 
 .layer { display: none; } 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="masterLayer"> <div class="layer">layer 1</div> <div class="layer">layer 2</div> <div class="layer">layer 3</div> <div class="layer">layer 4</div> <div class="layer">layer 5</div> <div class="layer">layer 6</div> </div> 
+6
source

Using async wait + promises

if you call .promise() after the animation, you will get a promise. thanks to this, you can wait until he finishes.

 async function animate() { await $("#layer-1").fadeIn(1000).promise() await $("#layer-2").fadeIn(1000).promise() await $("#layer-3").fadeIn(1000).promise() await $("#layer-4").fadeIn(1000).promise() await $("#layer-5").fadeIn(1000).promise() await $("#layer-6").fadeIn(1000).promise() await $("#layer-7").fadeIn(1000).promise() await $("#layer-8").fadeIn(1000).promise() // pause for 2 seconds, then reset and hide all layers: await $("#home-map .layer").delay(2000).fadeOut().promise(); } const loop = () => animate().then(loop) loop() 

Looking at this, the for loop fits very well.

 async function animate() { for (let i = 1; i < 9; i++) await $(`#layer-${i}`).fadeIn(1000).promise() // pause for 2 seconds, then reset and hide all layers: await $("#home-map .layer").delay(2000).fadeOut(); } 

This is only possible in the latest browser.


Guessing can be made more dynamic if you have done

 for (let layer of $("#home-map .layer")) await $(layer).fadeIn(1000).promise() 

Here is an es5 alternative for the same problem, but with the help of reduction to the chain of promises

 function animate() { $(".layer").toArray().reduce(function(prev, elm){ return prev.then(function(){ return $(elm).fadeIn(1000).promise(); }) }, Promise.resolve()).then(function(){ // do the master $(".masterLayer").delay(2000).fadeOut(); }) } animate() 
 .layer { display: none; } 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="masterLayer"> <div class="layer">layer 1</div> <div class="layer">layer 2</div> <div class="layer">layer 3</div> <div class="layer">layer 4</div> <div class="layer">layer 5</div> <div class="layer">layer 6</div> </div> 
+5
source

 var MAX_LAYER_NUMBER = 8; var LAYER_FADE_IN_DURATION = 1000; var LAYER_FADE_OUT_DURATION = 1000; var LAYER_FADE_IN_OUT_DELAY = 2000; function fadeInLayer(layerNumber, done) { if (layerNumber <= MAX_LAYER_NUMBER) { $('#layer-' + layerNumber).stop(true, true).fadeIn(LAYER_FADE_IN_DURATION, function() { done(layerNumber); fadeInLayer(layerNumber + 1, done); }); } } function fadeInOutLayers() { fadeInLayer(1, function(layerNumber) { if (layerNumber === MAX_LAYER_NUMBER) { $(".layer").delay(LAYER_FADE_IN_OUT_DELAY).fadeOut(LAYER_FADE_OUT_DURATION); } }); } $(function() { fadeInOutLayers(); setInterval(fadeInOutLayers, LAYER_FADE_IN_DURATION * MAX_LAYER_NUMBER + LAYER_FADE_IN_OUT_DELAY + LAYER_FADE_OUT_DURATION) }); 
 .layer { display: none; } 
 <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <div id="layer-1" class="layer">layer-1</div> <div id="layer-2" class="layer">layer-2</div> <div id="layer-3" class="layer">layer-3</div> <div id="layer-4" class="layer">layer-4</div> <div id="layer-5" class="layer">layer-5</div> <div id="layer-6" class="layer">layer-6</div> <div id="layer-7" class="layer">layer-7</div> <div id="layer-8" class="layer">layer-8</div> 
+3
source

You have a repeating series of asynchronous function calls. You can use promises to smooth your arrow code , but we are not talking about code that we expect to try..catch errors, so there is no need for an asynchronous try..catch , which is what promises is.

Asynchronous functions that occur in a sequence are queues. jQuery has a nice queue method , which I recommend here.

Now, since you are executing this queue on different elements, you need to select a common element to store the kernel queue. In this case, I will use the body , but you can use almost any element (I recommend the closest common parent to all the elements that you are animating, because this will allow you to use the structure on the page in several places without queuing, interfering with each other, but this is a more advanced step).

The fx queue is the default queue in which the animation takes place. We will want to manage this queue separately from the fx queue so that other animations can occur next to this queue.

 function myAnimation() { $('body') // queue up the next step of the animation .queue('my-animation', (next) => { // `next` is a function that tells the queue to continue // on to the next step. We pass next to the complete // callback of the animation so that they can continue // fluidly. $('#player-1').fadeIn(1000, next); }) .queue('my-animation', (next) => { $('#player-2').fadeIn(1000, next); }) .queue('my-animation', (next) => { $('#player-3').fadeIn(1000, next); }) .queue('my-animation', (next) => { $('#player-3').fadeIn(1000, next); }) .queue('my-animation', (next) => { $('#player-4').fadeIn(1000, next); }) .queue('my-animation', (next) => { $('#player-5').fadeIn(1000, next); }) .queue('my-animation', (next) => { $('#player-6').fadeIn(1000, next); }) .queue('my-animation', (next) => { $('#player-7').fadeIn(1000, next); }) .queue('my-animation', (next) => { $('#player-8').fadeIn(1000, next); }) // here we want to wait for a bit before continuing with // the rest of the queue .delay(2000, 'my-animation') .queue('my-animation', (next) => { $('#home-map .layer').fadeOut(next); }) // here we repeat the animation so that a `setInterval` call // is unnecessary, and so that we don't have to care how long // the animation takes in total .queue('my-animation', (next) => { // queue up the next iteration of the animation myAnimation(); // continue with the queued animation next(); }); } myAnimation(); // queue up the animation $('body').dequeue('my-animation'); // start the animation 

Now this code is terribly repeated. I will leave this as an exercise for the reader to change it to a simple for loop or whatever you would like. This is just an example of general simplification.

The code in this example is longer than the original, but don't let this fool you. Since this is the turn, it’s easier to reason and edit later. Steps can be inserted and deleted without having to change the timeout or trying to rebalance the nested brackets / curly braces.

+1
source

Source: https://habr.com/ru/post/1267239/


All Articles