Q:

How to animate multiple HTML5 canvas objects one after another? 

I want to make an animation using the HTML5 canvas and JavaScript.  The idea is to write classes for different objects, like this:
    class Line {
      constructor(x1, y1, x2, y2) {
        this.x1 = x1;
        this.y1 = y2;
        ...
      }

      draw() {
        }
    }

    class Circle {
      constructor(x, y, radius) {
        this.x = x;
        ...
      }

      draw() {}
    }

    ...

Then all you would have to do in the main code is to draw the shapes one after another with pauses in between:
let line1 = new Line(x1, y1, x2, y2);
let circle = new Circle(x, y, r);
let line2 = new Line(x1, y1, x2, y2);

line1.draw()
pause()
circle.draw()
pause()
line2.draw()

...

Is there an easy way to this (without  having to deal with Promises and nested Callback Functions), for example by using some library? 

A:

Key frames
You can use key frames to great effect to animate almost anything. 
The example below (was going to do more of a write up but I was too late, you have accepted an answer) shows how a very basic key frame utility can create animations. 
A key frame is just a time and a value
Key frames are added to tracks that give a name to the value. 
Thus the name x (position) and the keys {time:0, value:100}, {time:1000, value:900} will change the x property from 100 to 900 during the time 0 to 1 second
For example a circle
const circle = {
    x: 0,
    y: 0,
    r: 10,
    col : "",
    draw() { 
        ctx.fillStyle = this.col;
        ctx.beginPath(); 
        ctx.arc(this.x, this.y, this.r, 0, Math. PI * 2); 
        ctx.fill() 
    }
};

can have any of its properties changed over time. 
First create a tracks object and define the keys 
const circleTracks = createTracks();

// properties to animate
circleTracks.addTrack("x");
circleTracks.addTrack("y");
circleTracks.addTrack("r");
circleTracks.addTrack("col");

Then add key frames at specific time stamps. 
circleTracks.addKeysAtTime(0, {x: 220, y :85, r: 20, col: "#F00"});
circleTracks.addKeysAtTime(1000, {x: 220, y :50, r: 50, col: "#0F0"});
circleTracks.addKeysAtTime(2000, {x: 420, y :100, r: 20, col: "#00F"});
circleTracks.addKeysAtTime(3000, {x: 180, y :160, r: 10, col: "#444"});
circleTracks.addKeysAtTime(4000, {x: 20, y :100, r: 20});
circleTracks.addKeysAtTime(5000, {x: 220, y :85, r: 10, col: "#888"});
circleTracks.addKeysAtTime(5500, {r: 10, col: "#08F"});
circleTracks.addKeysAtTime(6000, {r: 340, col: "#00F"});

When ready clean up the the keys (You can add them out of time order)
circleTracks.clean();

Seek to the start
circleTracks.seek(0);

And update the object
circleTracks.update(circle);

To animate just call the tick and update functions, and draw the circle
circleTracks.tick();
circleTracks.update(circle);
circle.draw();

Example
Click to start the animation. 
When it ends you can scrub the animation using tracks.seek(time)
This is the most basic keyframe animations.  
And the best thing about key frames is that they separate the animation from the code, letting you import and export animations as simple data structures. 

const ctx = canvas.getContext("2d");

requestAnimationFrame(mainLoop);

const allTracks = [];
function addKeyframedObject(tracks, object) {
    tracks.clean();
    tracks.seek(0);
    tracks.update(object);
    allTracks.push({tracks, object});
}
const FRAMES_PER_SEC = 60, TICK = 1000 / FRAMES_PER_SEC; //
const key = (time, value) => ({time, value});
var playing = false;
var showScrubber = false;
var currentTime = 0;
function mainLoop() {
    ctx.clearRect(0 ,0 ,ctx.canvas.width, ctx.canvas.height);
    if(playing) {
        for (const animated of allTracks) {
            animated.tracks.tick();
            animated.tracks.update(animated.object);
        }
    }
    for (const animated of allTracks) {
        animated.object.draw();
    }

    
    if(showScrubber) {
    
        slide.update();
        slide.draw();
        if(slide.value ! == currentTime) {
            currentTime = slide.value;
            for (const animated of allTracks) {
                animated.tracks.seek(currentTime);
                animated.tracks.update(animated.object);
            }
        }
        
    } else {
        if(mouse.button) { playing = true }
    }
    if(allTracks[0].tracks.time > 6300) { 
        showScrubber = true 
        playing = false;
    }
    
    requestAnimationFrame(mainLoop);
}





const text = {
    x: canvas.width / 2,
    y: canvas.height / 2,
    alpha: 1,
    text: "",
    draw() { 
        ctx.font = "24px arial";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillStyle = "#000";
        ctx.globalAlpha = this.alpha;
        ctx.fillText(this.text, this.x, this.y);
        ctx.globalAlpha = 1;
    }
}
const circle = {
    x: 0,
    y: 0,
    r: 10,
    col : "",
    draw() { 
        ctx.fillStyle = this.col;
        ctx.beginPath(); 
        ctx.arc(this.x, this.y, this.r, 0, Math. PI * 2); 
        ctx.fill() 
    }
}




const circleTracks = createTracks();
circleTracks.addTrack("x");
circleTracks.addTrack("y");
circleTracks.addTrack("r");
circleTracks.addTrack("col");

