Skip to content

Instantly share code, notes, and snippets.

@ThomasBurleson
Last active August 29, 2015 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThomasBurleson/6681bf595539a98fb5ea to your computer and use it in GitHub Desktop.
Save ThomasBurleson/6681bf595539a98fb5ea to your computer and use it in GitHub Desktop.
Exercise 35: Traditional Nested Async calls before rewritten with Promises or Observables
function(window, $, showMovieLists, showError) {
var error,
configDone,
movieLists,
queueList,
windowLoaded,
outputDisplayed,
errorHandler = function() {
// Otherwise show the error.
error = "There was a connectivity error";
// We may be ready to display out
tryToDisplayOutput();
},
tryToDisplayOutput = function() {
if (outputDisplayed) {
return;
}
if (windowLoaded) {
if (configDone && movieLists !== undefined) {
if (queueList !== undefined) {
movieLists.push(queueList);
}
outputDisplayed = true;
showMovieLists(JSON.stringify(movieLists));
}
else if (error) {
outputDisplayed = true;
showError(error);
}
}
},
windowLoadHandler = function() {
windowLoaded = true;
// Remember to unsubscribe from events
window.removeEventListener("load", windowLoadHandler);
// This may be the last task we're waiting on, so try and display output.
tryToDisplayOutput();
};
// Register for the load event
window.addEventListener("load", windowLoadHandler);
// Request the service url prefix for the users AB test
$.getJSON(
"http://api-global.netflix.com/abTestInformation",
{
success: function(abTestInformation) {
// Request the member's config information to determine whether their instant
// queue should be displayed.
$.getJSON(
"http://api-global.netflix.com/" + abTestInformation.urlPrefix + "/config",
{
success: function(config) {
// Parallel retrieval of movie list could've failed,
// in which case we don't want to issue this call.
if (!error) {
// If we're supposed to
if (config.showInstantQueue) {
$.getJSON(
"http://api-global.netflix.com/" + abTestInformation.urlPrefix + "/queue",
{
success: function(queueMessage) {
queueList = queueMessage.list;
configDone = true;
tryToDisplayOutput();
},
error: errorHandler
});
}
else {
configDone = true;
tryToDisplayOutput();
}
}
},
error: errorHandler
});
// Retrieve the movie list
$.getJSON(
"http://api-global.netflix.com/" + abTestInformation.urlPrefix + "/movieLists",
{
success: function(movieListMessage) {
movieLists = movieListMessage.list;
tryToDisplayOutput();
},
error: errorHandler
});
},
error: errorHandler
});
}
@ThomasBurleson
Copy link
Author

The above is truly horrible nesting of parallel and dependent async calls; notice the use of local vars to store data between async calls.

First let's extract our custom NetFlix DataService:

function NetFlixService($http, supplant) {
 var urlPrefix  = "";
     urlABInfo  = "http://api-global.netflix.com/abTestInformation",
     urlABQueue = "http://api-global.netflix.com/{urlPrefix}/queue",
     urlConfig  = "http://api-global.netflix.com/{urlPrefix}/config",
     urlMovies  = "http://api-global.netflix.com/{urlPrefix}/movieLists",     
     extractPrefix = function(info) { urlPrefix = info.urlPrefix;  return info; };

  // Service API
  return {
    loadABInfo : function()  { return $http.get(urlABInfo).then( extractPrefix );    },
    loadMovies : function()  { return $http.get(supplant(urlMovies,urlPrefix));      },
    loadConfig : function()  { return $http.get(supplant(urlConfig,urlPrefix));      },
    loadQueue  : function()  { return $http.get(supplant(urlABQueueInfo,urlPrefix)); }
  };
}

Now we can greatly improve the original code with the use of Promises:

function(window, $q, netFlix, showMovieLists, showError) {

  var reportError = function() {
        onWinReady.then(function(){
          showError("There was a connectivity error");
        });
      },
      tryToDisplayOutput = function(movies) {
        onWinReady.then(function(){
          showMovieList(JSON.stringify(movieLists));
        });
      },
      onWinReady = $q(function(success,error){
        var onLoad = function() {
            window.removeEventListener("load", onLoad);
            success(true);
          };
        window.addEventListener('load', onLoad);
      }),
      extractWith = function(movies) {  
        // Partial application to capture the movies while extracting the Queue
        return function extractQueue(queueResponse) {
          var queue = queueResponse.list;
          movies && movies.push(queue);            
          return $q.when(movies || queue);
        };
      });

  // Load Endpoint, then parallel load movies and configuration info
  // If configured load Queue, then display output when the Window is ready...

  netFlix.loadABInfo()
    .then( function(){
      return $q.all([ netFlix.loadMovies(), netFlix.loadConfig() ]);
    })
    .then( $q.spread( function( response, config ) {
      var movies = response.list;
      var extractQueue = extractWith(movies); 

      return !config.showInstantQueue ? $q.when(movies) :
              netFlix.loadQueue().then(extractQueue);
    }))
    .then( tryToDisplayOutput )
    .catch( reportError );

}

This is declarative-style that is significantly easier to read and maintain.

@ThomasBurleson
Copy link
Author

Since I only want to call this batch of code one (1) time, the Promise solution here is very suitable. For indeterminant streams, then Observables are the way to go...

@ThomasBurleson
Copy link
Author

Now let's try to implement this with ES6 Generators:

Thanks to @rschmukler for his help with these ideas. Just a hint of pending approach

   try { 
    yield netFlix.loadAbInfo();
    var [allMovies, config] = yield [netflix.loadMovies(), netflix.loadConfig()];
    var output = !config.showInstantQueue ? allMovies.list : 
                 (yield netflix.loadQueue()).map(extractWith(allMovies.list));

     tryToDisplayOutput(output);

   } catche(e) {
    reportError(e);
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment