Promise chaining when using $timeout
Asked Answered
U

3

16

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/

Ure answered 8/3, 2016 at 4:9 Comment(0)
R
15

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

Rugg answered 8/3, 2016 at 5:25 Comment(6)
This is a pretty interesting solution too.Foremast
Very clearly stated. What a difference those parenthetical pairs can make.Ure
This helped me tremendouslyHowling
What about functions with multiple arguments?Izzard
Clear. But I need to call a function function1 and then call another function function2 once the function1 is resolved because I need the result of the function1. But function2 also has some other additional arguments that are available in the .js file.... Probably I need to also send these values to function1 and make function1 return a result which has these variables as well which can then be used in function2?Izzard
@Izzard Please ask that as a new question. The comment section should only be used to clarify the existing anwser. Lengthy discussions are not appropriate here. Also look at AngularJS execution order with $q — Chaining PromisesRugg
F
6

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

Foremast answered 8/3, 2016 at 4:35 Comment(1)
This is an excellent reminder of the underlying API structure; thank you.Ure
S
5

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.

Shardashare answered 8/3, 2016 at 10:41 Comment(1)
Thank you for the response. It's sometimes very difficult to capture intent well while being both brief and clear for SO posts. What I was trying to capture is a scenario where we intend A to be resolved before B, and A is the promise that must carry the delay. Meaning: I would like pushA to complete it's work, including the delay, and then-and-only-then execute pushB. Here's an updated fiddle, implemented with your input, which helped me arrive at what I was looking for: jsfiddle.net/nam3cbaw/1Ure

© 2022 - 2024 — McMap. All rights reserved.