angular js, broadcast an event and wait for it to complete

Problem

I have a an angular event like this:

$rootScope.$broadcast("postData");
doSomething();

however, doSomething() must wait for postData to complete before execution. I would normally do something like:

$rootScope.$broadcast("postData").then(function(){
    doSomething();
});

But apparently this isn't a thing in angular...Any ideas?

Problem courtesy of: user3689167

Solution

You could $broadcast the event, listen for it in your other controller with $on, and $emit another event on completion, and listen for it in your original controller so you know when it is finished.

I would not recommend this approach. Instead use a service.

Emit and broadcast are coupling your mechanisms for communication to the view because the $scope is fundamentally a fabric for data-binding.

The services approach is far more maintainable and can communicate between services in addition to controllers.

Solution courtesy of: Tristan

Discussion

I would like to point-out that the previous solutions are not possible to be implemented where we don't have a handle on the async call to put a callback/promise/throw an event to solve the issue. The async call may be library function like for example setTimeout and we just cant use the previous solutions to fix the flow.

Here's my solution:

Put doSomething(); in an setTimeout with the time interval set to 0,

$rootScope.$broadcast("postData");
setTimeout(function(){ 
    doSomething();}
, 0);

As simple as that!

settimeout makes dosomething() also to be asynchronous and this makes both the asynchronous operations happen one after the other(asynchronously). How? The explanation follows, but first note that the dosomething() is in a setTimout of interval 0 ms. One may obviously think that dosomething() shall be executed right-away (after 0 ms (actually default minimum time interval in javascript is 4 ms, so 0 ms becomes 4 ms)) before the postData event is broadcast-ed and serviced.

The answer is no!

Settimeout doesn't guarantee that the callback function passe inside it shall surely get executed after the specified interval. The specified interval is just the minimum interval needed after which the callback can be executed. SetTimeOut is an asynchronous call. if there are any other async operation waiting in the pipeline already, javascript runs them first.

For understanding how all this happen you need to understand what is an event loop in javascript.

Javascript runtime is single threaded, it just have one call stack, meaning it runs the code sequentially as it is written. Then how on earth does it implement asyncronicity?

So this is what happens under the hood when the javascript run-time encounters an operation that is asyncronous (like an API call, http call, settimeout, event broadcast etc). Please note these functions are not provided in our native javascipt run time engine (for example chromes V8 engine), instead they are provided by the browser (known as webAPIs), which are basically threads that you can make call to and they fork off an independent path of execution, separate from the javascript run-time execution flow, and thats how concurrency is actually achieved.

The question arises that Javascript run time is still single threaded. So how does these webAPI inturrpt the runtime flow and provide their results when they are complete? They can not just prompt the javascript runtime anytime when they are finished and provide their results to it? There must be some mechanism.

So javascript just makes the call to these webAPI's and does not wait for the output of the call. It simply goes on and execute the code that follows the call, and thats how the dosomething() in the problem, gets executed before the postDate event is listened and served).

Meanwhile the forked thread processes the http call or the setTimeout or handle the event etc, whatever the async call was made for. And when its done, the callback is pushed onto a event queue (task queue) (Note that multiple callback returns can be pushed into this queue.). But they are not run right away.

The javascript runtime waits for the call stack to get empty first. When there is nothing for the javascript runtime to execute the async calls callback function are popped out from the task queue, one by one and executed.

So in essence if we can just make dosomething() async, it will get executed after the 1st async is completed. Thats what I did. The settimeout callback gets pushed onto the event queue/task queue. Javascript call stack gets empty. The call back for postData event broadcast gets served. Then dosomething() gets a chance to execute.

Discussion courtesy of: Pradeep Jain

This isn't how events works, you can't wait for events to complete.

Why won't you fire 'postData', let the consumers of this event do whatever they does, and then wait for another event and execute 'doSomething' once you receive it?

This way, once the consumer of 'postData' finish processing the event, he can fire another event which you can consume and execute your 'doSomething' when you receive it.

Discussion courtesy of: AdirAmsalem

Im assuming the broadcast of 'postData' is defining the end of a funciton.

If you use the $q angular service this can be accomplished easily by creating asynchronous functions.

function postData() {
  var deferred = $q.defer();

  //Do your asynchronous work here that post data does

  //When the asynchronous work is done you can just resolve the defer or
  //you can return data with resolve. Passing the data you want
  //to return as a param of resolve()
  deferred.resolve();

  //return
  return deferred.promise;
}

When you call postData now you can now use the then method to run doSomething() after postData() is done.

 postData().then(function(data) {
    doSomething();
  }, function (err){
     //if your ansyncronous function return defer.reject() instead of defer.resolve() you can catch the error here
  };

Heres the angular documentation for $q

Heres a plunk to show you a simple example

Discussion courtesy of: jkerb

This recipe can be found in it's original form on Stack Over Flow.