JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/2.9.33/bluebird.js"></script>
<button id="run">Press this multiple times</button>
<div id="log"></div>

CSS

#log p {
    margin: 0 1em;
    font-family: monospace;
}

JavaScript

function log() {
    var p = $("<p>").appendTo("#log");
    $("#log").children().slice(0, -15).remove();
    p.text([].map.call(arguments, function(x) {
        return typeof x == "string" ? x : JSON.stringify(x);
    }).join(" "));
}
function delay(val) {
    log("start: ", val);
    return new Promise(function(resolve)  {
        setTimeout(function() {
            log("end: ", val); 
            resolve();
        }, 500);
    });
}
function extendedChain(p, stream, action) {
     // chains a new action to p on every stream event
     // until the chain ends before the next event comes
     // resolves with the result of the chain and the advanced stream
     return Promise.race([
         p.then(function(res){return {res:res}}), // wrap in object to distinguish from event
         stream                   // a promise that resolves with a .next promise
     ]).then(function (arg) {
         return arg.next
           ? extendedChain(p.then(action), arg.next, action) // a stream event happened first
             : {res:arg.res, next:stream};                           // the chain fulfilled first
     });
}
function rec(stream, action, partDone) {
    return stream.then(function(arg) {
        return extendedChain(action(), arg.next, action).then(function(arg) {
            partDone(arg.res);
            return rec(arg.next, action, partDone);
        });
    });
}

var v = 1;
rec(getEvents($("#run"), "click"), function(){
    return delay(v++);
}, function(res) {
    log("all current done, none waiting");
    log("last result", res);
}); // forever

function getEvents(emitter, name) {
    var next;
    function get() {
        return new Promise(function (res) {
            next = res;
        });
    }
    emitter.on(name, function() {
        next({next: get()});
    });
    return get();
}