admin管理员组

文章数量:1208153

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 /

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/

Share Improve this question edited Jan 7, 2016 at 12:57 11mb asked Jan 7, 2016 at 10:42 11mb11mb 1,3592 gold badges17 silver badges33 bronze badges 1
  • 1 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. – Mike Lawson Commented Jan 7, 2016 at 11:24
Add a comment  | 

3 Answers 3

Reset to default 13

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.

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));

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);
});

本文标签: javascriptHow to delay a promise in when functionStack Overflow