How to delay a promise in when function

11,731

Solution 1

How can I delay a chain of promises?

$timeout returns a promise. Return that promise to chain it.

$scope.openView = function (viewId) {
    // Check if view is already open
    if ($scope.viewId == viewId) {
        //chain right away with empty promise
        return $q.when();
    };

    //otherwise if view is not already open

    var p = MyService.getData(viewId).then(function(data) {
             // add data to view
             // change class to open view
             // this is working ok!
    });

    var pDelayed = p.then (function () {
         //return to chain delay
         return $timeout(angular.noop, 30000);
         });

    //return delayed promise for chaining
    return pDelayed;
};

$scope.openView(viewId).then(function() {
     //use chained promise            
     $scope.openAnotherView(anotherViewId);
});

Solution 2

To delay a promise, simply call the resolve function after a wait time.

new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve();
  }, 3000); // Wait 3s then resolve.
});

The issue with your code is that you are returning a Promise and then inside the then of that Promise you are creating another one and expecting the original promise to wait for it - I'm afraid that's not how promises work. You would have to do all your waiting inside the promise function and then call resolve:

Edit: This is not true, you can delay the promise chain in any then:

function promise1() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('promise1');
      resolve();
    }, 1000);
  })
  .then(promise2);
}

function promise2() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('promise2');
      resolve();
    }, 1000);
  });
}

function promise3() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('promise3');
      resolve();
    }, 1000);
  });
}

promise1()
  .then(promise3)
  .then(() => {
    console.log('...finished');
  })

However that is not a good way to wait for a css animation. It's better to listen to the transitionend event:

element.addEventListener('transitionend', onTransitionEnd);
element.classList.add('transition-me');

Note if you're using an animation instead of a transition the same concept applies but use the animationend event.

Solution 3

Each then accepts a function which should return a Promise. It does not accept an instance of Promise. You want to return the call to timeout:

return MyService
    .getData(viewId)
    .then(function(data) {
        // ...
    })
    .then(function () {
        return timeout(3000);
    });

Alternatively, have timeout return a function instead of a Promise:

 function timeout(delay) {
    return function () {
        return new Promise(function(resolve, reject) {
            //                      ^^^^^^^ (misspelt in your example)
            $timeout(resolve, delay);
        });
    };
}

And then you can use it as in your example:

return MyService
    .getData(viewId)
    .then(function(data) {
        // ...
    })
    .then(timeout(3000));
Share:
11,731

Related videos on Youtube

11mb
Author by

11mb

Fulltime webmagician, fools around web developing for a living

Updated on June 19, 2022

Comments

  • 11mb
    11mb almost 2 years

    How can I delay a chain of promises? I need this because I want to wait for a CSS animation to complete before going on with the script.

    The purpose of the function is to open a view. If the view is not already open, then open it (by changing a class), wait for the css animation, go on. If the view is already open, do nothing and go on.

    I want to call the function like this: (It is a function within an angular controller)

    $scope.openView(viewId).then(function() {              
         $scope.openAnotherView(anotherViewId);
    });
    
    
    /** Function to open some view **/
    $scope.openView = function (viewId) {
        function timeout(delay) {
            return new Promise(function(resolve, reject) {
                $timeout(resolve, delay);
            });
        }
    
        // Check if view is already open
        if ($scope.viewId != viewId) {
             $scope.viewId = viewId;             
    
             // get data from ajaxcall (also a promise)
             return MyService.getData(viewId).then(function(data) {
                 // add data to view
                 // change class to open view
                 // this is working ok!
             }).then(function() { 
                 return timeout(30000 /* some large number (testing purpose) */ )
             });
        } else {
             // view is already open, so return here we don't have to wait
             // return empty promise, with no timeout
             return new Promise(function(resolve, reject) {
                 resolve()
             });     
        }
    }
    

    This code works, but the delay is not working. Is my approach ok? What am I missing here?


    Edit 1: improved the code with the suggestion from @sdgluck


    Edit 2: Some clarification of the main question:

    To clarify the main question a bit more: Can I use this construction in my code?

    // code doesnt know wheter to wait or not
    // can the Promise do this?
    openView().then(function() {              
         openAnotherView();
    }
    

    Outcome 1:

    the browser will call openView() but since it is already open it will just call openAnotherView() right away (no delay).

    Outcome 2 :

    The view is not open, so openView() will open it, then a delay (or as @Dominic Tobias points out, add an eventlister?) then call openAnotherView() after some delay.

    Thanks for any help!

    Edit 3: Added a fiddle with the problem explained http://jsfiddle.net/C3TVg/60/

    • Mike Lawson
      Mike Lawson over 8 years
      I think you may be going about this the wrong way. It sounds like you might want to detect if the animation is completed like this instead.
  • 11mb
    11mb over 8 years
    Thx for identifying my mistake with the timout function and clearing up how to resolve it. But I think that there is another issue with my code like @Dominic Tobias is pointing out.
  • sdgluck
    sdgluck over 8 years
    @11mb No problem. And yes, the other answer is more comprehensive. :-)
  • 11mb
    11mb over 8 years
    Thx for your answer, I just updated my question. I tried to clarify the fact that I don't now on beforehand if I have to wait.
  • georgeawg
    georgeawg over 8 years
    Updated to add conditional delay.

Related