Bootstraps Models are Async

Last week a developer approached me and asked me if I could help him with a popup that breaks his page.

He was using Bootstrap 4 Modal. Bootstrap is based on jQuery, so the code looked somehow like this:

function moveToNewFolder(folder) {
     $("#notify").modal('hide');

     doRequest(folder).done(refreshItems).fail(
        $('#notify').modal('show').find('.modal-body').html('Error!');
     );
}

The problem was that when an error occurred, the modal did not appear. Even worse, the whole page was covered by a large div, so everything was un-clickable. And on top of that, the popup did work in 2 of 10 cases. So a random factor was included as well. The question was, how is all that possible?

Welcome in the async world of hell

After consulting the Bootstrap 4 documentation, I discovered the following remark:

All API methods are asynchronous and start a transition. They return to the caller as soon as the transition is started but before it ends. In addition, a method call on a transitioning component will be ignored.

Together with this example code:

$('#myCarousel').on('slid.bs.carousel', function (e) {
  $('#myCarousel').carousel('2') // Will slide to the slide 2 as soon as the transition to slide 1 is finished
})

$('#myCarousel').carousel('1') // Will start sliding to the slide 1 and returns to the caller
$('#myCarousel').carousel('2') // !! Will be ignored, as the transition to the slide 1 is not finished !!

So, I was not expecting this sort of behavior. Closing a modal should not be async in my opinion. But this gave conclusion about what was happening. For some reason, the notifiy hide call, was not finished when 'show' was called. This causes that the error page was not displayed.

In this case we could make it work with the following workaround:

function moveToNewFolder(folder) {
     doRequest(folder)
        .done(
            function(){
                refreshItems();
                $("#notify").modal('hide');
            })
        .fail(
            $('#notify').modal('show').find('.modal-body').html('Error!');
        );
}

Now hide and show didn't conflict anymore. In addition, the magically large div that broke the page was gone (although I have no explanation where this came from..)

How to execute code after element is hidden?

I found the two other possibilities to execute code sequentially after the modal is hidden:

1. Use Events

We could listen for the hidden event of the modal, and execute code after hiding is completed.

function moveToNewFolder(folder) {
    $('#notify').on('hidden.bs.modal', folder ,function (e) {
       doRequest(folder).done(refreshItems).fail(
             $('#notify').modal('show').find('.modal-body').html('Error!');
       );

       $('#notify').off('hidden.bs.modal');
    })

    $("#notify").modal('hide');
}

2 Use await

We can call promise() to return a promise object and await until its done. Then we can continue with our code. Notice that in order to use await, we need to change moveToNewFolder function to async:;

async function moveToNewFolder(folder) {
     await $("#notify").modal('hide').promise();

     doRequest(folder).done(refreshItems).fail(
        $('#notify').modal('show').find('.modal-body').html('Error!');
     );
}
``