Using Bacon.js to disable submit button while deferreds are "pending"
Asked Answered
D

2

9

I have a form, which has many image urls - the back-end persists url strings and the images are uploaded directly to S3. I'd like to use Bacon.js streams to handle disabling/enabling the form's submit button while uploads are in-progress.

I've tried various approaches (using a stream of streams of Bacon.fromPromises, using a stream of fromPromise-d deferreds and a bus of raw deferreds and trying to manually diff the two) but haven't found a solution that a) works as intended and b) feels like I'm not fighting the library.

This is where things stand, but as noted, the submit button is prematurely re-enabled.

function toResultStream(promise) {
  return Bacon.fromPromise(promise)
}

var deferreds = $('a').asEventStream('click', function (event) {
    event.preventDefault();
    var deferred = $.Deferred();

    // simulate upload
    setTimeout(function () {
        deferred.resolve(true);
    }, _.random(200, 1600))

    setTimeout(function () {
        deferred.rejectWith(false);
    }, _.random(200, 1600))

    return deferred;        
});

deferreds.onValue(function () {
    $('#submit').attr('disabled', true);
})

// only takes completed deferreds into consideration
var ongoingSearch = deferreds.flatMap(function (d) {
    return toResultStream(d);
})
.mapError(true)
.onValue(function () {
    $('#submit').attr('disabled', false);
});

Fiddle

*Update

@mjs2600's answer was enough to nudge me towards a solution.

Here's what I ended up doing:

var bus = new Bacon.Bus();

var deferreds = $('a').asEventStream('click', function (event) {
    // ...
    bus.push(-1);        
    // ...
});

var ongoingSearch = deferreds
    .flatMap(toResultStream)
    .mapError(1)
    .merge(bus)
    .scan(0, function (memo, n) { return memo + n; })
    .onValue(function (value) {
        var disabled = value < 0;
        $('#submit').attr('disabled', disabled);
    });

Updated Fiddle

I know using Buses is frowned upon, so if anyone has a suggestion as to how I could achieve the same behavior with streams, I'd very much like to see it.

Distant answered 13/7, 2015 at 21:28 Comment(0)
Y
1

I would make a stream and map it to a -1 when you make the request and a +1 when you get a response. Then, you can run a fold over the stream and just enable the button when it equals 0.

Yesteryear answered 12/8, 2015 at 12:38 Comment(1)
Thanks for this. Your suggestion to use -1, 1 and fold/scan was the nudge that I needed.Distant
C
5

If you know the number of urls to upload, you could add skip like this to ignore the responses from all but the last url.

var numStreams = 1;
var ongoingSearch = deferreds.flatMap(function (d) {
    return toResultStream(d);
}) 
.mapError(true)
.skip(numSteams-1)
.onValue(function () {
    alert('fooo');
    $('#submit').attr('disabled', false);
});

One thing I find a little worrisome about the code you posted is that these streams never terminate. You might consider refactoring such that the ongoingSearch is created by the click and terminates once all the streams are complete. (For example, merge the upload streams and re-enable the button in the onEnd of that merge.)

Crenellate answered 21/7, 2015 at 19:5 Comment(1)
Thanks for this, but the number of requests is dynamic. I will definitely keep your stream termination suggestion in mind. This page is short-lived, but that's no excuse for sloppiness.Distant
Y
1

I would make a stream and map it to a -1 when you make the request and a +1 when you get a response. Then, you can run a fold over the stream and just enable the button when it equals 0.

Yesteryear answered 12/8, 2015 at 12:38 Comment(1)
Thanks for this. Your suggestion to use -1, 1 and fold/scan was the nudge that I needed.Distant

© 2022 - 2024 — McMap. All rights reserved.