Create a gist now

Instantly share code, notes, and snippets.

Synchronous asynchronous JavaScript - Nicer callbacks
<html>
<head>
<style>
body {
background: #f5f8fa;
font: 16px/22px Arial, sans-serif;
margin: 0;
}
.statList {
background: #fff;
border-bottom: 1px solid #e1e8ed;
height: 60px;
overflow: hidden;
}
.statList__item {
float: left;
width: 75px;
height: 60px;
display: table;
}
.status {
display: table-cell;
text-align: center;
text-transform: uppercase;
vertical-align: middle;
}
.title {
color: #66757f;
font-size: 11px;
letter-spacing: .02em;
text-transform: uppercase;
}
.count {
color: #C29A34;
font-size: 18px;
font-weight: 500;
padding-top: 3px;
}
.tweetFeed {
margin: 10px;
}
.tweet {
background: #fff;
border: 1px solid #e1e8ed;
padding: 15px;
}
.tweet:nth-child(1){
border-radius: 5px 5px 0 0;
}
.tweet:nth-child(n+2){
border-top: none;
}
.tweet__name{
color: #292f33;
display: inline-block;
font-size: 14px;
font-weight: bold;
}
.tweet__handle {
color: #8899a6;
display: inline-block;
font-size: 13px;
margin-left: 10px;
}
.tweet__message {
margin-top: 5px;
}
</style>
</head>
<body>
<div class="statList">
<div class="statList__item">
<div class="status">
<div class="title">Tweets</div>
<div class="count totalTweets"></div>
</div>
</div>
<div class="statList__item">
<div class="status">
<div class="title">Photos</div>
<div class="count totalPhotos"></div>
</div>
</div>
<div class="statList__item">
<div class="status">
<div class="title">Favourites</div>
<div class="count totalFavourites"></div>
</div>
</div>
</div>
<div class="tweetFeed">
<div class="timeLine"></div>
</div>
<script type="text/javascript">
var timeLine = document.querySelector('.timeLine');
var totalTweets = [];
/**
* get
*
* Tiny utility for making XHR requests
* @param {String} url
* @param {Function} callback [description]
*/
function get(url, callback, errback) {
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.onreadystatechange = function() {
if(req.readyState != 4) return;
if (req.status === 200) {
callback(req);
}
else {
errback(req);
}
};
req.send(null);
}
/**
* Asynch Requests
*/
// our first async req
function get1stTweet(){
get('https://api.myjson.com/bins/2qjdn',
function(data){
totalTweets.push(data.response);
next();
},
function(err){
onError(err);
}
);
}
// our second async req wrapped in a 2 second delay
function get2ndTweet(){
setTimeout(function(){
get('https://api.myjson.com/bins/3zjqz',
function(data){
totalTweets.push(data.response);
next();
},
function(err){
onError(err);
}
);
}, 2000);
}
// our third async req
function get3rdTweet(){
get('https://api.myjson.com/bins/29e3f',
function(data){
totalTweets.push(data.response);
next();
},
function(err){
onError(err);
}
);
}
/**
* showStats
*
* Populate some stats
*/
function showStats(){
var totalTweets = document.querySelector('.totalTweets');
totalTweets.textContent = countProperties('user') || 0;
var totalPhotos = document.querySelector('.totalPhotos');
totalPhotos.textContent = countProperties('photo') || 0;
var totalFavourites = document.querySelector('.totalFavourites');
totalFavourites.textContent = countProperties('favourited') || 0;
next();
}
/**
* showTimeline
*
* Flesh out some sweet sweet tweet data
* then append to the .timeLine
*/
function showTimeline(){
var el = document.createElement('div');
totalTweets.forEach(function(tweet){
tweet = JSON.parse(tweet);
var tweetElement = el.cloneNode(true);
tweetElement.classList.add('tweet');
var name = el.cloneNode(true);
name.classList.add('tweet__name');
name.textContent = tweet.user.name;
var handle = el.cloneNode(true);
handle.classList.add('tweet__handle');
handle.textContent = '@' + tweet.user.handle;
var message = el.cloneNode(true);
message.classList.add('tweet__message');
message.textContent = tweet.message;
tweetElement.appendChild(name);
tweetElement.appendChild(handle);
tweetElement.appendChild(message);
timeLine.appendChild(tweetElement);
});
}
/**
* OnError
*
* @param {error} err
*/
function onError (err) {
console.error("Error: err");
}
/**
* countProperties
*
* Utility function to help us count certain props
*
* @param {String} a specific prop
* @return {Number} count
*/
function countProperties(prop) {
var count = 0;
totalTweets.forEach(function(tweet){
tweet = JSON.parse(tweet)
if (prop in tweet) count++;
});
return count;
}
// Define a queue of functions
var fns = [get1stTweet, get2ndTweet, get3rdTweet, showStats, showTimeline];
/**
* [next description]
* @param {[type]} val [description]
* @return {Function} [description]
*/
function next(){
var fn = fns.shift();
if (fn != null) {
fn();
}
else {
// just in case a method calls next() and there
// are more functions in `fns` to call
return;
}
}
// lets kick it off!
next();
</script>
</body>
</html>
@christiantakle

All the getTweet's functions can be simplified a bit by using the fact that you can use functions as values.

You allready defined onError, so there is no reasion to wrap it in another function for the get call.
The success function is identical for all the functions so it should be extracted and passed as a value aswell.

You could even mage get a higher-order function so you could write.

var get1stTweet = get('https://api.myjson.com/bins/2qjdn', onSuccess, onError);