circleTracks.addKeysAtTime(0, {x: 220, y :85, r: 20, col: "#F00"});
circleTracks.addKeysAtTime(1000, {x: 220, y :50, r: 50, col: "#0F0"});
circleTracks.addKeysAtTime(2000, {x: 420, y :100, r: 20, col: "#00F"});
circleTracks.addKeysAtTime(3000, {x: 180, y :160, r: 10, col: "#444"});
circleTracks.addKeysAtTime(4000, {x: 20, y :100, r: 20});
circleTracks.addKeysAtTime(5000, {x: 220, y :85, r: 10, col: "#888"});
circleTracks.addKeysAtTime(5500, {r: 10, col: "#08F"});
circleTracks.addKeysAtTime(6000, {r: 340, col: "#00F"});

addKeyframedObject(circleTracks, circle);


const textTracks = createTracks();
textTracks.addTrack("alpha");
textTracks.addTrack("text");
textTracks.addKeysAtTime(0, {alpha: 1, text: "Click to start"});
textTracks.addKeysAtTime(1, {alpha: 0});
textTracks.addKeysAtTime(20, {alpha: 0, text: "Simple keyframed animation"});
textTracks.addKeysAtTime(1000, {alpha: 1});
textTracks.addKeysAtTime(2000, {alpha: 0});
textTracks.addKeysAtTime(3500, {alpha: 0, text: "The END!" }); 
textTracks.addKeysAtTime(3500, {alpha: 1});
textTracks.addKeysAtTime(5500, {alpha: 1});
textTracks.addKeysAtTime(6000, {alpha: 0, text: "Use slider to scrub"});
textTracks.addKeysAtTime(6300, {alpha: 1});
addKeyframedObject(textTracks, text);



function createTracks() {
    return {
        tracks: {},
        addTrack(name, keys = [], value) {  
            this.tracks[name] = {name, keys, idx: -1, value}
        },
        addKeysAtTime(time, keys) {
            for(const name of Object.keys(keys)) {
                this.tracks[name].keys.push(key(time, keys[name]));
            }
        },
        clean() {
            for(const track of Object.values(this.tracks)) {
                track.keys.sort((a,b) => a.time - b.time);
            }
        },
        seek(time) { // seek to random time
            this.time = time;
            for(const track of Object.values(this.tracks)) {
                if (track.keys[0].time > time) {
                    track.idx = -1; // befor first key
                }else {
                    let idx = 1;
                    while(idx < track.keys.length) {
                        if(track.keys[idx].time > time && track.keys[idx-1].time <= time) {
                            track.idx = idx - 1;
                            break;
                        }
                        idx += 1;
                    }
                }
            }
            this.tick(0);
        }, 
        tick(timeStep = TICK) { 
            const time = this.time += timeStep;
            for(const track of Object.values(this.tracks)) {
                if(track.keys[track.idx + 1] && track.keys[track.idx + 1].time <= time) {
                    track.idx += 1;
                }
                if(track.idx === -1) {
                    track.value = track.keys[0].value;
                } else {
                    const k1 = track.keys[track.idx];
                    const k2 = track.keys[track.idx + 1];
                    if (typeof k1.value ! == "number" || ! k2) {
                        track.value = k1.value;
                    } else if (k2) {
                        const unitTime = (time - k1.time) / (k2.time - k1.time);
                        track.value = (k2.value - k1.value) * unitTime + k1.value;
                    } 
                }
                
            }
        },
        update(obj) {
            for(const track of Object.values(this.tracks)) {
                obj[track.name] = track.value;
            }
        }   
    };
};














const slide = {
    min: 0,
    max: 6300,
    value: 6300,
    top: 160,
    left: 1,
    height: 9,
    width: 438,
    slide: 10,
    slideX: 0,
    draw() {
        
        
        ctx.fillStyle = "#000";
        ctx.fillRect(this.left-1, this.top-1, this.width+ 2, this.height+ 2);
        ctx.fillStyle = "#888";
        ctx.fillRect(this.left, this.top, this.width, this.height);
        ctx.fillStyle = "#DDD";
        this.slideX = (this.value - this.min) / (this.max - this.min) * (this.width - this.slide)  + this.left;
        ctx.fillRect(this.slideX, this.top + 1, this.slide, this.height - 2);
    },
    
    update() {

         if(mouse.x > this.left && mouse.x < this.left + this.width && 
            mouse.y > this.top && mouse.y < this.top + this.height) {
            
            if (mouse.button && ! this.captured) {
                this.captured = true;
            } else {
                canvas.style.cursor = "ew-resize";
            }
         }
         if (this.captured) {
            if (! mouse.button) {
               this.captured = false;
               canvas.style.cursor = "default";
            } else {
               this.value = ((mouse.x - this.left) / this.width) * (this.max - this.min) + this.min;
               canvas.style.cursor = "none";
               this.value = this.value < this.min ?  this.min : this.value > this.max ?  this.max : this.value;
               
            }
         } 
     }
 };
        
        
        
const mouse  = {x : 0, y : 0, button : false};
function mouseEvents(e){
     const bounds = canvas.getBoundingClientRect();
    mouse.x = e.pageX - bounds.left - scrollX;
    mouse.y = e.pageY - bounds.top - scrollY;
    mouse.button = e.type === "mousedown" ?  true : e.type === "mouseup" ?  false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
canvas { border: 1px solid black; }
<canvas id="canvas" width="440" height="170"><canvas>

