Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to delay a promise in when function

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/

like image 550
11mb Avatar asked Jan 07 '16 10:01

11mb


People also ask

How do you add a delay to a function?

To delay a function call, use setTimeout() function. functionname − The function name for the function to be executed. milliseconds − The number of milliseconds. arg1, arg2, arg3 − These are the arguments passed to the function.

How do I delay a fetch request?

To avoid too many requests, you can fetch data in delay with await and loop and update data using setTimeout() .

Is setTimeout a promise function?

setTimeout() is not exactly a perfect tool for the job, but it's easy enough to wrap it into a promise: const awaitTimeout = delay => new Promise(resolve => setTimeout(resolve, delay)); awaitTimeout(300). then(() => console.


2 Answers

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);
});
like image 198
georgeawg Avatar answered Oct 05 '22 23:10

georgeawg


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.

like image 33
Dominic Avatar answered Oct 06 '22 01:10

Dominic