What are Asynchronous Functions?

An asynchronous function is a function whose completion depends on the actions of another object. The program in which it runs does not wait for the object to return a result, rather, that object ?pushes? the result when it?s finished. This push comes through the callback function ? a function defined specifically for the purpose of receiving a result, and that object calls the function with arguments it supplies. The signature of the callback function contains these arguments, and uses them in order to complete the procedure.

For example: asynchronous use of XMLHTTP. Upon sending the request to the server, the other scripts keep running while the page continues functioning. The XMLHTTP object has an event named onreadystatechange which is called each time the server returns an answer. This is unlike the synchronous case, where the browser waits for a response from the server. The window ?freezes? and only comes back to life when the response is received. This behavior is simply not Internet-correct, and not user-friendly.

Usually when we have an asynchronous function, we?d like another function to run upon its completion. We?ll put the call to the second function in the callback of the first one.

If we have a large number of functions and callback, they start to depend on each other: when A terminates call B, when B terminates call C, and so on. If I pull B out of the sequence, I?ve broken the chain, because C will not be called.

Case of Asynchronous Functions in Sequence

As an example, we?ll look at this sequence:

  1. Performing a semi-transparency effect on an element to be displayed, and once complete:
  2. A button appears in the element, when clicked:
  3. An XMLHTTP request is sent to the server, and when a response is received:
  4. A message is displayed, alerting that the process has finished.

The division into functions is quite simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function effectElement() {
  var div=document.getElementById("div");
  var opacity=0;
  var iv=setInterval(function () {
      opacity+=.05;
      div.style.opacity=opacity;
      if (opacity>=1) {
          clearTimeout(iv);
          showButton();
      }
  },50);
}
function showButton() {
  var button=document.getElementById("button");
  button.style.display="";
  button.addEventListener("click",function () {
      requestServer();
  },false);
}
function requestServer() {
  var xh=new XMLHttpRequest();
  xh.open("GET","url.htm",true); // true - async call
  xh.onreadystatechange=function () {
      if (xh.readyState==4) {
          div.innerHTML=xh.responseText;
          finish();
      }
  };
  xh.send(null);
}
function finish() {
  alert("finish");
}
window.addEventListener("load",function () {
  effectElement();
},false);

We see here that we can call the next function within each function?s callback. But what would happen if we were to remove one of the functions from the sequence? We?d have to change the function containing the call to the removed function, to call another function. And what would happen if we wanted to change the order? A mess.

Generic Solution for a Sequence of Asynchronous Functions

I?ve tried to think of a generic solution to the problem, a solution which would let me decide which function comes after which with minimum code and maximum flexibility, and where each function calls the next without being dependent on it.

This is the basic syntax:

1
2
3
4
5
6
7
8
var sequence=new Devign.Sequence();

sequence.add(effectElement);
sequence.add(showButton);
sequence.add(requestServer);
sequence.add(finish);

sequence.start();

Within each callback, the current sequence should be advanced by:

1
currSequence.next();

where currSequence is passed with the function as a single argument.

Simple, isn?t it?

We can play with the order of the functions, add and remove as much as we want.

Devign.Sequence Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Devign.Sequence class
if (typeof(Devign)=="undefined") var Devign={};

Devign.Sequence=function () {
  // private fields
  this.list=[]; // a list of functions
  this.index=-1;
  this.aborted=false;

  // public fields
  this.finished=false;
};

// public methods
Devign.Sequence.prototype={
  // adds a new function
  add:function (sequenceFunction) {
      this.list.push(sequenceFunction);
  },
  // starts the sequence from the first function
  // fires 'onStart' if exists
  start:function () {
      this.index=-1;
      this.aborted=false;
      this.next();
      if (typeof(this.onStart)=="function") this.onStart();
  },
  // ends the sequence
  // fires 'onEnd' if exists
  end:function () {
      if (typeof(this.onEnd)=="function") this.onEnd();
      this.finished=true;
  },
  // proceeds the sequence
  // if the sequence has finished calls 'end'
  next:function () {
      // if sequence was aborted - ignore next statements
      if (this.aborted) return;

      this.index++;

      if (this.index==this.list.length) return this.end();

      var currFunction=this.list[this.index];

      // calls the function with the sequence as an argument
      if (currFunction) currFunction(this);
  },
  // aborts the sequence by setting the index to 'not started' and the flag aborted to true
  abort:function () {
      this.index=-1;
      this.aborted=true;
  }
};

Each instance of the class has an array of functions, to which we can add more functions with the method add. Furthermore, there is numerical field named index which tracks the index of the function currently being executed (with -1 meaning the sequence has not yet begun). For each instance we can also define two events: onStart and onEnd. If defined, they will run at the appropriate time. Each time we move forward in the sequence, the index is incremented and the next function is called from the array. The function is called with the parameter this which refers to the instance of the class. The reference to the class is sent so that it may be identified from within the function, and advanced with next.

Complete Code

“`javascript
“`

[iframe: style=“width: 100%; height: 600px” src=“http://jsfiddle.net/wXQ4N/embedded/”]

Comments