JavaScript Avoid sequential animation callback hell in JS

I have an web project where I'd like to animate the opacity of five colored divs causing them to "blink" in sequence, and the user would then click on them in the same order (like Simon says). The demo sequence starts when the user clicks a button, and the button also fades out so it can only be clicked once. My code is this (only for the demo animation, not concerned with the user response at the moment):

function circleBlink(elem, callback) {
  elem.animate({'opacity':'0'}, function() {
    elem.animate({'opacity':'1'}, function() {
      if (callback && typeof callback === 'function') {
        callback();
      }
    });
  });
}

function runThrough() {
  circleBlink($('.sequence-options > .red-orange'), function() {
    circleBlink($('.sequence-options > .blue'), function() {
      circleBlink($('.sequence-options > .yellow'), function() {
        circleBlink($('.sequence-options > .green'), function() {
          circleBlink($('.sequence-options > .purple'));
        });
      });
    });
  });
}

$('.start-btn').click(function() {
  $that = $(this);
  $that.animate({'opacity': '0'}, function() {
    $that.addClass('hidden');
  });
  runThrough();
  setTimeout(runThrough, 5000);

});

The code works fine as-is, but I would like to know if there's a less verbose/more performant/best practice way to refactor it. I am using jQuery but don't want to bring in any other animation libraries or plugins for this particular project

Answer:1

You could create a wrapper function for animate that returns a Promise, and also turn circleBlink into a function that returns a Promise. You can also use an arrow function to avoid the uglyness of that = this:

const animateWithOpacity = (jqElm, opacity) => new Promise(resolve => {
  jqElm.animate({ opacity }, resolve);
});
async function circleBlink(elem) {
  await animateWithOpacity(elem, '0');
  await animateWithOpacity(elem, '1');
  // async function will automatically return promise that resolves when end is reached
}

async function runThrough() {
  const classes = ['red-orange', 'blue', 'yellow', 'green', 'purple'];
  for (const className of classes) {
    await circleBlink($('.sequence-options > .' + className));
  }
}

$('.start-btn').click(function() {
  animateWithOpacity($(this), 0)
    .then(() => $(this).addClass('hidden'));
  runThrough();
  setTimeout(runThrough, 5000);
  // might also be able to `runThrough().then(runThrough)` if the timing is right
});
Answer:2

so I have a time-stamp function in a javascript file that return a date that look like MM/DD/YY I would like tom import what the function return into another script ( node.js) and display it whenever ...

so I have a time-stamp function in a javascript file that return a date that look like MM/DD/YY I would like tom import what the function return into another script ( node.js) and display it whenever ...

I need to merge a Map instance w/ an object. This didn't work: > map = new Map([ ['foo', 1] ]) Map { 'foo' => 1 } > Object.assign({}, map) {} Looks like Object.assign doesn't like maps. Is ...

I need to merge a Map instance w/ an object. This didn't work: > map = new Map([ ['foo', 1] ]) Map { 'foo' => 1 } > Object.assign({}, map) {} Looks like Object.assign doesn't like maps. Is ...

I have JSON array that looks like var data = { "fields": [{ "firstName": { "fieldName": "First Name", "required": true, "provided": false } }, { "...

I have JSON array that looks like var data = { "fields": [{ "firstName": { "fieldName": "First Name", "required": true, "provided": false } }, { "...

I have lodash cdn included in this fiddle https://jsfiddle.net/byhh6Lyo/ console.log(_) console.log(_.range(1, 10)) _.map([1,2,3], (o) => console.log(o)) //worked The second line has an error, ...

I have lodash cdn included in this fiddle https://jsfiddle.net/byhh6Lyo/ console.log(_) console.log(_.range(1, 10)) _.map([1,2,3], (o) => console.log(o)) //worked The second line has an error, ...

  1. lodash range function