Added the small change to the original code and tested that it works.

<html>
  <head>
    <style>
      body {
        background: #f5f8fa;
        font: 16px/22px Arial, sans-serif;
        margin: 0;
      }
      .statList {
        background: #fff;
        border-bottom: 1px solid #e1e8ed;
        height: 60px;
        overflow: hidden;
      }
      .statList__item {
        float: left;
        width: 75px;
        height: 60px;
        display: table;
      }
      .status {
        display: table-cell;
        text-align: center;
        text-transform: uppercase;
        vertical-align: middle;
      }
      .title {
        color: #66757f;
        font-size: 11px;
        letter-spacing: .02em;
        text-transform: uppercase;
      }
      .count {
        color: #C29A34;
        font-size: 18px;
        font-weight: 500;
        padding-top: 3px;
      }
      .tweetFeed {
        margin: 10px;
      }
      .tweet {
        background: #fff;
        border: 1px solid #e1e8ed;
        padding: 15px;
      }
      .tweet:nth-child(1){
        border-radius: 5px 5px 0 0;
      }
      .tweet:nth-child(n+2){
        border-top: none;
      }
      .tweet__name{
        color: #292f33;
        display: inline-block;
        font-size: 14px;
        font-weight: bold;
      }
      .tweet__handle {
        color: #8899a6;
        display: inline-block;
        font-size: 13px;
        margin-left: 10px;
      }
      .tweet__message {
        margin-top: 5px;
      }
    </style>
  </head>
  <body>
    <div class="statList">
      <div class="statList__item">
        <div class="status">
          <div class="title">Tweets</div>
          <div class="count totalTweets"></div>
        </div>
      </div>
      <div class="statList__item">
        <div class="status">
          <div class="title">Photos</div>
          <div class="count totalPhotos"></div>
        </div>
      </div>
      <div class="statList__item">
        <div class="status">
          <div class="title">Favourites</div>
          <div class="count totalFavourites"></div>
        </div>
      </div>
    </div>

    <div class="tweetFeed">
      <div class="timeLine"></div>
    </div>

    <script type="text/javascript">

      var
        timeLine = document.querySelector('.timeLine'),
        totalTweets = [],
        onSuccess =
          function success(data){
            totalTweets.push(data.response);
            next();
          };

      /**
       * get
       *
       * Tiny utility for making XHR requests
       * @param  {String}   url
       * @param  {Function} callback [description]
       */

      function get(url, callback, errback) {
        var req = new XMLHttpRequest();
        req.open('GET', url, true);
        req.onreadystatechange = function() {
          if(req.readyState != 4) return;
          if (req.status === 200) {
            callback(req);
          }
          else {
            errback(req);
          }
        };
        req.send(null);
      }


      /**
       * Asynch Requests
       */

      // our first async req
      function get1stTweet(){
        get('https://api.myjson.com/bins/2qjdn', onSuccess, onError);
      }

      // our second async req wrapped in a 2 second delay
      function get2ndTweet(){
        setTimeout(function(){
          get('https://api.myjson.com/bins/3zjqz', onSuccess, onError);
        }, 2000);
      }

      // our third async req
      function get3rdTweet(){
        get('https://api.myjson.com/bins/29e3f', onSuccess, onError);
      }


      /**
       * showStats
       *
       * Populate some stats
       */

      function showStats(){
        var totalTweets = document.querySelector('.totalTweets');
        totalTweets.textContent = countProperties('user') || 0;

        var totalPhotos  = document.querySelector('.totalPhotos');
        totalPhotos.textContent = countProperties('photo') || 0;

        var totalFavourites  = document.querySelector('.totalFavourites');
        totalFavourites.textContent = countProperties('favourited') || 0;

        next();
      }


      /**
       * showTimeline
       *
       * Flesh out some sweet sweet tweet data
       * then append to the .timeLine
       */

      function showTimeline(){
        var el = document.createElement('div');

        totalTweets.forEach(function(tweet){
          tweet = JSON.parse(tweet);

          var tweetElement = el.cloneNode(true);
          tweetElement.classList.add('tweet');

          var name = el.cloneNode(true);
          name.classList.add('tweet__name');
          name.textContent = tweet.user.name;

          var handle = el.cloneNode(true);
          handle.classList.add('tweet__handle');
          handle.textContent = '@' + tweet.user.handle;

          var message = el.cloneNode(true);
          message.classList.add('tweet__message');
          message.textContent = tweet.message;

          tweetElement.appendChild(name);
          tweetElement.appendChild(handle);
          tweetElement.appendChild(message);
          timeLine.appendChild(tweetElement);
        });
      }


      /**
       * OnError
       *
       * @param  {error} err
       */
      function onError (err) {
        console.error("Error: err");
      }


      /**
       * countProperties
       *
       * Utility function to help us count certain props
       *
       * @param  {String} a specific prop
       * @return {Number} count
       */

      function countProperties(prop) {
        var count = 0;

        totalTweets.forEach(function(tweet){
          tweet = JSON.parse(tweet)
          if (prop in tweet) count++;
        });

        return count;
      }

      // Define a queue of functions
      var fns = [get1stTweet, get2ndTweet, get3rdTweet, showStats, showTimeline];

      /**
       * [next description]
       * @param  {[type]}   val [description]
       * @return {Function}     [description]
       */
      function next(){
        var fn = fns.shift();

        if (fn != null) {
          fn();
        }
        else {
          // just in case a method calls next() and there
          // are more functions in `fns` to call
          return;
        }
      }

      // lets kick it off!
      next();
    </script>
  </body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment