3

Bear with me--I realize what I'm doing below is stupid, I just can't see solutions online to un-stupidify myself :)

I have the following function, which I would like to have return a promise:

function getInput() {
  var form = $("form.prompt"); // some form in the DOM, this is working fine
  return Promise.resolve(form.show().focus().submit(function(event) {
    var input = $(this).find("input");
    var inputVal = input.val();
    alert("form submitted! input val is " + inputVal);
    event.preventDefault();
    resolve(inputVal);
  }));
}

I call that function and try to wait for the promise to resolve:

getInput().then(function(response) {
  alert("input resolved; response is " + response);
});

I would like to see the following behavior: no alerts until I submit the form, after which I would see "form submitted!" and then "input resolved".

Instead, I see "input resolved" immediately. (Obviously, after that, once I submit the form I see "form submitted!".)

I realize I'm completely not handling the .submit() function properly; I'm treating it like it is thennable, and will be cast by Promise.resolve() into a proper promise. Clearly, that's not happening. I gather that .submit() has some other value or properties and that Promise.resolve() is ending its promise evaluation chain right away, which is why the then() executes immediately.

Moreover, I realize I'm handling .submit() as if it's a single event I'm waiting on, whereas it's a callback function that is meant to be run every time the form submits.

My question is, what's the right way to do what I'm trying to do? Which is, postpone execution of some code (represented here by the "input resolved" alert) until a particular form is submitted once? It's like I need a function that would loop with a short Timeout until it sees that a flag has been set by the .submit() callback...

Ben Wheeler
  • 6,788
  • 2
  • 45
  • 55
  • 1
    why can't you just... execute the code when the form submits? why do you need a promise? – Kevin B Oct 17 '14 at 21:35
  • `Promise.resolve` expects a value. And what is `resolve` in your code?. You probably want `new Promise(function(resolve){...})` – elclanrs Oct 17 '14 at 21:38
  • 1
    I see. This is a form that only submits once per call of getInput. that makes sense. – Kevin B Oct 17 '14 at 21:41

3 Answers3

2

I think you want this:

function getInput() {
  var form = $("form.prompt"); // some form in the DOM, this is working fine
  var deferred = $.Deferred();
  form.show().focus().one("submit", function(event) {
    var input = $(this).find("input");
    var inputVal = input.val();
    alert("form submitted! input val is " + inputVal);
    event.preventDefault();
    deferred.resolve(inputVal);
  });
  return deferred.promise();
}
Dave
  • 10,748
  • 3
  • 43
  • 54
  • Tried this, it works great. Not choosing it as the answer only because it departs more from my original use of the newish native javascript Promises (I don't really understand the supposed problems with jQuery Deffereds, but I gather native promises are somehow better) – Ben Wheeler Oct 18 '14 at 03:37
  • 1
    @BenjaminWheeler (cc dave) the problem with jQuery deferreds here is that they're not throw safe. – Benjamin Gruenbaum Oct 18 '14 at 06:55
  • 1
    @BenjaminGruenbaum I'm sticking with jQuery Promises for now as the native JS version is not supported by **any** versions of Internet Explorer. – Dave Oct 18 '14 at 13:47
  • 1
    Use a polyfill, or better - use Bluebird – Benjamin Gruenbaum Oct 18 '14 at 14:10
  • @BenjaminGruenbaum thanks for mentioning Bluebird I was unaware of that library – Dave Oct 18 '14 at 14:56
1

You can totally do it. It may look like this:

function getInput() {
    return new Promise(function (resolve, reject) {
        var $form = $("form.prompt").on('submit', function (event) {
            var input = $form.find("input").val();
            event.preventDefault();
            resolve(input);
        });
    });
}

getInput().then(function (response) {
    alert("input resolved; response is " + response);
});

But this example perfectly demonstrates that Promises are not the right tool for the task: you can resolve or reject it only once.

Demo: http://jsfiddle.net/q306nzk2/

dfsq
  • 191,768
  • 25
  • 236
  • 258
  • This works great, and I think I can deal with that "resolve only once" issue by just nesting calls to getInput(), eg: http://jsfiddle.net/q306nzk2/1/ – Ben Wheeler Oct 18 '14 at 03:35
  • 1
    hmmm.... a problem: second form submission calls .on('submit'...) twice: http://jsfiddle.net/q306nzk2/2/ But, you can fix this pretty easily using .one instead of .on: http://jsfiddle.net/q306nzk2/3/ – Ben Wheeler Oct 18 '14 at 04:05
  • 1
    @BenjaminWheeler that's actually not necessary since the promise will only resolve once. The event handler will fire twice but the promise is already resolved so its state won't change anymore. Still it's better to use `.one` for clarity. – Benjamin Gruenbaum Oct 18 '14 at 06:53
  • What's IE? just kidding, what I really mean is FML ;) – Ben Wheeler Oct 18 '14 at 15:52
  • Benjamin Gruenbaum, I wouldn't want the code in the event handler to fire more than once for each user-triggered event. – Ben Wheeler Oct 18 '14 at 15:53
0

Taking @dsfq's answer even further --

Turns out you can nest promises in a flattened way that makes the code more elegant. This trick inspired by https://parse.com/docs/js_guide#promises-series -- please tell me if this is documented for native js promises anywhere!

(code is live at http://jsfiddle.net/q306nzk2/6/ )

function getInput() {
    return new Promise(function (resolve, reject) {
        var $form = $("form.prompt").one('submit', function (event) {
            var input = $form.find("input").val();
            event.preventDefault();
            resolve(input);
        });
    });
};

// start by making essentially a "blank", resolved promise:
var prm = Promise.resolve();

// next, append a function to perform, which returns a promise; 
// this will cause prm to effectively "block" until the promise 
// getInput returns is resolved
prm = prm.then(function() {
    return getInput().then(function(response) {
        alert("input resolved; response is " + response);
    });
});

// do it again -- this function won't execute until the previous promise resolves!
prm = prm.then(function() {
    return getInput().then(function(response) {
        alert("input resolved again; response is " + response);        
    });
});
Ben Wheeler
  • 6,788
  • 2
  • 45
  • 55
  • 1
    This 'trick' is documented in about a 100 answers here in SO :P There are even shorthands. – Benjamin Gruenbaum Oct 18 '14 at 06:54
  • Thanks! Is there a name for this pattern that I can search for? – Ben Wheeler Oct 18 '14 at 15:51
  • Probably search for "promises sequential" on Google, or "dynamic promise chain" or "promise for loop" (uses the same technique). All based on how promises abstract flow. – Benjamin Gruenbaum Oct 18 '14 at 15:53
  • I'd call it "unnesting promise callbacks", e.g. like [here](http://stackoverflow.com/a/22000931/1048572). And your code should actually look like `prm.then(getInput).then(function(response){…})` – Bergi Oct 18 '14 at 16:30