0

I don't think this has been discussed on the boards yet. I've been researching chaining with promises and I found that you can chain promises to a specific deferrer. This allows all the functions registered through the then statements to be invoked asynchronously from what I have read. But, this assumes those functions are written asynchronously. So for example if I had the following:

function asyncEvent() {
  var dfd = jQuery.Deferred();
  dfd.resolve( "start" );
  return dfd.promise();
}

$.when( asyncEvent() ).then(
  function( status ) {
    console.log(status);
    setTimeout(function(){console.log("1");},1000);
  }
).then(function (status) {
 setTimeout(function(){console.log("2");},1000);
});

Part of this was taken from jquery's documentation.

The console would output 1 and 2 in order 1 second after status was shown. This is instead of showing 1, 1 second after status and then 2, 1 second after 1. So this method does not work for chaining synchronous functions.

I understand that there are ways to change the returned promise from each then. But, my question is, can the promise be changed implicitly within the then statement or do you have to use code like the following (which is the only way I can think to do it)? If so, could you show me a simplified version of the code that does this because I don't think the code on this page actually does this.

Notice the created deferred variable d is left in the scope chain when the function within the then returns. I would like to avoid this if possible. And it would be nice not to have to create it in the first place.

function asyncEvent() {
  var dfd = jQuery.Deferred();
  dfd.resolve( "start" );
  return dfd.promise();
}

$.when( asyncEvent() ).then(
  function( status ) {
   var d=$.Deferred();
   console.log(status);
   setTimeout(function(){console.log("1");d.resolve();},1000);
   return d.promise();
  }
).then(function (status) {
   setTimeout(function(){console.log("2");},1000);
});

Part of this was taken from jquery's documentation. EDIT: I understand that setTimeout isn't actually handled synchronously, but from an output standpoint it might as well be.

Community
  • 1
  • 1
fritz
  • 45
  • 9

3 Answers3

0

How to chain synchronous functions to run asynchronously with promises?

But, my question is, can the promise be changed implicitly within the then statement or do you have to use code like the following (which is the only way I can think to do it)?

Notice the created deferred variable d is left in the scope chain when the function within the then returns. I would like to avoid this if possible.


