Saturday, September 1, 2012

Async ForEach–The basic three levels of async loops

 

Hi All,

Today I am going to continue my series about async. This time I will talk about loops or more specifically about for each loops over a collection. I present async for each loops by through three examples where each example adds the asynchronous part to a different section of the loop.

Non-Async loop

This is the most basic type of loop and is the same loop that is provided by all sorts of frameworks (for example underscoreJS has a forEach method). In this case it is up to the user to make the code asynchronus and the loop function itself does not provide any facilities for it.

Non async loop
Code Snippet
  1. App.eachSync = function (collection, iterator, context) {
  2.     var token = {
  3.         canceled: false
  4.     };
  5.     for (var i = 0, l = collection.length; i < l; i++) {
  6.         iterator.call(context, collection[i], token);
  7.         if (token.canceled) {
  8.             break;
  9.         }
  10.  
  11.     }
  12. };
(for brevity I have omitted all the error handling code)
The loop function takes three arguments:

  • collection – The collection to iterate over.
  • iterator – The function that will be called for each iteration.
  • context – The pointer to “this” operator within the iterator function.

The iterator takes two arguments


  • The item of the current iteration
  • A token which can be used to transfer data between the iteration (in this example used for breaking out of the loop).

If the user wants to run something asynchronously then she must add the asynchronous code calls within the iterator method.


Async-Itarators


The next obvious step from the previous version is that the loop calls the iterator asynchronously. In the following code the loop runs synchronously but schedules each call to the iterator to run asynchronously. In this case, if the collection contains many items, the loop itself may make your thread stuck.


A loop that calls the iterator asynchronously
Code Snippet



  1. App.eachAsync1 = function (collection, iterator, context) {
  2.     var token = {
  3.         canceled: false
  4.     };
  5.     for (var i = 0, l = collection.length; i < l; i++) {
  6.         setTimeout(function (index) {
  7.             if (!token.canceled) {
  8.                 iterator.call(context, collection[index], token);
  9.             }
  10.         }, 0, i);
  11.     }
  12. };

Async-Loop


Another approach to the async loop problem would be to make the loop itself asynchronous. To do this we need to save the current iteration nu,ber within the scope and call an intermediate method iteratively. The intermediate method will schedule itself and call the iterator. The code would look something like this: 


Both the loop and the iterator calls are async
Code Snippet



  1. App.eachAsync2 = function (collection, iterator, context) {
  2.     var token = {
  3.         canceled: false
  4.     },
  5.     totalLength = collection.length,
  6.     innerFunction = function (index) {
  7.         setTimeout(function (innerIndex) {
  8.             if (!token.canceled) {
  9.                 iterator.call(context, collection[innerIndex], token);
  10.             }
  11.         }, 0, index);
  12.         index++;
  13.         if (index < totalLength) {
  14.             setTimeout(innerFunction, 0, index);
  15.         }
  16.     }
  17.  
  18.     innerFunction(0);
  19. };

At first, the innerFunction is called with the index 0. Next, it schedules the iterator call and itself until we reach the end of the collection. Note that it is possible to run the iterator directly and not schedule it.


Conclusion


As always with async we gain non blocking code on the expense of time. The code runs slower but in chunks, each chunk block the thread for a short while but allows other things to run in the thread in between the chunks. I ran the three loops on a code that prints the numbers 1 to 1000 to the console. As expected the performance degradated noticeably from one method to the next.


To quote Isaac Z. Schlueter (and I hope I am quoting right) – Async is something that you do not when you want to make things go faster but when you can’t make things go fast enough.


 


Thank you for reading,


Boris.