Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Promise chaining when using $timeout

I'm trying to understand the promise API and chaining, particularly the timing when $timeoutis used with .then(). What I had expected from the following is that since $timeout returns a promise, .then() would not be called until it had resolved.

But instead of ABAB, it's ABBA all the time.

How can I use the promise API to ensure that a long-running call (or delayed call using $timeout) is actually complete before the .then() gets executed?

Code

angular
  .module('app', [])
  .controller('ThenCtrl', ThenCtrl);

function ThenCtrl($timeout, $q) {
  var vm = this;

  vm.items = [];

  $q.when(pushA()).then(pushB());

  $timeout(pushA, 5000).then(pushB());

  function pushA() {
    vm.items.push('A');
  }

  function pushB() {
    vm.items.push('B');
  }
}

Markup

<div ng-app="app">
  <div ng-controller="ThenCtrl as vm">
    {{vm.items}}
  </div>
</div>

I've set up a fiddle: https://jsfiddle.net/kan3c61t/

like image 732
twip Avatar asked Mar 08 '16 04:03

twip


3 Answers

Here you go. What I did is essentially added a success function in the then part of the code.

$timeout(pushA, 5000).then(function(success) {
    pushB()
  });

Here is the working demo.

You can also add an error function like this

 $timeout(pushA, 5000).then(function(success) {
    pushB()
  },function(error){console.log("Error");});

While searching for this answer, I also came across this very helpful link

like image 37
Satej S Avatar answered Nov 13 '22 09:11

Satej S


Don't invoke the functions inside the .then methods.

  ̶$̶q̶.̶w̶h̶e̶n̶(̶p̶u̶s̶h̶A̶(̶)̶)̶.̶t̶h̶e̶n̶(̶p̶u̶s̶h̶B̶(̶)̶)̶;̶
  $q.when(pushA()).then(pushB);

  ̶$̶t̶i̶m̶e̶o̶u̶t̶(̶p̶u̶s̶h̶A̶,̶ ̶5̶0̶0̶0̶)̶.̶t̶h̶e̶n̶(̶p̶u̶s̶h̶B̶(̶)̶)̶;̶    
  $timeout(pushA, 5000).then(pushB);

Instead pass the functions as arguments to the .then method. The $q service will hold those functions to be invoked later.

The way the $q service works is it stores the argument of the .then method as a function to be invoked later. In this case, the $q service was storing the value returned by pushB() with the side effect of pushing B immediately onto the array.

The DEMO on JSFiddle

like image 152
georgeawg Avatar answered Nov 13 '22 10:11

georgeawg


As others have mentioned - your bigger issue is that you .then(promise) and not .then(function).

Promises represent a value + time. It's the result of an already started operation. A promise is a value - then waits for functions. You can't "run a promise after another promise" - since a promise means the operation has already started.

When you then(x) for anything other than a function it is ignored. This is an unfortunate choice in the promises spec but we have to live with it.

Since your calls are synchronous you should not use promises for it. If your code does something synchronous you can sequence actions with ; and not then:

pushA();
pushB(); 

If it's a call that returns promises then it just becomes:

pushA().then(pushB);

There is no point in calling $q.when which converts non-promises to promises.

I'd write it as:

pushA();
$timeout(5000).then(pushB); 

There is no point to convert the first synchronous action to a promise returning function or to involve promises anywhere except the timeout. If you need pushA to happen after 5000ms itself I'd still probably write:

$timeout(5000).then(pushA).then(pushB)

Since I think it is more readable and again we don't involve pushA and pushB with promises directly.

like image 24
Benjamin Gruenbaum Avatar answered Nov 13 '22 09:11

Benjamin Gruenbaum