If requirement is to chain synchronous functions , try using .queue() or $.queue() . .queue() allows .promise("queueName") to be chained , where .then() could be called on "queueName" jQuery promise object when all `"queueName" functions called

var fns = [
    function asyncEvent(next) {
      var msg = "start";
      // push `msg` to `this.status` array
      this.status.push(msg);
      console.log(msg);
      // call `next` function in queue
      next();
    },
    function(next) {
      var msg = "1";
      setTimeout(function() {
        // push `msg` to `this.status` array
        this.status.push(msg);
        console.log(msg);
        // call `next` function in queue
        next()
      }.bind(this), 1000);
    },
    function(next) {
      var msg = "2";
      setTimeout(function() {
        // push `msg` to `this.status` array
        this.status.push(msg);
        console.log(msg);
        // call `next` function in queue
        next()
      }.bind(this), 1000);
    }
  ]
 
, obj = {
    status: []
  };
// set `"queueName"` , call `.queue()` with array of functions `fns` as parameter
$(obj).queue("status", fns)
.dequeue("status")
// do stuff when all `"queueName"` functions called
.promise("status")
.then(function() {
  console.log("complete", this[0].status)
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
guest271314
  • 1
  • 15
  • 104
  • 177
  • So after looking at the source I don't think I can go with a queue because the whole reason for using promises was to avoid an unlimited stack frame expansion. The only way these queues get around it is because they make calls to setTimeout which is triggered after the stack frame is cleared from my understanding. So if you write asynchronous code in the fns, it'll expand the stack with every next call. Is there any way around this other than a promise type architecture? – fritz Oct 08 '15 at 14:56
  • @fritz Not certain interpret Question correctly ? _"But, my question is, can the promise be changed implicitly within the then statement or do you have to use code like the following (which is the only way I can think to do it)?"_ What is expected result ? Promise cannot be "changed implicitly" . `.then()` returns new jQuery promise object at jQuery versions 1.8+ . Using `setTimeout` does not implicitly return a value , or promise . Would require creating new deferred object to return value after `setTimeout`, or using `.delay()` , `.promise()` to return a jQuery object to a chained `.then()` – guest271314 Oct 09 '15 at 01:54
  • I appologize for the misunderstanding I should have been more clear. When I said "changes implicitly" what I meant was generate a new promise /deferrer for each `.then()` that is then independently resolved apart from the original promise. Thus, allowing chaining of promises that aren't all resolved at once, unlike chaining `.then()`'s – fritz Oct 09 '15 at 13:50
  • @fritz _"generate a new promise /deferrer for each .then() that is then independently resolved apart from the original promise"_ A new `$.Deferred` or jQuery promise object would need to be returned from within `.then()` , e.g., `d` within first `.then()` following `$.when(asyncEvent())` – guest271314 Oct 09 '15 at 14:02
0

Well you shouldn't have access to the promise returned by .then however as you've already seen you can create a new promise inside this .then and have the callbacks attached in the next .then get executed when this new promise is resolved, I see in the implementation written in the stackoverflow post attached that a promise is always resolved by another entity called the Defer, actually you don't need to create Defer instances since a promise can resolve itself, if you see the documentation for Promises on MDN you'll see that the constructor receives an executor function used to resolve/reject the promise itself, so you can use the following:

new Promise(function (resolve, reject) {
  // do something async and then reject/resolve itself
  setTimeout(function () {
    resolve()
  }, 1000)
})

Your example can be written using this constructor as follows:

new Promise(function (resolve) {
  resolve('start')
})
  .then(function (status) {
    return new Promise(function (resolve, reject) {
      console.log(status)
      // do something async and then reject/resolve itself
      setTimeout(function () {
        console.log(1)
        resolve()
      }, 1000)
    })
  })
  .then(function () {
    setTimeout(function () {
      console.log(2)        
    }, 1000)
  })

The thing to remember is that you can't change the state of a promise after it was fulfilled/rejected and if you want to chain functions based on the content of the .then make sure to return a promise so that if there's a .then attached to the original promise it get executed when the promise created in the .then gets fulfilled/rejected

FYI the above is a shortcut for:

  new Promise (function (resolve) {
    resolve('start')
  })
    .then(function (status) {
      return new Promise (function (resolve, reject) {
        console.log(status)
        setTimeout(function () {
          console.log(1)
          resolve()
        }, 1000)
      })
        .then(function () {
          setTimeout(function () {
            console.log(2)
          }, 1000)
        })
    })
Mauricio Poppe
  • 4,817
  • 1
  • 22
  • 30
  • Is the only way then to chain promises like this? And if a promise is always resolved by a deferrer is there a defferer made implicitly for the call to new promise? – fritz Oct 08 '15 at 14:59
  • Nope, promises are not always resolved by a deferrer neither a deferrer is made implicitly for the call, promises exist on their own. A deferrer is just a wrapper for a promise, see [that the wrapper](https://github.com/petkaantonov/bluebird/blob/master/src/promise.js#L184-L187) creates a promise, and when you call `defer.resolve()`, `defer.reject()` the defer actually calls methods from the promise itself, [it doesn't modify the state of the promise inside the code that defines the wrapper](https://github.com/petkaantonov/bluebird/blob/master/src/promise_resolver.js#L104-L124) – Mauricio Poppe Oct 08 '15 at 16:00
  • so a defferer really isn't necessary in the promise architecture? – fritz Oct 08 '15 at 16:57
0

Probably this is what your are looking for

        function asyncEvent() {
            var dfd = jQuery.Deferred();

            setTimeout(function () {
                console.log('start');
                dfd.resolve("start");
            }, 1000)
            return dfd.promise();
        }


        var chainSyncOrAsync = function (outerDef) {
            var toReturn = {};
            toReturn.chain = function (defOrFunction) {
    //            console.log(outerDef, defOrFunction, '---');
                var def = defOrFunction, toExecute = false;
                if (typeof def === 'function') {
                    toExecute = true;
                    def = jQuery.Deferred();
                }
                outerDef.then(function () {
    //                console.log(toExecute, defOrFunction);
                    if (toExecute) {
                        var result = defOrFunction();
                        if (result) {
                            result.then(function () {
                                def.resolve();
                            })
                        } else {
                            def.resolve(result)
                        }

                    }
                })
                return chainSyncOrAsync(def);
            }
            return toReturn;
        }

        chainSyncOrAsync(asyncEvent())
                .chain(function () {
                    console.log(1)
                })

                .chain(function () {
                    console.log(2)
                })
                .chain(function () {
                    return asyncEvent();
                })

                .chain(function () {
                    console.log(3)
                })
                .chain(function () {
                    console.log(4)
                })
Ravi Hamsa
  • 4,721
  • 3
  • 14
  • 14
  • I'm not sure, but I just ran this code, and the timeouts are triggered at the same time roughly instead of sequentially. I believe you have an error where you call defOrFunction(). You get into a race condition where if the function doesn't return resolve can be called first. – fritz Oct 08 '15 at 14:58
  • Did you really run the code? To see the difference in timeouts try logging time with every console.log. This is how my console looks like start Fri Oct 09 2015 12:23:50 GMT+0530 (IST) 1 Fri Oct 09 2015 12:23:50 GMT+0530 (IST) 2 Fri Oct 09 2015 12:23:50 GMT+0530 (IST) start Fri Oct 09 2015 12:23:51 GMT+0530 (IST) 3 Fri Oct 09 2015 12:23:51 GMT+0530 (IST) 4 Fri Oct 09 2015 12:23:51 GMT+0530 (IST) – Ravi Hamsa Oct 09 '15 at 06:54
  • Yes, the problem is that 1 and 2 happen at the same time (12:23:50 in your example) I wanted to chain them so they will happen consecutively (like 1 at 12:23:50 then 2 at 12:23:51). The only reason the second start is not run at 12:23:50 is because it creates it a new promise and resolves in the function itself. I was trying to get away from this approach. – fritz Oct 09 '15 at 13:45